Backup tweaks (#24165)

* Backup tweaks

* Show progress in fab

* Revert unused changes

---------

Co-authored-by: Wendelin <w@pe8.at>
This commit is contained in:
Bram Kragten 2025-02-18 14:02:53 +01:00 committed by GitHub
parent 347ee2a4c3
commit d387f19a31
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 106 additions and 14 deletions

View File

@ -12,6 +12,7 @@ import type { HomeAssistant } from "../types";
import { fileDownload } from "../util/file_download"; import { fileDownload } from "../util/file_download";
import { domainToName } from "./integration"; import { domainToName } from "./integration";
import type { FrontendLocaleData } from "./translation"; import type { FrontendLocaleData } from "./translation";
import type { BackupManagerState, ManagerStateEvent } from "./backup_manager";
import checkValidDate from "../common/datetime/check_valid_date"; import checkValidDate from "../common/datetime/check_valid_date";
import { handleFetchPromise } from "../util/hass-call-api"; import { handleFetchPromise } from "../util/hass-call-api";
@ -130,7 +131,13 @@ export interface BackupContentExtended extends BackupContent, BackupData {}
export interface BackupInfo { export interface BackupInfo {
backups: BackupContent[]; backups: BackupContent[];
backing_up: boolean; agent_errors: Record<string, string>;
last_attempted_automatic_backup: string | null;
last_completed_automatic_backup: string | null;
last_non_idle_event: ManagerStateEvent | null;
next_automatic_backup: string | null;
next_automatic_backup_additional: boolean;
state: BackupManagerState;
} }
export interface BackupDetails { export interface BackupDetails {

View File

@ -4,6 +4,7 @@ import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../../common/dom/fire_event"; import { fireEvent } from "../../../../../common/dom/fire_event";
import { computeDomain } from "../../../../../common/entity/compute_domain"; import { computeDomain } from "../../../../../common/entity/compute_domain";
import { navigate } from "../../../../../common/navigate";
import "../../../../../components/ha-icon-button"; import "../../../../../components/ha-icon-button";
import "../../../../../components/ha-md-list"; import "../../../../../components/ha-md-list";
import "../../../../../components/ha-md-list-item"; import "../../../../../components/ha-md-list-item";
@ -22,7 +23,6 @@ import {
import type { CloudStatus } from "../../../../../data/cloud"; import type { CloudStatus } from "../../../../../data/cloud";
import type { HomeAssistant } from "../../../../../types"; import type { HomeAssistant } from "../../../../../types";
import { brandsUrl } from "../../../../../util/brands-url"; import { brandsUrl } from "../../../../../util/brands-url";
import { navigate } from "../../../../../common/navigate";
const DEFAULT_AGENTS = []; const DEFAULT_AGENTS = [];

View File

@ -8,7 +8,7 @@ import {
mdiUpload, mdiUpload,
} from "@mdi/js"; } from "@mdi/js";
import type { CSSResultGroup, TemplateResult } from "lit"; import type { CSSResultGroup, TemplateResult } from "lit";
import { html, LitElement, nothing } from "lit"; import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { isComponentLoaded } from "../../../common/config/is_component_loaded";
@ -27,6 +27,7 @@ import type {
} from "../../../components/data-table/ha-data-table"; } from "../../../components/data-table/ha-data-table";
import "../../../components/ha-button"; import "../../../components/ha-button";
import "../../../components/ha-button-menu"; import "../../../components/ha-button-menu";
import "../../../components/ha-circular-progress";
import "../../../components/ha-fab"; import "../../../components/ha-fab";
import "../../../components/ha-filter-states"; import "../../../components/ha-filter-states";
import "../../../components/ha-icon"; import "../../../components/ha-icon";
@ -460,7 +461,17 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
extended extended
@click=${this._newBackup} @click=${this._newBackup}
> >
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon> ${backupInProgress
? html`<div slot="icon">
<ha-circular-progress
.size=${"small"}
indeterminate
></ha-circular-progress>
</div>`
: html`<ha-svg-icon
slot="icon"
.path=${mdiPlus}
></ha-svg-icon>`}
</ha-fab> </ha-fab>
` `
: nothing} : nothing}
@ -605,7 +616,14 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
} }
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return haStyle; return [
haStyle,
css`
ha-circular-progress {
--md-sys-color-primary: var(--mdc-theme-on-secondary);
}
`,
];
} }
} }

View File

@ -8,6 +8,7 @@ import "../../../components/ha-button";
import "../../../components/ha-button-menu"; import "../../../components/ha-button-menu";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-fab"; import "../../../components/ha-fab";
import "../../../components/ha-circular-progress";
import "../../../components/ha-icon"; import "../../../components/ha-icon";
import "../../../components/ha-icon-next"; import "../../../components/ha-icon-next";
import "../../../components/ha-icon-overflow-menu"; import "../../../components/ha-icon-overflow-menu";
@ -17,8 +18,10 @@ import type {
BackupAgent, BackupAgent,
BackupConfig, BackupConfig,
BackupContent, BackupContent,
BackupInfo,
} from "../../../data/backup"; } from "../../../data/backup";
import { import {
computeBackupAgentName,
generateBackup, generateBackup,
generateBackupWithAutomaticSettings, generateBackupWithAutomaticSettings,
} from "../../../data/backup"; } from "../../../data/backup";
@ -50,6 +53,8 @@ class HaConfigBackupOverview extends LitElement {
@property({ attribute: false }) public manager!: ManagerStateEvent; @property({ attribute: false }) public manager!: ManagerStateEvent;
@property({ attribute: false }) public info?: BackupInfo;
@property({ attribute: false }) public backups: BackupContent[] = []; @property({ attribute: false }) public backups: BackupContent[] = [];
@property({ attribute: false }) public fetching = false; @property({ attribute: false }) public fetching = false;
@ -151,6 +156,26 @@ class HaConfigBackupOverview extends LitElement {
</ha-list-item> </ha-list-item>
</ha-button-menu> </ha-button-menu>
<div class="content"> <div class="content">
${this.info && Object.keys(this.info.agent_errors).length
? html`${Object.entries(this.info.agent_errors).map(
([agentId, error]) =>
html`<ha-alert
alert-type="error"
.title=${this.hass.localize(
"ui.panel.config.backup.overview.agent_error",
{
name: computeBackupAgentName(
this.hass.localize,
agentId,
this.agents
),
}
)}
>
${error}
</ha-alert>`
)}`
: nothing}
${backupInProgress ${backupInProgress
? html` ? html`
<ha-backup-overview-progress <ha-backup-overview-progress
@ -204,7 +229,14 @@ class HaConfigBackupOverview extends LitElement {
extended extended
@click=${this._newBackup} @click=${this._newBackup}
> >
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon> ${backupInProgress
? html`<div slot="icon">
<ha-circular-progress
.size=${"small"}
indeterminate
></ha-circular-progress>
</div>`
: html`<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>`}
</ha-fab> </ha-fab>
</hass-subpage> </hass-subpage>
`; `;
@ -231,6 +263,9 @@ class HaConfigBackupOverview extends LitElement {
padding-left: 0; padding-left: 0;
padding-right: 0; padding-right: 0;
} }
ha-circular-progress {
--md-sys-color-primary: var(--mdc-theme-on-secondary);
}
`, `,
]; ];
} }

View File

@ -1,4 +1,4 @@
import { mdiDotsVertical, mdiHarddisk } from "@mdi/js"; import { mdiDotsVertical, mdiHarddisk, mdiOpenInNew } from "@mdi/js";
import type { PropertyValues } from "lit"; import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit"; import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
@ -28,6 +28,7 @@ import "./components/config/ha-backup-config-encryption-key";
import "./components/config/ha-backup-config-schedule"; import "./components/config/ha-backup-config-schedule";
import type { BackupConfigSchedule } from "./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"; import { showLocalBackupLocationDialog } from "./dialogs/show-dialog-local-backup-location";
import { documentationUrl } from "../../../util/documentation-url";
@customElement("ha-config-backup-settings") @customElement("ha-config-backup-settings")
class HaConfigBackupSettings extends LitElement { class HaConfigBackupSettings extends LitElement {
@ -98,6 +99,8 @@ class HaConfigBackupSettings extends LitElement {
return nothing; return nothing;
} }
const supervisor = isComponentLoaded(this.hass, "hassio");
return html` return html`
<hass-subpage <hass-subpage
back-path="/config/backup" back-path="/config/backup"
@ -105,7 +108,7 @@ class HaConfigBackupSettings extends LitElement {
.narrow=${this.narrow} .narrow=${this.narrow}
.header=${this.hass.localize("ui.panel.config.backup.settings.header")} .header=${this.hass.localize("ui.panel.config.backup.settings.header")}
> >
${isComponentLoaded(this.hass, "hassio") ${supervisor
? html` ? html`
<ha-button-menu slot="toolbar-icon"> <ha-button-menu slot="toolbar-icon">
<ha-icon-button <ha-icon-button
@ -203,6 +206,29 @@ class HaConfigBackupSettings extends LitElement {
` `
: nothing} : nothing}
</div> </div>
<div class="card-actions">
<a
href=${documentationUrl(this.hass, "/integrations/#backup")}
target="_blank"
rel="noreferrer"
>
<ha-button>
<ha-svg-icon slot="icon" .path=${mdiOpenInNew}></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.backup.settings.locations.more_locations"
)}
</ha-button>
</a>
${supervisor
? html`<a href="/config/storage">
<ha-button>
${this.hass.localize(
"ui.panel.config.backup.settings.locations.manage_network_storage"
)}
</ha-button>
</a>`
: nothing}
</div>
</ha-card> </ha-card>
<ha-card> <ha-card>
<div class="card-header"> <div class="card-header">
@ -342,6 +368,9 @@ class HaConfigBackupSettings extends LitElement {
.card-content { .card-content {
padding-bottom: 0; padding-bottom: 0;
} }
a {
text-decoration: none;
}
`; `;
} }

View File

@ -4,7 +4,7 @@ import { customElement, property, state } from "lit/decorators";
import type { import type {
BackupAgent, BackupAgent,
BackupConfig, BackupConfig,
BackupContent, BackupInfo,
} from "../../../data/backup"; } from "../../../data/backup";
import { import {
compareAgents, compareAgents,
@ -44,7 +44,7 @@ class HaConfigBackup extends SubscribeMixin(HassRouterPage) {
@state() private _manager: ManagerStateEvent = DEFAULT_MANAGER_STATE; @state() private _manager: ManagerStateEvent = DEFAULT_MANAGER_STATE;
@state() private _backups: BackupContent[] = []; @state() private _info?: BackupInfo;
@state() private _agents: BackupAgent[] = []; @state() private _agents: BackupAgent[] = [];
@ -87,8 +87,7 @@ class HaConfigBackup extends SubscribeMixin(HassRouterPage) {
} }
private async _fetchBackupInfo() { private async _fetchBackupInfo() {
const info = await fetchBackupInfo(this.hass); this._info = await fetchBackupInfo(this.hass);
this._backups = info.backups;
} }
private async _fetchBackupConfig() { private async _fetchBackupConfig() {
@ -134,7 +133,8 @@ class HaConfigBackup extends SubscribeMixin(HassRouterPage) {
pageEl.narrow = this.narrow; pageEl.narrow = this.narrow;
pageEl.cloudStatus = this.cloudStatus; pageEl.cloudStatus = this.cloudStatus;
pageEl.manager = this._manager; pageEl.manager = this._manager;
pageEl.backups = this._backups; pageEl.info = this._info;
pageEl.backups = this._info?.backups || [];
pageEl.config = this._config; pageEl.config = this._config;
pageEl.agents = this._agents; pageEl.agents = this._agents;
pageEl.fetching = this._fetching; pageEl.fetching = this._fetching;

View File

@ -2512,6 +2512,7 @@
"menu": { "menu": {
"upload_backup": "Upload backup" "upload_backup": "Upload backup"
}, },
"agent_error": "Error in location {name}",
"new_backup": "Backup now", "new_backup": "Backup now",
"onboarding": { "onboarding": {
"title": "Set up backups", "title": "Set up backups",
@ -2650,7 +2651,9 @@
"title": "Locations", "title": "Locations",
"description": "Your backup will be stored on these locations when this default backup is created. You can use all locations for custom backups.", "description": "Your backup will be stored on these locations when this default backup is created. You can use all locations for custom backups.",
"no_location": "No location selected", "no_location": "No location selected",
"no_location_description": "You have to select at least one location to create a backup." "no_location_description": "You have to select at least one location to create a backup.",
"more_locations": "Explore more locations",
"manage_network_storage": "Manage network storage"
}, },
"encryption_key": { "encryption_key": {
"title": "Encryption key", "title": "Encryption key",