mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-14 21:06:34 +00:00
Change backup restore flow (#23354)
* Change backup restore flow * adapt and finish * Update dialog-restore-backup.ts
This commit is contained in:
parent
dc799bf691
commit
b35f9944ea
@ -1,245 +0,0 @@
|
|||||||
import { mdiClose } from "@mdi/js";
|
|
||||||
import type { CSSResultGroup } from "lit";
|
|
||||||
import { css, html, LitElement, nothing } from "lit";
|
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
|
||||||
import memoizeOne from "memoize-one";
|
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
|
||||||
import "../../../../components/ha-button";
|
|
||||||
import "../../../../components/ha-dialog-header";
|
|
||||||
import "../../../../components/ha-form/ha-form";
|
|
||||||
import type {
|
|
||||||
HaFormSchema,
|
|
||||||
SchemaUnion,
|
|
||||||
} from "../../../../components/ha-form/types";
|
|
||||||
import "../../../../components/ha-icon-button";
|
|
||||||
import "../../../../components/ha-md-dialog";
|
|
||||||
import type { HaMdDialog } from "../../../../components/ha-md-dialog";
|
|
||||||
import "../../../../components/ha-svg-icon";
|
|
||||||
import { fetchBackupConfig } from "../../../../data/backup";
|
|
||||||
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
|
||||||
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
|
||||||
import type { HomeAssistant } from "../../../../types";
|
|
||||||
import type { RestoreBackupEncryptionKeyDialogParams } from "./show-dialog-restore-backup-encryption-key";
|
|
||||||
|
|
||||||
type FormData = {
|
|
||||||
encryption_key_type: "config" | "custom";
|
|
||||||
custom_encryption_key: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const INITIAL_DATA: FormData = {
|
|
||||||
encryption_key_type: "config",
|
|
||||||
custom_encryption_key: "",
|
|
||||||
};
|
|
||||||
|
|
||||||
@customElement("ha-dialog-restore-backup-encryption-key")
|
|
||||||
class DialogRestoreBackupEncryptionKey
|
|
||||||
extends LitElement
|
|
||||||
implements HassDialog
|
|
||||||
{
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@state() private _params?: RestoreBackupEncryptionKeyDialogParams;
|
|
||||||
|
|
||||||
@state() private _formData?: FormData;
|
|
||||||
|
|
||||||
@state() private _backupEncryptionKey?: string;
|
|
||||||
|
|
||||||
@query("ha-md-dialog") private _dialog?: HaMdDialog;
|
|
||||||
|
|
||||||
public showDialog(_params: RestoreBackupEncryptionKeyDialogParams): void {
|
|
||||||
this._params = _params;
|
|
||||||
this._formData = INITIAL_DATA;
|
|
||||||
this._fetchEncryptionKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
private _dialogClosed() {
|
|
||||||
if (this._params!.cancel) {
|
|
||||||
this._params!.cancel();
|
|
||||||
}
|
|
||||||
this._formData = undefined;
|
|
||||||
this._params = undefined;
|
|
||||||
this._backupEncryptionKey = undefined;
|
|
||||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _fetchEncryptionKey() {
|
|
||||||
try {
|
|
||||||
const { config } = await fetchBackupConfig(this.hass);
|
|
||||||
this._backupEncryptionKey = config.create_backup.password || undefined;
|
|
||||||
} catch (e) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public closeDialog() {
|
|
||||||
this._dialog?.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private _schema = memoizeOne(
|
|
||||||
(hasEncryptionKey: boolean, type: "config" | "custom") =>
|
|
||||||
[
|
|
||||||
...(hasEncryptionKey
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
name: "encryption_key_type",
|
|
||||||
selector: {
|
|
||||||
select: {
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
value: "config",
|
|
||||||
label: "Use backup encryption key",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "custom",
|
|
||||||
label: "Enter encryption key",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
context: {
|
|
||||||
filter_entity: "entity",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
...(!hasEncryptionKey || type === "custom"
|
|
||||||
? ([
|
|
||||||
{
|
|
||||||
name: "custom_encryption_key",
|
|
||||||
selector: {
|
|
||||||
text: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
] as const satisfies readonly HaFormSchema[])
|
|
||||||
: []),
|
|
||||||
] as const satisfies readonly HaFormSchema[]
|
|
||||||
);
|
|
||||||
|
|
||||||
protected render() {
|
|
||||||
if (!this._params || !this._formData) {
|
|
||||||
return nothing;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dialogTitle = "Restore backup";
|
|
||||||
|
|
||||||
const hasEncryptionKey = this._backupEncryptionKey != null;
|
|
||||||
|
|
||||||
const schema = this._schema(
|
|
||||||
hasEncryptionKey,
|
|
||||||
this._formData.encryption_key_type
|
|
||||||
);
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<ha-md-dialog open @closed=${this._dialogClosed}>
|
|
||||||
<ha-dialog-header slot="headline">
|
|
||||||
<ha-icon-button
|
|
||||||
slot="navigationIcon"
|
|
||||||
.label=${this.hass.localize("ui.dialogs.generic.close")}
|
|
||||||
.path=${mdiClose}
|
|
||||||
@click=${this.closeDialog}
|
|
||||||
></ha-icon-button>
|
|
||||||
<span slot="title" .title=${dialogTitle}>${dialogTitle}</span>
|
|
||||||
</ha-dialog-header>
|
|
||||||
<div slot="content" class="content">
|
|
||||||
<p>
|
|
||||||
${hasEncryptionKey
|
|
||||||
? "The backup is encrypted. Which encryption key would you like to use to decrypt the backup?"
|
|
||||||
: "The backup is encrypted. Provide the encryption key to decrypt the backup."}
|
|
||||||
</p>
|
|
||||||
<ha-form
|
|
||||||
.schema=${schema}
|
|
||||||
.data=${this._formData}
|
|
||||||
@value-changed=${this._valueChanged}
|
|
||||||
.computeLabel=${this._computeLabelCallback}
|
|
||||||
>
|
|
||||||
</ha-form>
|
|
||||||
</div>
|
|
||||||
<div slot="actions">
|
|
||||||
<ha-button @click=${this.closeDialog}>Cancel</ha-button>
|
|
||||||
<ha-button
|
|
||||||
@click=${this._submit}
|
|
||||||
class="danger"
|
|
||||||
.disabled=${!this._getKey()}
|
|
||||||
>
|
|
||||||
Restore
|
|
||||||
</ha-button>
|
|
||||||
</div>
|
|
||||||
</ha-md-dialog>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _valueChanged(ev: CustomEvent): void {
|
|
||||||
ev.stopPropagation();
|
|
||||||
this._formData = ev.detail.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _computeLabelCallback = (
|
|
||||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
|
||||||
) => {
|
|
||||||
switch (schema.name) {
|
|
||||||
case "encryption_key_type":
|
|
||||||
return "";
|
|
||||||
case "custom_encryption_key":
|
|
||||||
return "Encryption key";
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private _getKey() {
|
|
||||||
if (!this._formData) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const hasEncryptionKey = this._backupEncryptionKey != null;
|
|
||||||
|
|
||||||
if (hasEncryptionKey) {
|
|
||||||
return this._formData.encryption_key_type === "config"
|
|
||||||
? this._backupEncryptionKey
|
|
||||||
: this._formData.custom_encryption_key;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._formData.custom_encryption_key;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _submit() {
|
|
||||||
if (!this._formData) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const key = this._getKey();
|
|
||||||
|
|
||||||
if (!key) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._params!.submit?.(key!);
|
|
||||||
this.closeDialog();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return [
|
|
||||||
haStyle,
|
|
||||||
haStyleDialog,
|
|
||||||
css`
|
|
||||||
ha-md-dialog {
|
|
||||||
max-width: 500px;
|
|
||||||
width: 100%;
|
|
||||||
--dialog-content-padding: 8px 24px;
|
|
||||||
}
|
|
||||||
.content p {
|
|
||||||
margin: 0 0 16px;
|
|
||||||
}
|
|
||||||
ha-button.danger {
|
|
||||||
--mdc-theme-primary: var(--error-color);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ha-dialog-restore-backup-encryption-key": DialogRestoreBackupEncryptionKey;
|
|
||||||
}
|
|
||||||
}
|
|
313
src/panels/config/backup/dialogs/dialog-restore-backup.ts
Normal file
313
src/panels/config/backup/dialogs/dialog-restore-backup.ts
Normal file
@ -0,0 +1,313 @@
|
|||||||
|
import { mdiClose } from "@mdi/js";
|
||||||
|
import type { CSSResultGroup } from "lit";
|
||||||
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
|
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import "../../../../components/ha-button";
|
||||||
|
import "../../../../components/ha-circular-progress";
|
||||||
|
import "../../../../components/ha-dialog-header";
|
||||||
|
import "../../../../components/ha-password-field";
|
||||||
|
|
||||||
|
import "../../../../components/ha-alert";
|
||||||
|
import "../../../../components/ha-icon-button";
|
||||||
|
import "../../../../components/ha-md-dialog";
|
||||||
|
import type { HaMdDialog } from "../../../../components/ha-md-dialog";
|
||||||
|
import "../../../../components/ha-svg-icon";
|
||||||
|
import {
|
||||||
|
fetchBackupConfig,
|
||||||
|
getPreferredAgentForDownload,
|
||||||
|
restoreBackup,
|
||||||
|
} from "../../../../data/backup";
|
||||||
|
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
||||||
|
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
||||||
|
import type { HomeAssistant } from "../../../../types";
|
||||||
|
import type { RestoreBackupDialogParams } from "./show-dialog-restore-backup";
|
||||||
|
import type { RestoreBackupStage } from "../../../../data/backup_manager";
|
||||||
|
import { subscribeBackupEvents } from "../../../../data/backup_manager";
|
||||||
|
|
||||||
|
type FormData = {
|
||||||
|
encryption_key_type: "config" | "custom";
|
||||||
|
custom_encryption_key: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const INITIAL_DATA: FormData = {
|
||||||
|
encryption_key_type: "config",
|
||||||
|
custom_encryption_key: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const STEPS = ["confirm", "encryption", "progress"] as const;
|
||||||
|
|
||||||
|
@customElement("ha-dialog-restore-backup")
|
||||||
|
class DialogRestoreBackup extends LitElement implements HassDialog {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@state() private _step?: "confirm" | "encryption" | "progress";
|
||||||
|
|
||||||
|
@state() private _params?: RestoreBackupDialogParams;
|
||||||
|
|
||||||
|
@state() private _formData?: FormData;
|
||||||
|
|
||||||
|
@state() private _backupEncryptionKey?: string;
|
||||||
|
|
||||||
|
@state() private _userPassword?: string;
|
||||||
|
|
||||||
|
@state() private _error?: string;
|
||||||
|
|
||||||
|
@state() private _stage?: RestoreBackupStage | null;
|
||||||
|
|
||||||
|
@state() private _unsub?: Promise<UnsubscribeFunc>;
|
||||||
|
|
||||||
|
@query("ha-md-dialog") private _dialog?: HaMdDialog;
|
||||||
|
|
||||||
|
public async showDialog(params: RestoreBackupDialogParams) {
|
||||||
|
this._params = params;
|
||||||
|
|
||||||
|
this._formData = INITIAL_DATA;
|
||||||
|
if (this._params.backup.protected) {
|
||||||
|
this._backupEncryptionKey = await this._fetchEncryptionKey();
|
||||||
|
if (!this._backupEncryptionKey) {
|
||||||
|
this._step = STEPS[1];
|
||||||
|
} else {
|
||||||
|
this._step = STEPS[0];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._step = STEPS[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public closeDialog() {
|
||||||
|
this._dialog?.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _dialogClosed() {
|
||||||
|
this._formData = undefined;
|
||||||
|
this._params = undefined;
|
||||||
|
this._backupEncryptionKey = undefined;
|
||||||
|
this._userPassword = undefined;
|
||||||
|
this._error = undefined;
|
||||||
|
this._stage = undefined;
|
||||||
|
this._step = undefined;
|
||||||
|
if (this._unsub) {
|
||||||
|
this._unsub.then((unsub) => unsub());
|
||||||
|
this._unsub = undefined;
|
||||||
|
}
|
||||||
|
window.removeEventListener("connection-status", this._connectionStatus);
|
||||||
|
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _fetchEncryptionKey() {
|
||||||
|
try {
|
||||||
|
const { config } = await fetchBackupConfig(this.hass);
|
||||||
|
return config.create_backup.password || undefined;
|
||||||
|
} catch (e) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(e);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (!this._step || !this._params || !this._formData) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dialogTitle = "Restore backup";
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-md-dialog open @closed=${this._dialogClosed}>
|
||||||
|
<ha-dialog-header slot="headline">
|
||||||
|
<ha-icon-button
|
||||||
|
slot="navigationIcon"
|
||||||
|
.label=${this.hass.localize("ui.dialogs.generic.close")}
|
||||||
|
.path=${mdiClose}
|
||||||
|
@click=${this.closeDialog}
|
||||||
|
></ha-icon-button>
|
||||||
|
<span slot="title" .title=${dialogTitle}>${dialogTitle}</span>
|
||||||
|
</ha-dialog-header>
|
||||||
|
<div slot="content" class="content">
|
||||||
|
${this._error
|
||||||
|
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||||
|
: this._step === "confirm"
|
||||||
|
? this._renderConfirm()
|
||||||
|
: this._step === "encryption"
|
||||||
|
? this._renderEncryption()
|
||||||
|
: this._renderProgress()}
|
||||||
|
</div>
|
||||||
|
<div slot="actions">
|
||||||
|
${this._error
|
||||||
|
? html`<ha-button @click=${this.closeDialog}>Close</ha-button>`
|
||||||
|
: this._step === "confirm" || this._step === "encryption"
|
||||||
|
? this._renderConfirmActions()
|
||||||
|
: nothing}
|
||||||
|
</div>
|
||||||
|
</ha-md-dialog>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderConfirm() {
|
||||||
|
return html`<p>
|
||||||
|
Your backup will be restored and all current data will be overwritten.
|
||||||
|
Depending on the size of the backup, this can take a while.
|
||||||
|
</p>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderEncryption() {
|
||||||
|
return html`<p>
|
||||||
|
${this._userPassword
|
||||||
|
? "The provided encryption key was incorrect, please try again."
|
||||||
|
: this._backupEncryptionKey
|
||||||
|
? "The backup is encrypted with a different key or password than that is saved on this system. Please enter the key for this backup."
|
||||||
|
: "The backup is encrypted. Provide the encryption key to decrypt the backup."}
|
||||||
|
</p>
|
||||||
|
<ha-password-field
|
||||||
|
@change=${this._passwordChanged}
|
||||||
|
.value=${this._userPassword || ""}
|
||||||
|
></ha-password-field>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderConfirmActions() {
|
||||||
|
return html`<ha-button @click=${this.closeDialog}>Cancel</ha-button>
|
||||||
|
<ha-button @click=${this._restoreBackup} class="destructive"
|
||||||
|
>Restore</ha-button
|
||||||
|
>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderProgress() {
|
||||||
|
return html`<div class="centered">
|
||||||
|
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||||
|
<p>
|
||||||
|
${this.hass.connected
|
||||||
|
? this._restoreState()
|
||||||
|
: "Restarting Home Asssistant"}
|
||||||
|
</p>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _passwordChanged(ev): void {
|
||||||
|
this._userPassword = ev.target.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _restoreBackup() {
|
||||||
|
try {
|
||||||
|
this._step = "progress";
|
||||||
|
window.addEventListener("connection-status", this._connectionStatus);
|
||||||
|
await this._doRestoreBackup(
|
||||||
|
this._userPassword || this._backupEncryptionKey
|
||||||
|
);
|
||||||
|
this._subscribeBackupEvents();
|
||||||
|
} catch (e: any) {
|
||||||
|
window.removeEventListener("connection-status", this._connectionStatus);
|
||||||
|
if (e.code === "password_incorrect") {
|
||||||
|
this._step = "encryption";
|
||||||
|
} else {
|
||||||
|
this._error = e.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _connectionStatus = (ev) => {
|
||||||
|
if (ev.detail === "connected") {
|
||||||
|
this.closeDialog();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private _subscribeBackupEvents() {
|
||||||
|
this._unsub = subscribeBackupEvents(this.hass!, (event) => {
|
||||||
|
if (event.manager_state !== "restore_backup") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.state === "completed") {
|
||||||
|
this.closeDialog();
|
||||||
|
}
|
||||||
|
if (event.state === "failed") {
|
||||||
|
this._error = "Backup restore failed";
|
||||||
|
}
|
||||||
|
if (event.state === "in_progress") {
|
||||||
|
this._stage = event.stage;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _restoreState() {
|
||||||
|
switch (this._stage) {
|
||||||
|
case "addon_repositories":
|
||||||
|
return "Restoring add-on repositories";
|
||||||
|
case "addons":
|
||||||
|
return "Restoring add-ons";
|
||||||
|
case "await_addon_restarts":
|
||||||
|
return "Waiting for add-ons to restart";
|
||||||
|
case "await_home_assistant_restart":
|
||||||
|
return "Waiting for Home Assistant to restart";
|
||||||
|
case "check_home_assistant":
|
||||||
|
return "Checking Home Assistant configuration";
|
||||||
|
case "docker_config":
|
||||||
|
return "Restoring Docker configuration";
|
||||||
|
case "download_from_agent":
|
||||||
|
return "Downloading backup";
|
||||||
|
case "folders":
|
||||||
|
return "Restoring folders";
|
||||||
|
case "home_assistant":
|
||||||
|
return "Restoring Home Assistant";
|
||||||
|
case "remove_delta_addons":
|
||||||
|
return "Removing add-ons that are no longer in the backup";
|
||||||
|
}
|
||||||
|
return "Restoring backup";
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _doRestoreBackup(password?: string) {
|
||||||
|
if (!this._params) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const preferedAgent = getPreferredAgentForDownload(
|
||||||
|
this._params.backup.agent_ids!
|
||||||
|
);
|
||||||
|
|
||||||
|
const { addons, database_included, homeassistant_included, folders } =
|
||||||
|
this._params.selectedData;
|
||||||
|
|
||||||
|
await restoreBackup(this.hass, {
|
||||||
|
backup_id: this._params.backup.backup_id,
|
||||||
|
agent_id: preferedAgent,
|
||||||
|
password,
|
||||||
|
restore_addons: addons.map((addon) => addon.slug),
|
||||||
|
restore_database: database_included,
|
||||||
|
restore_folders: folders,
|
||||||
|
restore_homeassistant: homeassistant_included,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return [
|
||||||
|
haStyle,
|
||||||
|
haStyleDialog,
|
||||||
|
css`
|
||||||
|
ha-md-dialog {
|
||||||
|
max-width: 500px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.content p {
|
||||||
|
margin: 0 0 16px;
|
||||||
|
}
|
||||||
|
.destructive {
|
||||||
|
--mdc-theme-primary: var(--error-color);
|
||||||
|
}
|
||||||
|
.centered {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
ha-circular-progress {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-dialog-restore-backup": DialogRestoreBackup;
|
||||||
|
}
|
||||||
|
}
|
@ -1,37 +0,0 @@
|
|||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
|
||||||
|
|
||||||
export interface RestoreBackupEncryptionKeyDialogParams {
|
|
||||||
submit?: (value: string) => void;
|
|
||||||
cancel?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const loadRestoreBackupEncryptionKeyDialog = () =>
|
|
||||||
import("./dialog-restore-backup-encryption-key");
|
|
||||||
|
|
||||||
export const showRestoreBackupEncryptionKeyDialog = (
|
|
||||||
element: HTMLElement,
|
|
||||||
params: RestoreBackupEncryptionKeyDialogParams
|
|
||||||
) =>
|
|
||||||
new Promise<string | null>((resolve) => {
|
|
||||||
const origCancel = params.cancel;
|
|
||||||
const origSubmit = params.submit;
|
|
||||||
fireEvent(element, "show-dialog", {
|
|
||||||
dialogTag: "ha-dialog-restore-backup-encryption-key",
|
|
||||||
dialogImport: loadRestoreBackupEncryptionKeyDialog,
|
|
||||||
dialogParams: {
|
|
||||||
...params,
|
|
||||||
cancel: () => {
|
|
||||||
resolve(null);
|
|
||||||
if (origCancel) {
|
|
||||||
origCancel();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
submit: (response) => {
|
|
||||||
resolve(response);
|
|
||||||
if (origSubmit) {
|
|
||||||
origSubmit(response);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
@ -0,0 +1,23 @@
|
|||||||
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import type {
|
||||||
|
BackupContentExtended,
|
||||||
|
BackupData,
|
||||||
|
} from "../../../../data/backup";
|
||||||
|
|
||||||
|
export interface RestoreBackupDialogParams {
|
||||||
|
backup: BackupContentExtended;
|
||||||
|
selectedData: BackupData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const loadRestoreBackupDialog = () => import("./dialog-restore-backup");
|
||||||
|
|
||||||
|
export const showRestoreBackupDialog = (
|
||||||
|
element: HTMLElement,
|
||||||
|
params: RestoreBackupDialogParams
|
||||||
|
) => {
|
||||||
|
fireEvent(element, "show-dialog", {
|
||||||
|
dialogTag: "ha-dialog-restore-backup",
|
||||||
|
dialogImport: loadRestoreBackupDialog,
|
||||||
|
dialogParams: params,
|
||||||
|
});
|
||||||
|
};
|
@ -15,7 +15,7 @@ import "../../../components/ha-list-item";
|
|||||||
import "../../../components/ha-md-list";
|
import "../../../components/ha-md-list";
|
||||||
import "../../../components/ha-md-list-item";
|
import "../../../components/ha-md-list-item";
|
||||||
import { getSignedPath } from "../../../data/auth";
|
import { getSignedPath } from "../../../data/auth";
|
||||||
import type { BackupContentExtended } from "../../../data/backup";
|
import type { BackupContentExtended, BackupData } from "../../../data/backup";
|
||||||
import {
|
import {
|
||||||
compareAgents,
|
compareAgents,
|
||||||
computeBackupAgentName,
|
computeBackupAgentName,
|
||||||
@ -24,7 +24,6 @@ import {
|
|||||||
getBackupDownloadUrl,
|
getBackupDownloadUrl,
|
||||||
getPreferredAgentForDownload,
|
getPreferredAgentForDownload,
|
||||||
isLocalAgent,
|
isLocalAgent,
|
||||||
restoreBackup,
|
|
||||||
} from "../../../data/backup";
|
} from "../../../data/backup";
|
||||||
import type { HassioAddonInfo } from "../../../data/hassio/addon";
|
import type { HassioAddonInfo } from "../../../data/hassio/addon";
|
||||||
import "../../../layouts/hass-subpage";
|
import "../../../layouts/hass-subpage";
|
||||||
@ -34,7 +33,7 @@ import { bytesToString } from "../../../util/bytes-to-string";
|
|||||||
import { fileDownload } from "../../../util/file_download";
|
import { fileDownload } from "../../../util/file_download";
|
||||||
import { showConfirmationDialog } from "../../lovelace/custom-card-helpers";
|
import { showConfirmationDialog } from "../../lovelace/custom-card-helpers";
|
||||||
import "./components/ha-backup-data-picker";
|
import "./components/ha-backup-data-picker";
|
||||||
import { showRestoreBackupEncryptionKeyDialog } from "./dialogs/show-dialog-restore-backup-encryption-key";
|
import { showRestoreBackupDialog } from "./dialogs/show-dialog-restore-backup";
|
||||||
|
|
||||||
type Agent = {
|
type Agent = {
|
||||||
id: string;
|
id: string;
|
||||||
@ -66,7 +65,7 @@ class HaConfigBackupDetails extends LitElement {
|
|||||||
|
|
||||||
@state() private _error?: string;
|
@state() private _error?: string;
|
||||||
|
|
||||||
@state() private _selectedBackup?: BackupContentExtended;
|
@state() private _selectedData?: BackupData;
|
||||||
|
|
||||||
@state() private _addonsInfo?: HassioAddonInfo[];
|
@state() private _addonsInfo?: HassioAddonInfo[];
|
||||||
|
|
||||||
@ -144,7 +143,7 @@ class HaConfigBackupDetails extends LitElement {
|
|||||||
<ha-backup-data-picker
|
<ha-backup-data-picker
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.data=${this._backup}
|
.data=${this._backup}
|
||||||
.value=${this._selectedBackup}
|
.value=${this._selectedData}
|
||||||
@value-changed=${this._selectedBackupChanged}
|
@value-changed=${this._selectedBackupChanged}
|
||||||
.addonsInfo=${this._addonsInfo}
|
.addonsInfo=${this._addonsInfo}
|
||||||
>
|
>
|
||||||
@ -251,57 +250,28 @@ class HaConfigBackupDetails extends LitElement {
|
|||||||
|
|
||||||
private _selectedBackupChanged(ev: CustomEvent) {
|
private _selectedBackupChanged(ev: CustomEvent) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this._selectedBackup = ev.detail.value;
|
this._selectedData = ev.detail.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _isRestoreDisabled() {
|
private _isRestoreDisabled() {
|
||||||
return (
|
return (
|
||||||
!this._selectedBackup ||
|
!this._selectedData ||
|
||||||
!(
|
!(
|
||||||
this._selectedBackup?.database_included ||
|
this._selectedData?.database_included ||
|
||||||
this._selectedBackup?.homeassistant_included ||
|
this._selectedData?.homeassistant_included ||
|
||||||
this._selectedBackup.addons.length ||
|
this._selectedData.addons.length ||
|
||||||
this._selectedBackup.folders.length
|
this._selectedData.folders.length
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _restore() {
|
private _restore() {
|
||||||
let password: string | undefined;
|
if (!this._backup || !this._selectedData) {
|
||||||
if (this._backup?.protected) {
|
|
||||||
const response = await showRestoreBackupEncryptionKeyDialog(this, {});
|
|
||||||
if (!response) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
password = response;
|
showRestoreBackupDialog(this, {
|
||||||
} else {
|
backup: this._backup,
|
||||||
const response = await showConfirmationDialog(this, {
|
selectedData: this._selectedData,
|
||||||
title: "Restore backup",
|
|
||||||
text: "The backup will be restored to your instance.",
|
|
||||||
confirmText: "Restore",
|
|
||||||
dismissText: "Cancel",
|
|
||||||
destructive: true,
|
|
||||||
});
|
|
||||||
if (!response) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const preferedAgent = getPreferredAgentForDownload(
|
|
||||||
this._backup!.agent_ids!
|
|
||||||
);
|
|
||||||
|
|
||||||
const { addons, database_included, homeassistant_included, folders } =
|
|
||||||
this._selectedBackup!;
|
|
||||||
|
|
||||||
await restoreBackup(this.hass, {
|
|
||||||
backup_id: this._backup!.backup_id,
|
|
||||||
agent_id: preferedAgent,
|
|
||||||
password: password,
|
|
||||||
restore_addons: addons.map((addon) => addon.slug),
|
|
||||||
restore_database: database_included,
|
|
||||||
restore_folders: folders,
|
|
||||||
restore_homeassistant: homeassistant_included,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user