1
0
mirror of https://github.com/home-assistant/frontend.git synced 2025-12-24 12:49:19 +00:00
Files
frontend/src/onboarding/onboarding-restore-backup.ts
2025-04-11 13:43:45 +02:00

340 lines
9.8 KiB
TypeScript

import type { TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import "./restore-backup/onboarding-restore-backup-restore";
import "./restore-backup/onboarding-restore-backup-status";
import type { LocalizeFunc } from "../common/translations/localize";
import "./onboarding-loading";
import { removeSearchParam } from "../common/url/search-params";
import { navigate } from "../common/navigate";
import { onBoardingStyles } from "./styles";
import {
fetchBackupOnboardingInfo,
type BackupOnboardingConfig,
type BackupOnboardingInfo,
} from "../data/backup_onboarding";
import { CLOUD_AGENT, type BackupContentExtended } from "../data/backup";
import { storage } from "../common/decorators/storage";
import {
fetchHaCloudStatus,
signOutHaCloud,
waitForIntegration,
} from "../data/onboarding";
import type { CloudStatus } from "../data/cloud";
import { showToast } from "../util/toast";
const STATUS_INTERVAL_IN_MS = 5000;
@customElement("onboarding-restore-backup")
class OnboardingRestoreBackup extends LitElement {
@property({ attribute: false }) public localize!: LocalizeFunc;
@property({ type: Boolean }) public supervisor = false;
@property() public mode!: "upload" | "cloud";
@state() private _view:
| "loading"
| "upload"
| "cloud_login"
| "empty_cloud"
| "restore"
| "status" = "loading";
@state() private _backup?: BackupContentExtended;
@state() private _backupInfo?: BackupOnboardingInfo;
@state() private _error?: string;
@state() private _failed?: boolean;
@state() private _cloudStatus?: CloudStatus;
@storage({
key: "onboarding-restore-backup-backup-id",
})
private _backupId?: string;
@storage({
key: "onboarding-restore-running",
})
private _restoreRunning?: boolean;
private _loadedIntegrations = new Set<string>();
protected render(): TemplateResult {
return html`
${this._error && this._view !== "restore"
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: nothing}
${this._view === "loading"
? html`<onboarding-loading></onboarding-loading>`
: this._view === "upload"
? html`
<onboarding-restore-backup-upload
.supervisor=${this.supervisor}
.localize=${this.localize}
@backup-uploaded=${this._backupUploaded}
></onboarding-restore-backup-upload>
`
: this._view === "cloud_login"
? html`
<onboarding-restore-backup-cloud-login
.localize=${this.localize}
@ha-refresh-cloud-status=${this._showCloudBackup}
></onboarding-restore-backup-cloud-login>
`
: this._view === "empty_cloud"
? html`
<onboarding-restore-backup-no-cloud-backup
.localize=${this.localize}
@sign-out=${this._signOut}
></onboarding-restore-backup-no-cloud-backup>
`
: this._view === "restore"
? html`<onboarding-restore-backup-restore
.mode=${this.mode}
.localize=${this.localize}
.backup=${this._backup!}
.supervisor=${this.supervisor}
.error=${this._failed
? this.localize(
`ui.panel.page-onboarding.restore.${this._backupInfo?.last_action_event?.reason === "password_incorrect" ? "failed_wrong_password_description" : "failed_description"}`
)
: this._error}
@restore-started=${this._restoreStarted}
@restore-backup-back=${this._back}
@sign-out=${this._signOut}
></onboarding-restore-backup-restore>`
: nothing}
${this._view === "status" && this._backupInfo
? html`<onboarding-restore-backup-status
.localize=${this.localize}
.backupInfo=${this._backupInfo}
@restore-backup-back=${this._back}
></onboarding-restore-backup-status>`
: nothing}
`;
}
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
if (this.mode === "cloud") {
import("./restore-backup/onboarding-restore-backup-cloud-login");
import("./restore-backup/onboarding-restore-backup-no-cloud-backup");
} else {
import("./restore-backup/onboarding-restore-backup-upload");
}
this._loadBackupInfo();
}
private async _loadBackupInfo() {
let onboardingInfo: BackupOnboardingConfig;
try {
if (!this._loadedIntegrations.has("backup")) {
if ((await waitForIntegration("backup")).integration_loaded) {
this._loadedIntegrations.add("backup");
} else {
this._error = "Backup integration not loaded";
return;
}
}
onboardingInfo = await fetchBackupOnboardingInfo();
} catch (err: any) {
if (this._restoreRunning) {
if (
err.error === "Request error" ||
// core can restart but haven't loaded the backup integration yet
(err.status_code === 500 && err.body?.error === "backup_disabled")
) {
// core is down because of restore, keep trying
this._scheduleLoadBackupInfo();
return;
}
// core seems to be back up restored
if (err.status_code === 404) {
this._restoreRunning = undefined;
this._backupId = undefined;
window.location.replace("/");
return;
}
}
this._error = err?.message || "Cannot get backup info";
// if we are in an unknown state, show upload
if (this._view === "loading") {
this._view = "upload";
}
return;
}
const {
last_action_event: lastActionEvent,
state: currentState,
backups,
} = onboardingInfo;
this._backupInfo = {
state: currentState,
last_action_event: lastActionEvent,
};
if (this.mode === "cloud") {
try {
if (!this._loadedIntegrations.has("cloud")) {
if ((await waitForIntegration("cloud")).integration_loaded) {
this._loadedIntegrations.add("cloud");
} else {
this._error = "Cloud integration not loaded";
return;
}
}
this._cloudStatus = await fetchHaCloudStatus();
} catch (err: any) {
this._error = err?.message || "Cannot get Home Assistant Cloud status";
}
if (this._cloudStatus?.logged_in && !this._backupId) {
this._backup = backups.find(({ agents }) =>
Object.keys(agents).includes(CLOUD_AGENT)
);
if (!this._backup) {
this._view = "empty_cloud";
return;
}
this._backupId = this._backup?.backup_id;
}
} else if (this._backupId) {
this._backup = backups.find(
({ backup_id }) => backup_id === this._backupId
);
}
const failedRestore =
lastActionEvent?.manager_state === "restore_backup" &&
lastActionEvent?.state === "failed";
if (failedRestore) {
this._failed = true;
}
if (this._restoreRunning) {
this._view = "status";
if (failedRestore || currentState !== "restore_backup") {
this._failed = true;
this._restoreRunning = undefined;
} else {
this._scheduleLoadBackupInfo();
}
return;
}
if (this._backup) {
this._view = "restore";
return;
}
// show default view
if (this.mode === "upload") {
this._view = "upload";
} else if (this._cloudStatus?.logged_in) {
this._view = "empty_cloud";
} else {
this._view = "cloud_login";
}
}
private _showCloudBackup() {
this._view = "loading";
this._loadBackupInfo();
}
private _scheduleLoadBackupInfo() {
setTimeout(() => this._loadBackupInfo(), STATUS_INTERVAL_IN_MS);
}
private async _backupUploaded(ev: CustomEvent) {
this._backupId = ev.detail.backupId;
await this._loadBackupInfo();
}
private async _restoreStarted() {
if (this._backupInfo) {
this._backupInfo.state = "restore_backup";
}
this._view = "status";
this._restoreRunning = true;
await this._loadBackupInfo();
}
private async _signOut() {
this._view = "loading";
showToast(this, {
id: "sign-out-ha-cloud",
message: this.localize(
"ui.panel.page-onboarding.restore.ha-cloud.sign_out_progress"
),
});
this._backupId = undefined;
this._cloudStatus = undefined;
try {
await signOutHaCloud();
showToast(this, {
id: "sign-out-ha-cloud",
message: this.localize(
"ui.panel.page-onboarding.restore.ha-cloud.sign_out_success"
),
});
} catch (err) {
// eslint-disable-next-line no-console
console.error(err);
showToast(this, {
id: "sign-out-ha-cloud",
message: this.localize(
"ui.panel.page-onboarding.restore.ha-cloud.sign_out_error"
),
});
}
navigate(`${location.pathname}?${removeSearchParam("page")}`);
}
private async _back() {
this._view = "loading";
this._backup = undefined;
this._backupId = undefined;
if (this.mode === "upload") {
this._view = "upload";
} else {
navigate(`${location.pathname}?${removeSearchParam("page")}`);
}
}
static styles = [
onBoardingStyles,
css`
:host {
display: flex;
flex-direction: column;
position: relative;
}
.logout {
white-space: nowrap;
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
"onboarding-restore-backup": OnboardingRestoreBackup;
}
}