From 89418376971a04aef39f169262c919f19eb5c478 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 18 Nov 2024 11:37:48 +0100 Subject: [PATCH] Improve backup screen and navigation (#22827) * Add location page * Start dashboard * Move list to dashboard * Add mocked config page * Fix hardcoded boolean * Add summary card * Use new format for BackupAgent * Use new API * Rename to ha-backup-summary-card * Use new api --- src/components/ha-circular-progress.ts | 3 +- src/data/backup.ts | 53 +-- src/dialogs/backup/dialog-backup-upload.ts | 1 + src/layouts/hass-tabs-subpage-data-table.ts | 1 + .../components/ha-backup-summary-card.ts | 149 ++++++++ .../ha-config-backup-automatic-config.ts | 132 +++++++ .../backup/ha-config-backup-dashboard.ts | 321 +++++++++++------- .../config/backup/ha-config-backup-details.ts | 2 +- .../config/backup/ha-config-backup-list.ts | 240 ------------- .../backup/ha-config-backup-locations.ts | 136 ++++++++ src/panels/config/backup/ha-config-backup.ts | 19 +- 11 files changed, 646 insertions(+), 411 deletions(-) create mode 100644 src/panels/config/backup/components/ha-backup-summary-card.ts create mode 100644 src/panels/config/backup/ha-config-backup-automatic-config.ts delete mode 100644 src/panels/config/backup/ha-config-backup-list.ts create mode 100644 src/panels/config/backup/ha-config-backup-locations.ts diff --git a/src/components/ha-circular-progress.ts b/src/components/ha-circular-progress.ts index 91fd45e9a1..bae2532d01 100644 --- a/src/components/ha-circular-progress.ts +++ b/src/components/ha-circular-progress.ts @@ -8,7 +8,7 @@ export class HaCircularProgress extends MdCircularProgress { @property({ attribute: "aria-label", type: String }) public ariaLabel = "Loading"; - @property() public size: "tiny" | "small" | "medium" | "large" = "medium"; + @property() public size?: "tiny" | "small" | "medium" | "large"; protected updated(changedProps: PropertyValues) { super.updated(changedProps); @@ -21,7 +21,6 @@ export class HaCircularProgress extends MdCircularProgress { case "small": this.style.setProperty("--md-circular-progress-size", "28px"); break; - // medium is default size case "medium": this.style.setProperty("--md-circular-progress-size", "48px"); break; diff --git a/src/data/backup.ts b/src/data/backup.ts index 0006a99e83..3cce889976 100644 --- a/src/data/backup.ts +++ b/src/data/backup.ts @@ -1,40 +1,44 @@ import type { HomeAssistant } from "../types"; -interface BackupSyncAgent { - id: string; +export interface BackupAgent { + agent_id: string; } -interface BaseBackupContent { +export interface BackupContent { slug: string; date: string; name: string; + protected: boolean; size: number; agents?: string[]; } -export interface BackupContent extends BaseBackupContent { - path?: string; -} - -export interface BackupSyncedContent extends BaseBackupContent { - id: string; - agent_id: string; -} - -export interface BackupData { - backing_up: boolean; +export interface BackupInfo { backups: BackupContent[]; + backing_up: boolean; +} + +export interface BackupDetails { + backup: BackupContent; } export interface BackupAgentsInfo { - agents: BackupSyncAgent[]; - syncing: boolean; + agents: BackupAgent[]; } +export type GenerateBackupParams = { + agent_ids: string[]; + database_included?: boolean; + folders_included?: string[]; + addons_included?: string[]; + name?: string; + password?: string; +}; + export const getBackupDownloadUrl = (slug: string) => `/api/backup/download/${slug}`; -export const fetchBackupInfo = (hass: HomeAssistant): Promise => +export const fetchBackupInfo = (hass: HomeAssistant): Promise => hass.callWS({ type: "backup/info", }); @@ -42,7 +46,7 @@ export const fetchBackupInfo = (hass: HomeAssistant): Promise => export const fetchBackupDetails = ( hass: HomeAssistant, slug: string -): Promise<{ backup: BackupContent }> => +): Promise => hass.callWS({ type: "backup/details", slug, @@ -55,13 +59,6 @@ export const fetchBackupAgentsInfo = ( type: "backup/agents/info", }); -export const fetchBackupAgentsSynced = ( - hass: HomeAssistant -): Promise => - hass.callWS({ - type: "backup/agents/synced", - }); - export const removeBackup = ( hass: HomeAssistant, slug: string @@ -71,9 +68,13 @@ export const removeBackup = ( slug, }); -export const generateBackup = (hass: HomeAssistant): Promise => +export const generateBackup = ( + hass: HomeAssistant, + params: GenerateBackupParams +): Promise<{ slug: string }> => hass.callWS({ type: "backup/generate", + ...params, }); export const uploadBackup = async ( diff --git a/src/dialogs/backup/dialog-backup-upload.ts b/src/dialogs/backup/dialog-backup-upload.ts index b4de65e0ee..dfffa561a4 100644 --- a/src/dialogs/backup/dialog-backup-upload.ts +++ b/src/dialogs/backup/dialog-backup-upload.ts @@ -6,6 +6,7 @@ import { fireEvent } from "../../common/dom/fire_event"; import "../../components/ha-alert"; import "../../components/ha-file-upload"; import "../../components/ha-header-bar"; +import "../../components/ha-dialog"; import "../../components/ha-icon-button"; import { uploadBackup } from "../../data/backup"; import { haStyleDialog } from "../../resources/styles"; diff --git a/src/layouts/hass-tabs-subpage-data-table.ts b/src/layouts/hass-tabs-subpage-data-table.ts index 9ac568e80d..378c0e04cf 100644 --- a/src/layouts/hass-tabs-subpage-data-table.ts +++ b/src/layouts/hass-tabs-subpage-data-table.ts @@ -456,6 +456,7 @@ export class HaTabsSubpageDataTable extends LitElement { ${!this.narrow ? html`
+
${this.hasFilters && !this.showFilters diff --git a/src/panels/config/backup/components/ha-backup-summary-card.ts b/src/panels/config/backup/components/ha-backup-summary-card.ts new file mode 100644 index 0000000000..ba5822e4b4 --- /dev/null +++ b/src/panels/config/backup/components/ha-backup-summary-card.ts @@ -0,0 +1,149 @@ +import { + mdiAlertCircleCheckOutline, + mdiAlertOutline, + mdiCheck, + mdiInformationOutline, + mdiSync, +} from "@mdi/js"; +import { css, html, LitElement, nothing } from "lit"; +import { customElement, property } from "lit/decorators"; +import "../../../../components/ha-button"; +import "../../../../components/ha-card"; +import "../../../../components/ha-circular-progress"; +import "../../../../components/ha-icon"; + +type SummaryStatus = "success" | "error" | "info" | "warning" | "loading"; + +const ICONS: Record = { + success: mdiCheck, + error: mdiAlertCircleCheckOutline, + warning: mdiAlertOutline, + info: mdiInformationOutline, + loading: mdiSync, +}; + +@customElement("ha-backup-summary-card") +class HaBackupSummaryCard extends LitElement { + @property() + public title!: string; + + @property() + public description!: string; + + @property({ type: Boolean, attribute: "has-action" }) + public hasAction = false; + + @property() + private status: SummaryStatus = "info"; + + render() { + return html` + +
+ ${this.status === "loading" + ? html`` + : html` +
+ +
+ `} + +
+

${this.title}

+

${this.description}

+
+ ${this.hasAction + ? html` +
+ +
+ ` + : nothing} +
+
+ `; + } + + static styles = css` + .summary { + display: flex; + flex-direction: row; + gap: 16px; + align-items: center; + padding: 20px; + width: 100%; + box-sizing: border-box; + } + .icon { + position: relative; + border-radius: 20px; + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + --icon-color: var(--primary-color); + } + .icon.success { + --icon-color: var(--success-color); + } + .icon.warning { + --icon-color: var(--warning-color); + } + .icon.error { + --icon-color: var(--error-color); + } + .icon::before { + display: block; + content: ""; + position: absolute; + inset: 0; + background-color: var(--icon-color, var(--primary-color)); + opacity: 0.2; + } + .icon ha-svg-icon { + color: var(--icon-color, var(--primary-color)); + width: 24px; + height: 24px; + } + ha-circular-progress { + --md-circular-progress-size: 40px; + } + .content { + display: flex; + flex-direction: column; + flex: 1; + min-width: 0; + } + .title { + font-size: 22px; + font-style: normal; + font-weight: 400; + line-height: 28px; + color: var(--primary-text-color); + margin: 0; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + .description { + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 20px; + letter-spacing: 0.25px; + color: var(--secondary-text-color); + margin: 0; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-backup-summary-card": HaBackupSummaryCard; + } +} diff --git a/src/panels/config/backup/ha-config-backup-automatic-config.ts b/src/panels/config/backup/ha-config-backup-automatic-config.ts new file mode 100644 index 0000000000..81c77b46e1 --- /dev/null +++ b/src/panels/config/backup/ha-config-backup-automatic-config.ts @@ -0,0 +1,132 @@ +import type { TemplateResult } from "lit"; +import { css, html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators"; +import "../../../components/ha-card"; +import "../../../components/ha-settings-row"; +import "../../../components/ha-switch"; +import "../../../components/ha-select"; +import "../../../components/ha-button"; +import "../../../components/ha-list-item"; +import "../../../layouts/hass-subpage"; +import type { HomeAssistant } from "../../../types"; + +@customElement("ha-config-backup-automatic-config") +class HaConfigBackupAutomaticConfig extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ type: Boolean }) public narrow = false; + + protected render(): TemplateResult { + return html` + +
+ +
Automation
+
+ + Schedule + + How often you want to create a backup. + + + Daily at 02:00 + + + + Maximum copies + + The number of backups that are saved + + + Latest 3 copies + + + + Locations + + What locations you want to automatically backup to. + + Configure + + + Password + + Automatic backups are protected with this password + + + + + Custom backup name + + By default it will use the date and description (2024-07-05 + Automatic backup). + + + +
+
+ +
Backup data
+
+ + + Home Assistant settings is always included + + + With these settings you are able to restore your system. + + Learn more + + + History + + For example of your energy dashboard. + + + + + Media + For example camera recordings. + + + + Add-ons + + Select what add-ons you want to backup. + + + All, including new (4) + + +
+
+
+
+ `; + } + + static styles = css` + .content { + padding: 28px 20px 0; + max-width: 690px; + margin: 0 auto; + gap: 24px; + display: flex; + flex-direction: column; + } + .card-content { + padding: 0; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-config-backup-automatic-config": HaConfigBackupAutomaticConfig; + } +} diff --git a/src/panels/config/backup/ha-config-backup-dashboard.ts b/src/panels/config/backup/ha-config-backup-dashboard.ts index 0940e7389b..ec9820649f 100644 --- a/src/panels/config/backup/ha-config-backup-dashboard.ts +++ b/src/panels/config/backup/ha-config-backup-dashboard.ts @@ -1,19 +1,35 @@ -import "@material/mwc-list/mwc-list"; -import type { TemplateResult } from "lit"; +import { mdiPlus } from "@mdi/js"; +import type { PropertyValues, TemplateResult } from "lit"; import { css, html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; +import { relativeTime } from "../../../common/datetime/relative_time"; +import { navigate } from "../../../common/navigate"; +import type { LocalizeFunc } from "../../../common/translations/localize"; +import type { + DataTableColumnContainer, + RowClickedEvent, +} from "../../../components/data-table/ha-data-table"; import "../../../components/ha-button"; import "../../../components/ha-card"; +import "../../../components/ha-fab"; import "../../../components/ha-icon"; import "../../../components/ha-icon-next"; -import "../../../components/ha-list-item"; -import "../../../layouts/hass-subpage"; - -import { navigate } from "../../../common/navigate"; -import { fetchBackupAgentsInfo } from "../../../data/backup"; +import "../../../components/ha-svg-icon"; +import { + fetchBackupInfo, + generateBackup, + type BackupContent, +} from "../../../data/backup"; +import "../../../layouts/hass-tabs-subpage-data-table"; import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; -import type { HomeAssistant } from "../../../types"; +import type { HomeAssistant, Route } from "../../../types"; import { brandsUrl } from "../../../util/brands-url"; +import { + showAlertDialog, + showConfirmationDialog, +} from "../../lovelace/custom-card-helpers"; +import "./components/ha-backup-summary-card"; @customElement("ha-config-backup-dashboard") class HaConfigBackupDashboard extends SubscribeMixin(LitElement) { @@ -21,151 +37,194 @@ class HaConfigBackupDashboard extends SubscribeMixin(LitElement) { @property({ type: Boolean }) public narrow = false; - @state() private _agents: { id: string }[] = []; + @property({ attribute: false }) public route!: Route; - protected firstUpdated(changedProps) { - super.firstUpdated(changedProps); - this._fetchAgents(); - } + @state() private _backingUp = false; + + @state() private _backups: BackupContent[] = []; + + private _columns = memoizeOne( + (localize: LocalizeFunc): DataTableColumnContainer => ({ + name: { + title: localize("ui.panel.config.backup.name"), + main: true, + sortable: true, + filterable: true, + flex: 2, + template: (backup) => backup.name, + }, + size: { + title: localize("ui.panel.config.backup.size"), + filterable: true, + sortable: true, + template: (backup) => Math.ceil(backup.size * 10) / 10 + " MB", + }, + date: { + title: localize("ui.panel.config.backup.created"), + direction: "desc", + filterable: true, + sortable: true, + template: (backup) => + relativeTime(new Date(backup.date), this.hass.locale), + }, + locations: { + title: "Locations", + template: (backup) => + html`${(backup.agents || []).map((agent) => { + const [domain, name] = agent.split("."); + return html` + ${name} + `; + })}`, + }, + }) + ); protected render(): TemplateResult { return html` - -
-
- -
-
-
- -
- -
Backed up
-
- Your configuration has been backed up. -
-
-
- - Show all backups - -
-
-
-
- -
-
Locations
-
- To keep your data safe it is recommended your backups is at - least on two different locations and one of them is off-site. -
- ${this._agents.length > 0 - ? html` - ${this._agents.map((agent) => { - const [domain, name] = agent.id.split("."); - return html` - cloud - - ${this.hass.localize(`component.${domain}.title`) || - domain}: - ${name} - - - `; - })} - ` - : html`

No sync agents configured

`} -
-
-
+
+ + + Configure + + + + + Configure + +
- + + + + `; } - private async _fetchAgents() { - const resp = await fetchBackupAgentsInfo(this.hass); - this._agents = resp.agents; + protected firstUpdated(changedProps: PropertyValues) { + super.firstUpdated(changedProps); + this._fetchBackupInfo(); } - private _showBackupList(): void { - navigate("/config/backup/list"); + private async _fetchBackupInfo() { + const info = await fetchBackupInfo(this.hass); + this._backups = info.backups; + this._backingUp = info.backing_up; } - private _showAgentSyncs(event: Event): void { - const agent = (event.currentTarget as any).agent; - navigate(`/config/backup/list?agent=${agent}`); + private async _generateBackup(): Promise { + const confirm = await showConfirmationDialog(this, { + title: this.hass.localize("ui.panel.config.backup.create.title"), + text: this.hass.localize("ui.panel.config.backup.create.description"), + confirmText: this.hass.localize("ui.panel.config.backup.create.confirm"), + }); + if (!confirm) { + return; + } + + try { + await generateBackup(this.hass, { + agent_ids: ["backup.local"], + }); + } catch (err) { + showAlertDialog(this, { text: (err as Error).message }); + } + + await this._fetchBackupInfo(); + + // Todo subscribe for status updates instead of polling + const interval = setInterval(async () => { + await this._fetchBackupInfo(); + if (!this._backingUp) { + clearInterval(interval); + } + }, 2000); + } + + private _showBackupDetails(ev: CustomEvent): void { + const slug = (ev.detail as RowClickedEvent).id; + navigate(`/config/backup/details/${slug}`); + } + + private _configureAutomaticBackup() { + navigate("/config/backup/automatic-config"); + } + + private _configureBackupLocations() { + navigate("/config/backup/locations"); } static styles = css` - .content { - padding: 28px 20px 0; - max-width: 690px; - margin: 0 auto; - gap: 24px; - display: grid; - } - - .backup-status .card-content { + .header { + padding: 16px 16px 0 16px; display: flex; flex-direction: row; - justify-content: space-between; + gap: 16px; + background-color: var(--primary-background-color); + } + @media (max-width: 1000px) { + .header { + flex-direction: column; + } + } + .header > * { + flex: 1; + min-width: 0; } - .backup-status-contents { - display: flex; - flex-direction: row; - } - - .status-icon { - color: var(--success-color); - height: 100%; - align-content: center; - } - - .status-icon ha-icon { - --mdc-icon-size: 40px; - padding: 8px 16px 8px 8px; - } - - .status-header { - font-size: 22px; - line-height: 28px; - } - - .status-text { - font-size: 14px; - line-height: 20px; - color: var(--secondary-text-color); - } - - ha-button.show-all-backups { - align-items: center; + ha-fab[disabled] { + --mdc-theme-secondary: var(--disabled-text-color) !important; } `; } diff --git a/src/panels/config/backup/ha-config-backup-details.ts b/src/panels/config/backup/ha-config-backup-details.ts index da538819be..f0b3a972aa 100644 --- a/src/panels/config/backup/ha-config-backup-details.ts +++ b/src/panels/config/backup/ha-config-backup-details.ts @@ -43,7 +43,7 @@ class HaConfigBackupDetails extends LitElement { } return html` => ({ - name: { - title: localize("ui.panel.config.backup.name"), - main: true, - sortable: true, - filterable: true, - flex: 2, - template: (backup) => - narrow || !backup.path - ? backup.name - : html`${backup.name} -
${backup.path}
`, - }, - path: { - title: localize("ui.panel.config.backup.path"), - hidden: !narrow, - template: (backup) => backup.path || "-", - }, - size: { - title: localize("ui.panel.config.backup.size"), - filterable: true, - sortable: true, - template: (backup) => Math.ceil(backup.size * 10) / 10 + " MB", - }, - date: { - title: localize("ui.panel.config.backup.created"), - direction: "desc", - filterable: true, - sortable: true, - template: (backup) => - relativeTime(new Date(backup.date), this.hass.locale), - }, - locations: { - title: "Locations", - template: (backup) => - html`${[ - ...(backup.path ? [localAgent] : []), - ...(backup.agents || []).sort(), - ].map((agent) => { - const [domain, name] = agent.split("."); - return html`${name}`; - })}`, - }, - }) - ); - - private _getItems = memoize((backupItems: BackupContent[]) => - backupItems.map((backup) => ({ - name: backup.name, - slug: backup.slug, - date: backup.date, - size: backup.size, - path: backup.path, - agents: backup.agents, - })) - ); - - protected render(): TemplateResult { - if (!this.hass || this._backupData === undefined) { - return html``; - } - - return html` - - - ${this._backupData.backing_up - ? html`` - : html``} - - - `; - } - - protected firstUpdated(changedProps: PropertyValues) { - super.firstUpdated(changedProps); - this._getBackups(); - } - - private async _getBackups(): Promise { - const backupData: Record = {}; - const [local, synced] = await Promise.all([ - fetchBackupInfo(this.hass), - fetchBackupAgentsSynced(this.hass), - ]); - - for (const backup of local.backups) { - backupData[backup.slug] = backup; - } - for (const agent of synced) { - if (!(agent.slug in backupData)) { - backupData[agent.slug] = { ...agent, agents: [agent.agent_id] }; - } else if (!("agents" in backupData[agent.slug])) { - backupData[agent.slug].agents = [agent.agent_id]; - } else { - backupData[agent.slug].agents!.push(agent.agent_id); - } - } - this._backupData = { - backing_up: local.backing_up, - backups: Object.values(backupData), - }; - } - - private async _generateBackup(): Promise { - const confirm = await showConfirmationDialog(this, { - title: this.hass.localize("ui.panel.config.backup.create.title"), - text: this.hass.localize("ui.panel.config.backup.create.description"), - confirmText: this.hass.localize("ui.panel.config.backup.create.confirm"), - }); - if (!confirm) { - return; - } - - generateBackup(this.hass) - .then(() => this._getBackups()) - .catch((err) => showAlertDialog(this, { text: (err as Error).message })); - - await this._getBackups(); - } - - private _showBackupDetails(ev: CustomEvent): void { - const slug = (ev.detail as RowClickedEvent).id; - navigate(`/config/backup/details/${slug}`); - } - - static get styles(): CSSResultGroup { - return [ - css` - ha-fab[disabled] { - --mdc-theme-secondary: var(--disabled-text-color) !important; - } - `, - ]; - } -} - -declare global { - interface HTMLElementTagNameMap { - "ha-config-backup-list": HaConfigBackup; - } -} diff --git a/src/panels/config/backup/ha-config-backup-locations.ts b/src/panels/config/backup/ha-config-backup-locations.ts new file mode 100644 index 0000000000..505f04b95b --- /dev/null +++ b/src/panels/config/backup/ha-config-backup-locations.ts @@ -0,0 +1,136 @@ +import type { TemplateResult } from "lit"; +import { css, html, LitElement } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import "../../../components/ha-card"; +import "../../../components/ha-icon-next"; +import "../../../components/ha-md-list"; +import "../../../components/ha-md-list-item"; +import type { BackupAgent } from "../../../data/backup"; +import { fetchBackupAgentsInfo } from "../../../data/backup"; +import "../../../layouts/hass-subpage"; +import type { HomeAssistant } from "../../../types"; +import { brandsUrl } from "../../../util/brands-url"; + +@customElement("ha-config-backup-locations") +class HaConfigBackupLocations extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ type: Boolean }) public narrow = false; + + @state() private _agents: BackupAgent[] = []; + + protected firstUpdated(changedProps) { + super.firstUpdated(changedProps); + this._fetchAgents(); + } + + protected render(): TemplateResult { + return html` + +
+
+

Locations

+

+ To keep your data safe it is recommended your backups is at least + on two different locations and one of them is off-site. +

+
+ +
+ ${this._agents.length > 0 + ? html` + + ${this._agents.map((agent) => { + const [domain, name] = agent.agent_id.split("."); + const domainName = + this.hass.localize(`component.${domain}.title`) || + domain; + return html` + + +
${domainName}: ${name}
+ +
+ `; + })} +
+ ` + : html`

No sync agents configured

`} +
+
+
+
+ `; + } + + private async _fetchAgents() { + const data = await fetchBackupAgentsInfo(this.hass); + this._agents = data.agents; + } + + static styles = css` + .content { + padding: 28px 20px 0; + max-width: 690px; + margin: 0 auto; + gap: 24px; + display: flex; + flex-direction: column; + } + + .header .title { + font-size: 22px; + font-style: normal; + font-weight: 400; + line-height: 28px; + color: var(--primary-text-color); + margin: 0; + margin-bottom: 8px; + } + + .header .description { + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 20px; + letter-spacing: 0.25px; + color: var(--secondary-text-color); + margin: 0; + } + + .agents ha-md-list { + background: none; + } + .agents ha-md-list-item img { + width: 48px; + } + .card-content { + padding: 0; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-config-backup-locations": HaConfigBackupLocations; + } +} diff --git a/src/panels/config/backup/ha-config-backup.ts b/src/panels/config/backup/ha-config-backup.ts index cc02ae4972..0e2dd4d114 100644 --- a/src/panels/config/backup/ha-config-backup.ts +++ b/src/panels/config/backup/ha-config-backup.ts @@ -12,10 +12,6 @@ class HaConfigBackup extends HassRouterPage { @property({ type: Boolean }) public narrow = false; - @property({ type: Boolean }) public isWide = false; - - @property({ type: Boolean }) public showAdvanced = false; - protected routerOptions: RouterOptions = { defaultPage: "dashboard", routes: { @@ -23,23 +19,24 @@ class HaConfigBackup extends HassRouterPage { tag: "ha-config-backup-dashboard", cache: true, }, - list: { - tag: "ha-config-backup-list", - load: () => import("./ha-config-backup-list"), - }, details: { tag: "ha-config-backup-details", load: () => import("./ha-config-backup-details"), }, + locations: { + tag: "ha-config-backup-locations", + load: () => import("./ha-config-backup-locations"), + }, + "automatic-config": { + tag: "ha-config-backup-automatic-config", + load: () => import("./ha-config-backup-automatic-config"), + }, }, }; protected updatePageEl(pageEl, changedProps: PropertyValues) { pageEl.hass = this.hass; - pageEl.narrow = this.narrow; - pageEl.isWide = this.isWide; pageEl.route = this.routeTail; - pageEl.showAdvanced = this.showAdvanced; if ( (!changedProps || changedProps.has("route")) &&