1
0
mirror of https://github.com/home-assistant/frontend.git synced 2025-12-19 18:28:42 +00:00

Improve onboarding backup restore (#23340)

* Improve onboarding backup restore

* Fix onboarding backup restore

* Fix restoring value in onboarding-restore-backup
This commit is contained in:
Wendelin
2024-12-20 12:13:16 +01:00
committed by GitHub
parent f1f53b9f24
commit 37aa2bd869
7 changed files with 265 additions and 207 deletions

View File

@@ -1,7 +1,7 @@
import { mdiFolderUpload } from "@mdi/js";
import type { TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, state } from "lit/decorators";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/components/ha-circular-progress";
import "../../../src/components/ha-file-upload";
@@ -10,10 +10,12 @@ import { uploadBackup } from "../../../src/data/hassio/backup";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
import type { HomeAssistant } from "../../../src/types";
import type { LocalizeFunc } from "../../../src/common/translations/localize";
declare global {
interface HASSDomEvents {
"backup-uploaded": { backup: HassioBackup };
"backup-cleared": void;
}
}
@@ -21,6 +23,8 @@ declare global {
export class HassioUploadBackup extends LitElement {
public hass?: HomeAssistant;
@property({ attribute: false }) public localize?: LocalizeFunc;
@state() public value: string | null = null;
@state() private _uploading = false;
@@ -32,13 +36,26 @@ export class HassioUploadBackup extends LitElement {
.uploading=${this._uploading}
.icon=${mdiFolderUpload}
accept="application/x-tar"
label="Upload backup"
supports="Supports .TAR files"
.label=${this.localize?.(
"ui.panel.page-onboarding.restore.upload_backup"
) || "Upload backup"}
.supports=${this.localize?.(
"ui.panel.page-onboarding.restore.upload_supports"
) || "Supports .TAR files"}
.secondary=${this.localize?.(
"ui.panel.page-onboarding.restore.upload_drop"
) || "Or drop your file here"}
@file-picked=${this._uploadFile}
@files-cleared=${this._clear}
></ha-file-upload>
`;
}
private _clear() {
this.value = null;
fireEvent(this, "backup-cleared");
}
private async _uploadFile(ev) {
const file = ev.detail.files[0];

View File

@@ -65,7 +65,7 @@ const _computeAddons = (addons): AddonCheckboxItem[] =>
@customElement("supervisor-backup-content")
export class SupervisorBackupContent extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public localize?: LocalizeFunc;
@@ -186,12 +186,13 @@ export class SupervisorBackupContent extends LitElement {
.iconPath=${mdiHomeAssistant}
.version=${this.backup
? this.backup.homeassistant
: this.hass.config.version}
: this.hass?.config.version}
>
</supervisor-formfield-label>`}
>
<ha-checkbox
.checked=${this.homeAssistant}
.checked=${this.onboarding || this.homeAssistant}
.disabled=${this.onboarding}
@change=${this._toggleHomeAssistant}
>
</ha-checkbox>
@@ -334,7 +335,7 @@ export class SupervisorBackupContent extends LitElement {
| HassioFullBackupCreateParams {
const data: any = {};
if (!this.backup) {
if (!this.backup && this.hass) {
data.name =
this.backupName ||
formatDate(new Date(), this.hass.locale, this.hass.config);
@@ -364,7 +365,9 @@ export class SupervisorBackupContent extends LitElement {
if (folders?.length) {
data.folders = folders;
}
data.homeassistant = this.homeAssistant;
// onboarding needs at least homeassistant to restore
data.homeassistant = this.onboarding || this.homeAssistant;
return data;
}
@@ -386,6 +389,7 @@ export class SupervisorBackupContent extends LitElement {
.iconPath=${section === "addons" ? mdiPuzzle : mdiFolder}
.imageUrl=${section === "addons" &&
!this.onboarding &&
this.hass &&
atLeastVersion(this.hass.config.version, 0, 105) &&
addons?.get(item.slug)?.icon
? `/api/hassio/addons/${item.slug}/icon`

View File

@@ -8,9 +8,11 @@ import { atLeastVersion } from "../../../../src/common/config/version";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { stopPropagation } from "../../../../src/common/dom/stop_propagation";
import { slugify } from "../../../../src/common/string/slugify";
import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-md-dialog";
import "../../../../src/components/ha-dialog-header";
import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-button";
import "../../../../src/components/ha-button-menu";
import "../../../../src/components/ha-header-bar";
import "../../../../src/components/ha-icon-button";
@@ -19,6 +21,7 @@ import type { HassioBackupDetail } from "../../../../src/data/hassio/backup";
import {
fetchHassioBackupInfo,
removeBackup,
restoreBackup,
} from "../../../../src/data/hassio/backup";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import {
@@ -33,6 +36,7 @@ import "../../components/supervisor-backup-content";
import type { SupervisorBackupContent } from "../../components/supervisor-backup-content";
import type { HassioBackupDialogParams } from "./show-dialog-hassio-backup";
import type { BackupOrRestoreKey } from "../../util/translations";
import type { HaMdDialog } from "../../../../src/components/ha-md-dialog";
@customElement("dialog-hassio-backup")
class HassioBackupDialog
@@ -52,13 +56,20 @@ class HassioBackupDialog
@query("supervisor-backup-content")
private _backupContent!: SupervisorBackupContent;
@query("ha-md-dialog") private _dialog?: HaMdDialog;
public async showDialog(dialogParams: HassioBackupDialogParams) {
this._backup = await fetchHassioBackupInfo(this.hass, dialogParams.slug);
this._dialogParams = dialogParams;
this._backup = await fetchHassioBackupInfo(this.hass, dialogParams.slug);
if (!this._backup) {
this._error = this._localize("no_backup_found");
} else if (this._dialogParams.onboarding && !this._backup.homeassistant) {
this._error = this._localize("restore_no_home_assistant");
}
this._restoringBackup = false;
}
public closeDialog() {
private _dialogClosed(): void {
this._backup = undefined;
this._dialogParams = undefined;
this._restoringBackup = false;
@@ -66,6 +77,10 @@ class HassioBackupDialog
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
public closeDialog(): void {
this._dialog?.close();
}
private _localize(key: BackupOrRestoreKey) {
return (
this._dialogParams!.supervisor?.localize(`backup.${key}`) ||
@@ -78,100 +93,80 @@ class HassioBackupDialog
return nothing;
}
return html`
<ha-dialog
<ha-md-dialog
open
scrimClickAction
@closed=${this.closeDialog}
.heading=${this._backup.name}
.disableCancelAction=${!this._error}
@closed=${this._dialogClosed}
>
<div slot="heading">
<ha-header-bar>
<span slot="title">${this._backup.name}</span>
<ha-icon-button
.label=${this._localize("close")}
.path=${mdiClose}
slot="actionItems"
dialogAction="cancel"
></ha-icon-button>
</ha-header-bar>
<ha-dialog-header slot="headline">
<ha-icon-button
slot="navigationIcon"
.label=${this._localize("close")}
.path=${mdiClose}
@click=${this.closeDialog}
.disabled=${this._restoringBackup}
></ha-icon-button>
<span slot="title" .title=${this._backup.name}
>${this._backup.name}</span
>
${!this._dialogParams.onboarding && this._dialogParams.supervisor
? html`<ha-button-menu
slot="actionItems"
fixed
@action=${this._handleMenuAction}
@closed=${stopPropagation}
>
<ha-icon-button
.label=${this._dialogParams.supervisor.localize(
"backup.more_actions"
)}
.path=${mdiDotsVertical}
slot="trigger"
></ha-icon-button>
<mwc-list-item
>${this._dialogParams.supervisor.localize(
"backup.download_backup"
)}</mwc-list-item
>
<mwc-list-item class="error"
>${this._dialogParams.supervisor.localize(
"backup.delete_backup_title"
)}</mwc-list-item
>
</ha-button-menu>`
: nothing}
</ha-dialog-header>
<div slot="content">
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: this._restoringBackup
? html`<div class="loading">
<ha-circular-progress indeterminate></ha-circular-progress>
</div>`
: html`
<supervisor-backup-content
.hass=${this.hass}
.supervisor=${this._dialogParams.supervisor}
.backup=${this._backup}
.onboarding=${this._dialogParams.onboarding || false}
.localize=${this._dialogParams.localize}
dialogInitialFocus
>
</supervisor-backup-content>
`}
</div>
${this._restoringBackup
? html`<ha-circular-progress indeterminate></ha-circular-progress>`
: html`
<supervisor-backup-content
.hass=${this.hass}
.supervisor=${this._dialogParams.supervisor}
.backup=${this._backup}
.onboarding=${this._dialogParams.onboarding || false}
.localize=${this._dialogParams.localize}
dialogInitialFocus
>
</supervisor-backup-content>
`}
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: nothing}
<mwc-button
.disabled=${this._restoringBackup}
slot="secondaryAction"
@click=${this._restoreClicked}
>
${this._localize("restore")}
</mwc-button>
${!this._dialogParams.onboarding && this._dialogParams.supervisor
? html`<ha-button-menu
fixed
slot="primaryAction"
@action=${this._handleMenuAction}
@closed=${stopPropagation}
>
<ha-icon-button
.label=${this._dialogParams.supervisor.localize(
"backup.more_actions"
)}
.path=${mdiDotsVertical}
slot="trigger"
></ha-icon-button>
<mwc-list-item
>${this._dialogParams.supervisor.localize(
"backup.download_backup"
)}</mwc-list-item
>
<mwc-list-item class="error"
>${this._dialogParams.supervisor.localize(
"backup.delete_backup_title"
)}</mwc-list-item
>
</ha-button-menu>`
: nothing}
</ha-dialog>
<div slot="actions">
<ha-button
.disabled=${this._restoringBackup || !!this._error}
@click=${this._restoreClicked}
>
${this._localize("restore")}
</ha-button>
</div>
</ha-md-dialog>
`;
}
static get styles(): CSSResultGroup {
return [
haStyle,
haStyleDialog,
css`
ha-circular-progress {
display: block;
text-align: center;
}
ha-header-bar {
--mdc-theme-on-primary: var(--primary-text-color);
--mdc-theme-primary: var(--mdc-theme-surface);
flex-shrink: 0;
display: block;
}
ha-icon-button {
color: var(--secondary-text-color);
}
`,
];
}
private _handleMenuAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
@@ -184,18 +179,9 @@ class HassioBackupDialog
}
private async _restoreClicked() {
const backupDetails = this._backupContent.backupDetails();
this._restoringBackup = true;
this._dialogParams?.onRestoring?.();
if (this._backupContent.backupType === "full") {
await this._fullRestoreClicked(backupDetails);
} else {
await this._partialRestoreClicked(backupDetails);
}
this._restoringBackup = false;
}
const backupDetails = this._backupContent.backupDetails();
private async _partialRestoreClicked(backupDetails) {
const supervisor = this._dialogParams?.supervisor;
if (supervisor !== undefined && supervisor.info.state !== "running") {
await showAlertDialog(this, {
@@ -204,91 +190,45 @@ class HassioBackupDialog
state: supervisor.info.state,
}),
});
this._restoringBackup = false;
return;
}
if (
!(await showConfirmationDialog(this, {
title: this._localize("confirm_restore_partial_backup_title"),
text: this._localize("confirm_restore_partial_backup_text"),
title: this._localize(
this._backupContent.backupType === "full"
? "confirm_restore_full_backup_title"
: "confirm_restore_partial_backup_title"
),
text: this._localize(
this._backupContent.backupType === "full"
? "confirm_restore_full_backup_text"
: "confirm_restore_partial_backup_text"
),
confirmText: this._localize("restore"),
dismissText: this._localize("cancel"),
}))
) {
this._restoringBackup = false;
return;
}
if (!this._dialogParams?.onboarding) {
try {
await this.hass!.callApi(
"POST",
`hassio/${
atLeastVersion(this.hass!.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/restore/partial`,
backupDetails
);
this.closeDialog();
} catch (error: any) {
this._error = error.body.message;
}
} else {
this._dialogParams?.onRestoring?.();
await fetch(`/api/hassio/backups/${this._backup!.slug}/restore/partial`, {
method: "POST",
body: JSON.stringify(backupDetails),
});
this.closeDialog();
}
}
private async _fullRestoreClicked(backupDetails) {
const supervisor = this._dialogParams?.supervisor;
if (supervisor !== undefined && supervisor.info.state !== "running") {
await showAlertDialog(this, {
title: supervisor.localize("backup.could_not_restore"),
text: supervisor.localize("backup.restore_blocked_not_running", {
state: supervisor.info.state,
}),
});
return;
}
if (
!(await showConfirmationDialog(this, {
title: this._localize("confirm_restore_full_backup_title"),
text: this._localize("confirm_restore_full_backup_text"),
confirmText: this._localize("restore"),
dismissText: this._localize("cancel"),
}))
) {
return;
}
if (!this._dialogParams?.onboarding) {
this.hass!.callApi(
"POST",
`hassio/${
atLeastVersion(this.hass!.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/restore/full`,
backupDetails
).then(
() => {
this.closeDialog();
},
(error) => {
this._error = error.body.message;
}
try {
await restoreBackup(
this.hass,
this._backupContent.backupType,
this._backup!.slug,
backupDetails,
!!this.hass && atLeastVersion(this.hass.config.version, 2021, 9)
);
} else {
this._dialogParams?.onRestoring?.();
fetch(`/api/hassio/backups/${this._backup!.slug}/restore/full`, {
method: "POST",
body: JSON.stringify(backupDetails),
});
this.closeDialog();
} catch (error: any) {
this._error =
error?.body?.message || this._localize("restore_start_failed");
} finally {
this._restoringBackup = false;
}
}
@@ -361,7 +301,36 @@ class HassioBackupDialog
private get _computeName() {
return this._backup
? this._backup.name || this._backup.slug
: "Unnamed backup";
: this._localize("unnamed_backup");
}
static get styles(): CSSResultGroup {
return [
haStyle,
haStyleDialog,
css`
ha-circular-progress {
display: block;
text-align: center;
}
ha-header-bar {
--mdc-theme-on-primary: var(--primary-text-color);
--mdc-theme-primary: var(--mdc-theme-surface);
flex-shrink: 0;
display: block;
}
ha-icon-button {
color: var(--secondary-text-color);
}
.loading {
width: 100%;
display: flex;
height: 100%;
justify-content: center;
align-items: center;
}
`,
];
}
}