mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-27 03:06:41 +00:00
Add HA Cloud login to onboarding (#24485)
* Add ha cloud login to onboarding * Add view for no cloud backup available * Add logout and forgot pw * Improve styling * Fix bug to open cloud backup after login * Remove callback from catch in transform methods * Remove unused variable * Fix lint * Add new onboarding restore design * Fix lint * Change back button style * Update header styles * Style onboarding left aligned * Remove unused imports * Fix imports * Fix multi factor cloud auth * Fix prettier * Edit onboarding translations * Revert gulp change * Improve cloud login component * Fix no-cloud-backup naming * fix types * Use cloud login function directly * Fix eslint * Hide restore picker when there is nothing to select * Fix eslint
This commit is contained in:
parent
858b8b90d8
commit
4076e5655a
@ -14,6 +14,8 @@ export class HaProgressButton extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public raised = false;
|
||||
|
||||
@property({ type: Boolean }) public unelevated = false;
|
||||
|
||||
@state() private _result?: "success" | "error";
|
||||
|
||||
public render(): TemplateResult {
|
||||
@ -21,6 +23,7 @@ export class HaProgressButton extends LitElement {
|
||||
return html`
|
||||
<mwc-button
|
||||
?raised=${this.raised}
|
||||
.unelevated=${this.unelevated}
|
||||
.disabled=${this.disabled || this.progress}
|
||||
class=${this._result || ""}
|
||||
>
|
||||
@ -78,6 +81,7 @@ export class HaProgressButton extends LitElement {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
mwc-button[unelevated].success,
|
||||
mwc-button[raised].success {
|
||||
--mdc-theme-primary: var(--success-color);
|
||||
--mdc-theme-on-primary: white;
|
||||
@ -91,6 +95,7 @@ export class HaProgressButton extends LitElement {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
mwc-button[unelevated].error,
|
||||
mwc-button[raised].error {
|
||||
--mdc-theme-primary: var(--error-color);
|
||||
--mdc-theme-on-primary: white;
|
||||
|
50
src/components/ha-divider.ts
Normal file
50
src/components/ha-divider.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
|
||||
@customElement("ha-divider")
|
||||
export class HaMdDivider extends LitElement {
|
||||
@property() public label?: string;
|
||||
|
||||
public render() {
|
||||
return html`
|
||||
<div
|
||||
role=${ifDefined(this.label ? "separator" : undefined)}
|
||||
aria-label=${ifDefined(this.label)}
|
||||
>
|
||||
<span class="line"></span>
|
||||
${this.label
|
||||
? html`
|
||||
<span class="label">${this.label}</span>
|
||||
<span class="line"></span>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
width: var(--ha-divider-width, 100%);
|
||||
}
|
||||
div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.label {
|
||||
padding: var(--ha-divider-label-padding, 0 16px);
|
||||
}
|
||||
.line {
|
||||
flex: 1;
|
||||
background-color: var(--divider-color);
|
||||
height: var(--ha-divider-line-height, 1px);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-divider": HaMdDivider;
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { handleFetchPromise } from "../util/hass-call-api";
|
||||
import type { CloudStatus } from "./cloud";
|
||||
|
||||
export interface InstallationType {
|
||||
installation_type:
|
||||
@ -36,6 +37,18 @@ export interface OnboardingStep {
|
||||
done: boolean;
|
||||
}
|
||||
|
||||
interface CloudLoginBase {
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface CloudLoginPassword extends CloudLoginBase {
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface CloudLoginMFA extends CloudLoginBase {
|
||||
code: string;
|
||||
}
|
||||
|
||||
export const fetchOnboardingOverview = () =>
|
||||
fetch(`${__HASS_URL__}/api/onboarding`, { credentials: "same-origin" });
|
||||
|
||||
@ -91,3 +104,27 @@ export const fetchInstallationType = async (): Promise<InstallationType> => {
|
||||
|
||||
return response.json();
|
||||
};
|
||||
|
||||
export const loginHaCloud = async (
|
||||
params: CloudLoginPassword | CloudLoginMFA
|
||||
) =>
|
||||
handleFetchPromise(
|
||||
fetch("/api/onboarding/cloud/login", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(params),
|
||||
})
|
||||
);
|
||||
|
||||
export const fetchHaCloudStatus = async (): Promise<CloudStatus> =>
|
||||
handleFetchPromise(fetch("/api/onboarding/cloud/status"));
|
||||
|
||||
export const signOutHaCloud = async () =>
|
||||
handleFetchPromise(fetch("/api/onboarding/cloud/logout", { method: "POST" }));
|
||||
|
||||
export const forgotPasswordHaCloud = async (email: string) =>
|
||||
handleFetchPromise(
|
||||
fetch("/api/onboarding/cloud/forgot_password", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ email }),
|
||||
})
|
||||
);
|
||||
|
@ -21,6 +21,7 @@ export interface ConfirmationDialogParams extends BaseDialogBoxParams {
|
||||
|
||||
export interface PromptDialogParams extends BaseDialogBoxParams {
|
||||
inputLabel?: string;
|
||||
dismissText?: string;
|
||||
inputType?: string;
|
||||
defaultValue?: string;
|
||||
placeholder?: string;
|
||||
|
@ -29,8 +29,9 @@
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
justify-content: flex-start;
|
||||
margin-bottom: 32px;
|
||||
margin-left: 32px;
|
||||
}
|
||||
|
||||
.header img {
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { property, query, state } from "lit/decorators";
|
||||
import { computeRTL } from "../common/util/compute_rtl";
|
||||
import "../components/ha-button";
|
||||
import "../components/ha-toast";
|
||||
import "../components/ha-icon-button";
|
||||
@ -63,7 +62,6 @@ class NotificationManager extends LitElement {
|
||||
return html`
|
||||
<ha-toast
|
||||
leading
|
||||
dir=${computeRTL(this.hass) ? "rtl" : "ltr"}
|
||||
.labelText=${this._parameters.message}
|
||||
.timeoutMs=${this._parameters.duration!}
|
||||
@MDCSnackbar:closed=${this._toastClosed}
|
||||
|
@ -41,7 +41,6 @@ import "./onboarding-analytics";
|
||||
import "./onboarding-create-user";
|
||||
import "./onboarding-loading";
|
||||
import "./onboarding-welcome";
|
||||
import "./onboarding-restore-backup";
|
||||
import "./onboarding-welcome-links";
|
||||
import { makeDialogManager } from "../dialogs/make-dialog-manager";
|
||||
import { navigate } from "../common/navigate";
|
||||
@ -50,7 +49,7 @@ import { mainWindow } from "../common/dom/get_main_window";
|
||||
type OnboardingEvent =
|
||||
| {
|
||||
type: "init";
|
||||
result: { restore: boolean };
|
||||
result?: { restore: "upload" | "cloud" };
|
||||
}
|
||||
| {
|
||||
type: "user";
|
||||
@ -98,7 +97,7 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
|
||||
@state() private _init = false;
|
||||
|
||||
@state() private _restoring = false;
|
||||
@state() private _restoring?: "upload" | "cloud";
|
||||
|
||||
@state() private _supervisor?: boolean;
|
||||
|
||||
@ -160,7 +159,7 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
return html`<onboarding-restore-backup
|
||||
.localize=${this.localize}
|
||||
.supervisor=${this._supervisor ?? false}
|
||||
.language=${this.language}
|
||||
.mode=${this._restoring}
|
||||
>
|
||||
</onboarding-restore-backup>`;
|
||||
}
|
||||
@ -174,7 +173,7 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
const step = this._curStep()!;
|
||||
|
||||
if (this._loading || !step) {
|
||||
return html`<onboarding-loading></onboarding-loading> `;
|
||||
return html`<onboarding-loading></onboarding-loading>`;
|
||||
}
|
||||
if (step.step === "user") {
|
||||
return html`<onboarding-create-user
|
||||
@ -215,6 +214,7 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
this._fetchOnboardingSteps();
|
||||
import("./onboarding-integrations");
|
||||
import("./onboarding-core-config");
|
||||
import("./onboarding-restore-backup");
|
||||
registerServiceWorker(this, false);
|
||||
this.addEventListener("onboarding-step", (ev) => this._handleStepDone(ev));
|
||||
this.addEventListener("onboarding-progress", (ev) =>
|
||||
@ -230,7 +230,12 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
if (changedProps.has("_page")) {
|
||||
this._restoring = this._page === "restore_backup";
|
||||
this._restoring =
|
||||
this._page === "restore_backup"
|
||||
? "upload"
|
||||
: this._page === "restore_backup_cloud"
|
||||
? "cloud"
|
||||
: undefined;
|
||||
if (this._page === null && this._steps && !this._steps[0].done) {
|
||||
this._init = true;
|
||||
}
|
||||
@ -345,12 +350,12 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
|
||||
if (stepResult.type === "init") {
|
||||
this._init = false;
|
||||
this._restoring = stepResult.result.restore;
|
||||
this._restoring = stepResult.result?.restore;
|
||||
if (!this._restoring) {
|
||||
this._progress = 0.25;
|
||||
} else {
|
||||
navigate(
|
||||
`${location.pathname}?${addSearchParam({ page: "restore_backup" })}`
|
||||
`${location.pathname}?${addSearchParam({ page: `restore_backup${this._restoring === "cloud" ? "_cloud" : ""}` })}`
|
||||
);
|
||||
}
|
||||
} else if (stepResult.type === "user") {
|
||||
@ -489,6 +494,9 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.card-content {
|
||||
padding: 32px;
|
||||
}
|
||||
mwc-linear-progress {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
@ -1,7 +1,6 @@
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../components/ha-svg-icon";
|
||||
import { brandsUrl } from "../util/brands-url";
|
||||
|
||||
@customElement("integration-badge")
|
||||
|
@ -1,15 +1,9 @@
|
||||
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-upload";
|
||||
import "./restore-backup/onboarding-restore-backup-details";
|
||||
import "./restore-backup/onboarding-restore-backup-restore";
|
||||
import "./restore-backup/onboarding-restore-backup-status";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
import "../components/ha-card";
|
||||
import "../components/ha-icon-button-arrow-prev";
|
||||
import "../components/ha-spinner";
|
||||
import "../components/ha-alert";
|
||||
import "./onboarding-loading";
|
||||
import { removeSearchParam } from "../common/url/search-params";
|
||||
import { navigate } from "../common/navigate";
|
||||
@ -19,9 +13,11 @@ import {
|
||||
type BackupOnboardingConfig,
|
||||
type BackupOnboardingInfo,
|
||||
} from "../data/backup_onboarding";
|
||||
import type { BackupContentExtended, BackupData } from "../data/backup";
|
||||
import { showConfirmationDialog } from "../dialogs/generic/show-dialog-box";
|
||||
import { CLOUD_AGENT, type BackupContentExtended } from "../data/backup";
|
||||
import { storage } from "../common/decorators/storage";
|
||||
import { fetchHaCloudStatus, signOutHaCloud } from "../data/onboarding";
|
||||
import type { CloudStatus } from "../data/cloud";
|
||||
import { showToast } from "../util/toast";
|
||||
|
||||
const STATUS_INTERVAL_IN_MS = 5000;
|
||||
|
||||
@ -29,27 +25,28 @@ const STATUS_INTERVAL_IN_MS = 5000;
|
||||
class OnboardingRestoreBackup extends LitElement {
|
||||
@property({ attribute: false }) public localize!: LocalizeFunc;
|
||||
|
||||
@property() public language!: string;
|
||||
|
||||
@property({ type: Boolean }) public supervisor = false;
|
||||
|
||||
@property() public mode!: "upload" | "cloud";
|
||||
|
||||
@state() private _view:
|
||||
| "loading"
|
||||
| "upload"
|
||||
| "select_data"
|
||||
| "confirm_restore"
|
||||
| "cloud_login"
|
||||
| "empty_cloud"
|
||||
| "restore"
|
||||
| "status" = "loading";
|
||||
|
||||
@state() private _backup?: BackupContentExtended;
|
||||
|
||||
@state() private _backupInfo?: BackupOnboardingInfo;
|
||||
|
||||
@state() private _selectedData?: BackupData;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@state() private _failed?: boolean;
|
||||
|
||||
@state() private _cloudStatus?: CloudStatus;
|
||||
|
||||
@storage({
|
||||
key: "onboarding-restore-backup-backup-id",
|
||||
})
|
||||
@ -62,37 +59,8 @@ class OnboardingRestoreBackup extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
${
|
||||
this._view !== "status" || this._failed
|
||||
? html`<ha-icon-button-arrow-prev
|
||||
.label=${this.localize("ui.panel.page-onboarding.restore.back")}
|
||||
@click=${this._back}
|
||||
></ha-icon-button-arrow-prev>`
|
||||
: nothing
|
||||
}
|
||||
</ha-icon-button>
|
||||
<h1>${this.localize("ui.panel.page-onboarding.restore.header")}</h1>
|
||||
${
|
||||
this._error || (this._failed && this._view !== "status")
|
||||
? html`<ha-alert
|
||||
alert-type="error"
|
||||
.title=${this._failed && this._view !== "status"
|
||||
? this.localize("ui.panel.page-onboarding.restore.failed")
|
||||
: ""}
|
||||
>
|
||||
${this._failed && this._view !== "status"
|
||||
? this.localize(
|
||||
`ui.panel.page-onboarding.restore.${this._backupInfo?.last_non_idle_event?.reason === "password_incorrect" ? "failed_wrong_password_description" : "failed_description"}`
|
||||
)
|
||||
: this._error}
|
||||
</ha-alert>`
|
||||
: nothing
|
||||
}
|
||||
${
|
||||
this._view === "loading"
|
||||
? html`<div class="loading">
|
||||
<ha-spinner></ha-spinner>
|
||||
</div>`
|
||||
${this._view === "loading"
|
||||
? html`<onboarding-loading></onboarding-loading>`
|
||||
: this._view === "upload"
|
||||
? html`
|
||||
<onboarding-restore-backup-upload
|
||||
@ -101,51 +69,56 @@ class OnboardingRestoreBackup extends LitElement {
|
||||
@backup-uploaded=${this._backupUploaded}
|
||||
></onboarding-restore-backup-upload>
|
||||
`
|
||||
: this._view === "select_data"
|
||||
? html`<onboarding-restore-backup-details
|
||||
: this._view === "cloud_login"
|
||||
? html`
|
||||
<onboarding-restore-backup-cloud-login
|
||||
.localize=${this.localize}
|
||||
.backup=${this._backup!}
|
||||
@backup-restore=${this._restore}
|
||||
></onboarding-restore-backup-details>`
|
||||
: this._view === "confirm_restore"
|
||||
@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}
|
||||
.selectedData=${this._selectedData!}
|
||||
.error=${this._failed
|
||||
? this.localize(
|
||||
`ui.panel.page-onboarding.restore.${this._backupInfo?.last_non_idle_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
|
||||
: nothing}
|
||||
${this._view === "status" && this._backupInfo
|
||||
? html`<onboarding-restore-backup-status
|
||||
.localize=${this.localize}
|
||||
.backupInfo=${this._backupInfo}
|
||||
@show-backup-upload=${this._reupload}
|
||||
@restore-backup-back=${this._back}
|
||||
></onboarding-restore-backup-status>`
|
||||
: nothing
|
||||
}
|
||||
${
|
||||
["select_data", "confirm_restore"].includes(this._view) && this._backup
|
||||
? html`<div class="backup-summary-wrapper">
|
||||
<ha-backup-details-summary
|
||||
translation-key-panel="page-onboarding.restore"
|
||||
show-upload-another
|
||||
.backup=${this._backup}
|
||||
.localize=${this.localize}
|
||||
@show-backup-upload=${this._reupload}
|
||||
.isHassio=${this.supervisor}
|
||||
></ha-backup-details-summary>
|
||||
</div>`
|
||||
: nothing
|
||||
}
|
||||
: 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();
|
||||
}
|
||||
|
||||
@ -194,7 +167,25 @@ class OnboardingRestoreBackup extends LitElement {
|
||||
last_non_idle_event: lastNonIdleEvent,
|
||||
};
|
||||
|
||||
if (this._backupId) {
|
||||
if (this.mode === "cloud") {
|
||||
try {
|
||||
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
|
||||
);
|
||||
@ -219,31 +210,24 @@ class OnboardingRestoreBackup extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
this._backup &&
|
||||
// after backup was uploaded
|
||||
(lastNonIdleEvent?.manager_state === "receive_backup" ||
|
||||
// when restore was confirmed but failed to start (for example, encryption key was wrong)
|
||||
failedRestore)
|
||||
) {
|
||||
if (!this.supervisor && this._backup.homeassistant_included) {
|
||||
this._selectedData = {
|
||||
homeassistant_included: true,
|
||||
folders: [],
|
||||
addons: [],
|
||||
homeassistant_version: this._backup.homeassistant_version,
|
||||
database_included: this._backup.database_included,
|
||||
};
|
||||
// skip select data when supervisor is not available and backup includes HA
|
||||
this._view = "confirm_restore";
|
||||
} else {
|
||||
this._view = "select_data";
|
||||
}
|
||||
if (this._backup) {
|
||||
this._view = "restore";
|
||||
return;
|
||||
}
|
||||
|
||||
// show upload as default
|
||||
// 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() {
|
||||
@ -264,45 +248,48 @@ class OnboardingRestoreBackup extends LitElement {
|
||||
await this._loadBackupInfo();
|
||||
}
|
||||
|
||||
private async _back() {
|
||||
if (this._view === "upload" || (this._view === "status" && this._failed)) {
|
||||
navigate(`${location.pathname}?${removeSearchParam("page")}`);
|
||||
} else {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.localize(
|
||||
"ui.panel.page-onboarding.restore.cancel_restore.title"
|
||||
),
|
||||
text: this.localize(
|
||||
"ui.panel.page-onboarding.restore.cancel_restore.text"
|
||||
),
|
||||
confirmText: this.localize(
|
||||
"ui.panel.page-onboarding.restore.cancel_restore.yes"
|
||||
),
|
||||
dismissText: this.localize(
|
||||
"ui.panel.page-onboarding.restore.cancel_restore.no"
|
||||
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"
|
||||
),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
navigate(`${location.pathname}?${removeSearchParam("page")}`);
|
||||
}
|
||||
}
|
||||
|
||||
private _restore(ev: CustomEvent) {
|
||||
if (!this._backup || !ev.detail.selectedData) {
|
||||
return;
|
||||
}
|
||||
this._selectedData = ev.detail.selectedData;
|
||||
|
||||
this._view = "confirm_restore";
|
||||
}
|
||||
|
||||
private _reupload() {
|
||||
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 = [
|
||||
@ -313,21 +300,8 @@ class OnboardingRestoreBackup extends LitElement {
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
ha-icon-button-arrow-prev {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
}
|
||||
ha-card {
|
||||
width: 100%;
|
||||
}
|
||||
.loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 32px;
|
||||
}
|
||||
.backup-summary-wrapper {
|
||||
margin-top: 24px;
|
||||
padding: 0 20px;
|
||||
.logout {
|
||||
white-space: nowrap;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@ -6,6 +6,10 @@ import type { HomeAssistant } from "../types";
|
||||
import { onBoardingStyles } from "./styles";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import "../components/ha-button";
|
||||
import "../components/ha-divider";
|
||||
import "../components/ha-md-list";
|
||||
import "../components/ha-md-list-item";
|
||||
import "../components/ha-icon-button-next";
|
||||
|
||||
@customElement("onboarding-welcome")
|
||||
class OnboardingWelcome extends LitElement {
|
||||
@ -22,23 +26,52 @@ class OnboardingWelcome extends LitElement {
|
||||
${this.localize("ui.panel.page-onboarding.welcome.start")}
|
||||
</ha-button>
|
||||
|
||||
<ha-button @click=${this._restoreBackup}>
|
||||
${this.localize("ui.panel.page-onboarding.welcome.restore_backup")}
|
||||
</ha-button>
|
||||
<ha-divider
|
||||
.label=${this.localize("ui.panel.page-onboarding.welcome.or_restore")}
|
||||
></ha-divider>
|
||||
|
||||
<ha-md-list>
|
||||
<ha-md-list-item type="button" @click=${this._restoreBackupUpload}>
|
||||
<div slot="headline">
|
||||
${this.localize("ui.panel.page-onboarding.restore.upload_backup")}
|
||||
</div>
|
||||
<div slot="supporting-text">
|
||||
${this.localize(
|
||||
"ui.panel.page-onboarding.restore.options.upload_description"
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-button-next slot="end"></ha-icon-button-next>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item type="button" @click=${this._restoreBackupCloud}>
|
||||
<div slot="headline">Home Assistant Cloud</div>
|
||||
<div slot="supporting-text">
|
||||
${this.localize(
|
||||
"ui.panel.page-onboarding.restore.ha-cloud.description"
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-button-next slot="end"></ha-icon-button-next>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
`;
|
||||
}
|
||||
|
||||
private _start(): void {
|
||||
fireEvent(this, "onboarding-step", {
|
||||
type: "init",
|
||||
result: { restore: false },
|
||||
});
|
||||
}
|
||||
|
||||
private _restoreBackup(): void {
|
||||
private _restoreBackupUpload(): void {
|
||||
fireEvent(this, "onboarding-step", {
|
||||
type: "init",
|
||||
result: { restore: true },
|
||||
result: { restore: "upload" },
|
||||
});
|
||||
}
|
||||
|
||||
private _restoreBackupCloud(): void {
|
||||
fireEvent(this, "onboarding-step", {
|
||||
type: "init",
|
||||
result: { restore: "cloud" },
|
||||
});
|
||||
}
|
||||
|
||||
@ -49,13 +82,31 @@ class OnboardingWelcome extends LitElement {
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
h1 {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
.start {
|
||||
--button-height: 48px;
|
||||
--mdc-typography-button-font-size: 1rem;
|
||||
--mdc-button-horizontal-padding: 24px;
|
||||
margin: 16px 0;
|
||||
margin: 32px 0;
|
||||
}
|
||||
ha-divider {
|
||||
--ha-divider-width: calc(100% + 64px);
|
||||
margin-left: -32px;
|
||||
margin-right: -32px;
|
||||
}
|
||||
ha-md-list {
|
||||
width: calc(100% + 32px);
|
||||
margin-left: -16px;
|
||||
margin-right: -16px;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@ -0,0 +1,148 @@
|
||||
import { css, html, LitElement, nothing, type CSSResultGroup } from "lit";
|
||||
import { customElement, property, state, query } from "lit/decorators";
|
||||
import "../../panels/config/cloud/login/cloud-login";
|
||||
import "../../components/ha-icon-button-arrow-prev";
|
||||
import "../../components/ha-spinner";
|
||||
import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
import type { BackupContentExtended } from "../../data/backup";
|
||||
import { navigate } from "../../common/navigate";
|
||||
import { removeSearchParam } from "../../common/url/search-params";
|
||||
import { onBoardingStyles } from "../styles";
|
||||
import type { CloudLogin } from "../../panels/config/cloud/login/cloud-login";
|
||||
import type { CloudForgotPasswordCard } from "../../panels/config/cloud/forgot-password/cloud-forgot-password-card";
|
||||
|
||||
@customElement("onboarding-restore-backup-cloud-login")
|
||||
class OnboardingRestoreBackupCloudLogin extends LitElement {
|
||||
@property({ attribute: false }) public localize!: LocalizeFunc;
|
||||
|
||||
@property({ attribute: false }) public backup!: BackupContentExtended;
|
||||
|
||||
@state() private _email?: string;
|
||||
|
||||
@state() private _view: "login" | "forgot-password" | "loading" = "login";
|
||||
|
||||
@state() private _showResetPasswordDone = false;
|
||||
|
||||
@query("cloud-login") private _cloudLoginElement?: CloudLogin;
|
||||
|
||||
@query("cloud-forgot-password-card")
|
||||
private _forgotPasswordElement?: CloudForgotPasswordCard;
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<ha-icon-button-arrow-prev
|
||||
.label=${this.localize("ui.panel.page-onboarding.restore.back")}
|
||||
@click=${this._back}
|
||||
></ha-icon-button-arrow-prev>
|
||||
<h1>Home Assistant Cloud</h1>
|
||||
<p>
|
||||
${this.localize(
|
||||
"ui.panel.page-onboarding.restore.ha-cloud.sign_in_description"
|
||||
)}
|
||||
</p>
|
||||
${this._showResetPasswordDone ? this._renderResetPasswordDone() : nothing}
|
||||
${this._view === "login"
|
||||
? html`<cloud-login
|
||||
card-less
|
||||
.email=${this._email}
|
||||
.localize=${this.localize}
|
||||
translation-key-panel="page-onboarding.restore.ha-cloud"
|
||||
@cloud-forgot-password=${this._showForgotPassword}
|
||||
></cloud-login>`
|
||||
: this._view === "loading"
|
||||
? html`<div class="loading">
|
||||
<ha-spinner size="large"></ha-spinner>
|
||||
</div>`
|
||||
: html`<cloud-forgot-password-card
|
||||
card-less
|
||||
.email=${this._email}
|
||||
.localize=${this.localize}
|
||||
translation-key-panel="page-onboarding.restore.ha-cloud.forgot_password"
|
||||
@cloud-email-changed=${this._emailChanged}
|
||||
@cloud-done=${this._showPasswordResetDone}
|
||||
></cloud-forgot-password-card>`}
|
||||
`;
|
||||
}
|
||||
|
||||
private _back() {
|
||||
if (this._view === "forgot-password") {
|
||||
this._view = "login";
|
||||
return;
|
||||
}
|
||||
|
||||
navigate(`${location.pathname}?${removeSearchParam("page")}`);
|
||||
}
|
||||
|
||||
private _renderResetPasswordDone() {
|
||||
return html`<ha-alert
|
||||
dismissable
|
||||
@alert-dismissed-clicked=${this._dismissResetPasswordDoneInfo}
|
||||
>
|
||||
${this.localize(
|
||||
"ui.panel.page-onboarding.restore.ha-cloud.forgot_password.check_your_email"
|
||||
)}
|
||||
</ha-alert>`;
|
||||
}
|
||||
|
||||
private async _showForgotPassword() {
|
||||
this._view = "loading";
|
||||
if (this._cloudLoginElement) {
|
||||
this._email = this._cloudLoginElement.emailField.value;
|
||||
}
|
||||
|
||||
await import(
|
||||
"../../panels/config/cloud/forgot-password/cloud-forgot-password-card"
|
||||
);
|
||||
this._view = "forgot-password";
|
||||
}
|
||||
|
||||
private _emailChanged() {
|
||||
if (this._forgotPasswordElement) {
|
||||
this._email = this._forgotPasswordElement?.emailField.value;
|
||||
}
|
||||
}
|
||||
|
||||
private _showPasswordResetDone() {
|
||||
this._view = "login";
|
||||
this._showResetPasswordDone = true;
|
||||
}
|
||||
|
||||
private _dismissResetPasswordDoneInfo() {
|
||||
this._showResetPasswordDone = false;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
onBoardingStyles,
|
||||
css`
|
||||
h1,
|
||||
p {
|
||||
text-align: left;
|
||||
}
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
h2 img {
|
||||
width: 48px;
|
||||
}
|
||||
.loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
ha-alert {
|
||||
margin-bottom: 8px;
|
||||
display: block;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"onboarding-restore-backup-cloud-login": OnboardingRestoreBackupCloudLogin;
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
import { css, html, LitElement, type CSSResultGroup } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../components/ha-card";
|
||||
import "../../components/ha-alert";
|
||||
import "../../components/ha-button";
|
||||
import "../../panels/config/backup/components/ha-backup-details-restore";
|
||||
import "../../panels/config/backup/components/ha-backup-details-summary";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
import type { BackupContentExtended } from "../../data/backup";
|
||||
|
||||
@customElement("onboarding-restore-backup-details")
|
||||
class OnboardingRestoreBackupDetails extends LitElement {
|
||||
@property({ attribute: false }) public localize!: LocalizeFunc;
|
||||
|
||||
@property({ attribute: false }) public backup!: BackupContentExtended;
|
||||
|
||||
render() {
|
||||
return html`
|
||||
${this.backup.homeassistant_included
|
||||
? html`<ha-backup-details-restore
|
||||
.backup=${this.backup}
|
||||
.localize=${this.localize}
|
||||
translation-key-panel="page-onboarding.restore"
|
||||
ha-required
|
||||
></ha-backup-details-restore>`
|
||||
: html`
|
||||
<ha-alert alert-type="error">
|
||||
${this.localize(
|
||||
"ui.panel.page-onboarding.restore.details.home_assistant_missing"
|
||||
)}
|
||||
</ha-alert>
|
||||
`}
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
:host {
|
||||
padding: 28px 20px 0;
|
||||
}
|
||||
ha-backup-details-restore {
|
||||
display: block;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"onboarding-restore-backup-details": OnboardingRestoreBackupDetails;
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
import { LitElement, html, css, type CSSResultGroup } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
import "../../components/ha-button";
|
||||
import "../../components/ha-icon-button-arrow-prev";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { navigate } from "../../common/navigate";
|
||||
import { removeSearchParam } from "../../common/url/search-params";
|
||||
import { onBoardingStyles } from "../styles";
|
||||
|
||||
@customElement("onboarding-restore-backup-no-cloud-backup")
|
||||
class OnboardingRestoreBackupNoCloudBackup extends LitElement {
|
||||
@property({ attribute: false }) public localize!: LocalizeFunc;
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<ha-icon-button-arrow-prev
|
||||
.label=${this.localize("ui.panel.page-onboarding.restore.back")}
|
||||
@click=${this._back}
|
||||
></ha-icon-button-arrow-prev>
|
||||
<h1>
|
||||
${this.localize(
|
||||
"ui.panel.page-onboarding.restore.ha-cloud.no_cloud_backup"
|
||||
)}
|
||||
</h1>
|
||||
<div class="description">
|
||||
${this.localize(
|
||||
"ui.panel.page-onboarding.restore.ha-cloud.no_cloud_backup_description"
|
||||
)}
|
||||
</div>
|
||||
<div class="actions">
|
||||
<ha-button @click=${this._signOut}>
|
||||
${this.localize("ui.panel.page-onboarding.restore.ha-cloud.sign_out")}
|
||||
</ha-button>
|
||||
<a
|
||||
href="https://www.nabucasa.com/config/backups/"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<ha-button>
|
||||
${this.localize(
|
||||
"ui.panel.page-onboarding.restore.ha-cloud.learn_more"
|
||||
)}
|
||||
</ha-button>
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _back() {
|
||||
navigate(`${location.pathname}?${removeSearchParam("page")}`);
|
||||
}
|
||||
|
||||
private _signOut() {
|
||||
fireEvent(this, "sign-out");
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
onBoardingStyles,
|
||||
css`
|
||||
h1,
|
||||
p {
|
||||
text-align: left;
|
||||
}
|
||||
.description {
|
||||
font-size: 1rem;
|
||||
line-height: 1.5rem;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"onboarding-restore-backup-no-cloud-backup": OnboardingRestoreBackupNoCloudBackup;
|
||||
}
|
||||
interface HASSDomEvents {
|
||||
"sign-out": undefined;
|
||||
}
|
||||
}
|
@ -1,20 +1,25 @@
|
||||
import { css, html, LitElement, nothing, type CSSResultGroup } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../components/ha-card";
|
||||
import { customElement, property, state, query } from "lit/decorators";
|
||||
import "../../components/ha-button";
|
||||
import "../../components/ha-alert";
|
||||
import "../../components/ha-md-list";
|
||||
import "../../components/ha-md-list-item";
|
||||
import "../../components/buttons/ha-progress-button";
|
||||
import "../../components/ha-icon-button-arrow-prev";
|
||||
import "../../components/ha-password-field";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import "../../panels/config/backup/components/ha-backup-data-picker";
|
||||
import "../../panels/config/backup/components/ha-backup-formfield-label";
|
||||
import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
import {
|
||||
CORE_LOCAL_AGENT,
|
||||
HASSIO_LOCAL_AGENT,
|
||||
getPreferredAgentForDownload,
|
||||
type BackupContentExtended,
|
||||
type BackupData,
|
||||
} from "../../data/backup";
|
||||
import { restoreOnboardingBackup } from "../../data/backup_onboarding";
|
||||
import type { HaProgressButton } from "../../components/buttons/ha-progress-button";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { onBoardingStyles } from "../styles";
|
||||
import { formatDateTimeWithBrowserDefaults } from "../../common/datetime/format_date_time";
|
||||
|
||||
@customElement("onboarding-restore-backup-restore")
|
||||
class OnboardingRestoreBackupRestore extends LitElement {
|
||||
@ -22,11 +27,12 @@ class OnboardingRestoreBackupRestore extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public backup!: BackupContentExtended;
|
||||
|
||||
@property({ attribute: false })
|
||||
public selectedData!: BackupData;
|
||||
|
||||
@property({ type: Boolean }) public supervisor = false;
|
||||
|
||||
@property() public error?: string;
|
||||
|
||||
@property() public mode!: "upload" | "cloud";
|
||||
|
||||
@state() private _encryptionKey = "";
|
||||
|
||||
@state() private _encryptionKeyWrong = false;
|
||||
@ -35,47 +41,136 @@ class OnboardingRestoreBackupRestore extends LitElement {
|
||||
|
||||
@state() private _loading = false;
|
||||
|
||||
@state() private _selectedData?: BackupData;
|
||||
|
||||
@query("ha-progress-button")
|
||||
private _progressButtonElement!: HaProgressButton;
|
||||
|
||||
render() {
|
||||
const agentId = this.supervisor ? HASSIO_LOCAL_AGENT : CORE_LOCAL_AGENT;
|
||||
const agentId = getPreferredAgentForDownload(
|
||||
Object.keys(this.backup.agents)
|
||||
);
|
||||
const backupProtected = this.backup.agents[agentId].protected;
|
||||
|
||||
const formattedDate = formatDateTimeWithBrowserDefaults(
|
||||
new Date(this.backup.date)
|
||||
);
|
||||
|
||||
const onlyHomeAssistantBackup =
|
||||
this.backup.addons.length === 0 && this.backup.folders.length === 0;
|
||||
|
||||
return html`
|
||||
${this.backup.homeassistant_included &&
|
||||
!this.supervisor &&
|
||||
(this.backup.addons.length > 0 || this.backup.folders.length > 0)
|
||||
? html`<ha-alert alert-type="warning" class="supervisor-warning">
|
||||
<ha-icon-button-arrow-prev
|
||||
.label=${this.localize("ui.panel.page-onboarding.restore.back")}
|
||||
@click=${this._back}
|
||||
></ha-icon-button-arrow-prev>
|
||||
<h1>
|
||||
${this.localize(
|
||||
"ui.panel.page-onboarding.restore.details.addons_unsupported"
|
||||
"ui.panel.page-onboarding.restore.details.restore.title"
|
||||
)}
|
||||
</ha-alert>`
|
||||
: nothing}
|
||||
<ha-card
|
||||
.header=${this.localize("ui.panel.page-onboarding.restore.restore")}
|
||||
>
|
||||
<div class="card-content">
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert> `
|
||||
: nothing}
|
||||
<p>
|
||||
</h1>
|
||||
|
||||
${this.backup.homeassistant_included
|
||||
? html`<div class="description">
|
||||
${this.localize(
|
||||
"ui.panel.page-onboarding.restore.confirm_restore_full_backup_text"
|
||||
)}
|
||||
</p>
|
||||
${backupProtected
|
||||
? html`<p>
|
||||
${this.localize(
|
||||
"ui.panel.page-onboarding.restore.details.restore.encryption.title"
|
||||
)}
|
||||
</p>
|
||||
${this._encryptionKeyWrong
|
||||
? html`
|
||||
</div>`
|
||||
: html`
|
||||
<ha-alert alert-type="error">
|
||||
${this.localize(
|
||||
"ui.panel.page-onboarding.restore.details.restore.encryption.incorrect_key"
|
||||
"ui.panel.page-onboarding.restore.details.home_assistant_missing"
|
||||
)}
|
||||
</ha-alert>
|
||||
`
|
||||
`}
|
||||
${this.error
|
||||
? html`<ha-alert
|
||||
alert-type="error"
|
||||
.title=${this.localize("ui.panel.page-onboarding.restore.failed")}
|
||||
>
|
||||
${this.error}
|
||||
</ha-alert>`
|
||||
: nothing}
|
||||
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.localize(
|
||||
"ui.panel.page-onboarding.restore.details.summary.created"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">${formattedDate}</span>
|
||||
</ha-md-list-item>
|
||||
${onlyHomeAssistantBackup
|
||||
? html`<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.localize(
|
||||
"ui.panel.page-onboarding.restore.details.summary.content"
|
||||
)}
|
||||
</span>
|
||||
<ha-backup-formfield-label
|
||||
slot="supporting-text"
|
||||
.version=${this.backup.homeassistant_version}
|
||||
.label=${this.localize(
|
||||
`ui.panel.page-onboarding.restore.data_picker.${this.backup.database_included ? "settings_and_history" : "settings"}`
|
||||
)}
|
||||
></ha-backup-formfield-label>
|
||||
</ha-md-list-item>`
|
||||
: nothing}
|
||||
</ha-md-list>
|
||||
|
||||
${!onlyHomeAssistantBackup
|
||||
? html`<h2>
|
||||
${this.localize("ui.panel.page-onboarding.restore.select_type")}
|
||||
</h2>`
|
||||
: nothing}
|
||||
${this.backup.homeassistant_included &&
|
||||
!this.supervisor &&
|
||||
this.backup.addons.length > 0
|
||||
? html`<ha-alert class="supervisor-warning">
|
||||
${this.localize(
|
||||
"ui.panel.page-onboarding.restore.details.addons_unsupported"
|
||||
)}
|
||||
<a
|
||||
slot="action"
|
||||
href="https://www.home-assistant.io/installation/#advanced-installation-methods"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<ha-button
|
||||
>${this.localize(
|
||||
"ui.panel.page-onboarding.restore.ha-cloud.learn_more"
|
||||
)}</ha-button
|
||||
>
|
||||
</a>
|
||||
</ha-alert>`
|
||||
: nothing}
|
||||
${!onlyHomeAssistantBackup
|
||||
? html`<ha-backup-data-picker
|
||||
translation-key-panel="page-onboarding.restore"
|
||||
.localize=${this.localize}
|
||||
.data=${this.backup}
|
||||
.value=${this._selectedData}
|
||||
@value-changed=${this._selectedBackupChanged}
|
||||
.requiredItems=${["config"]}
|
||||
.addonsDisabled=${!this.supervisor}
|
||||
></ha-backup-data-picker>`
|
||||
: nothing}
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert> `
|
||||
: nothing}
|
||||
${backupProtected
|
||||
? html`<div class="encryption">
|
||||
<h2>
|
||||
${this.localize(
|
||||
"ui.panel.page-onboarding.restore.details.restore.encryption.label"
|
||||
)}
|
||||
</h2>
|
||||
<span>
|
||||
${this.localize(
|
||||
`ui.panel.page-onboarding.restore.details.restore.encryption.description${this.mode === "cloud" ? "_cloud" : ""}`
|
||||
)}
|
||||
</span>
|
||||
<ha-password-field
|
||||
.disabled=${this._loading}
|
||||
@input=${this._encryptionKeyChanged}
|
||||
@ -83,46 +178,100 @@ class OnboardingRestoreBackupRestore extends LitElement {
|
||||
"ui.panel.page-onboarding.restore.details.restore.encryption.input_label"
|
||||
)}
|
||||
.value=${this._encryptionKey}
|
||||
></ha-password-field>`
|
||||
@keydown=${this._keyDown}
|
||||
.errorMessage=${this._encryptionKeyWrong
|
||||
? this.localize(
|
||||
"ui.panel.page-onboarding.restore.details.restore.encryption.incorrect_key"
|
||||
)
|
||||
: ""}
|
||||
.invalid=${this._encryptionKeyWrong}
|
||||
></ha-password-field>
|
||||
</div>`
|
||||
: nothing}
|
||||
|
||||
<div class="actions${this.mode === "cloud" ? " cloud" : ""}">
|
||||
${this.mode === "cloud"
|
||||
? html`<ha-button @click=${this._signOut}>
|
||||
${this.localize(
|
||||
"ui.panel.page-onboarding.restore.ha-cloud.sign_out"
|
||||
)}
|
||||
</ha-button>`
|
||||
: nothing}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button
|
||||
unelevated
|
||||
.progress=${this._loading}
|
||||
.disabled=${this._loading ||
|
||||
(backupProtected && this._encryptionKey === "")}
|
||||
(backupProtected && this._encryptionKey === "") ||
|
||||
!this.backup.homeassistant_included}
|
||||
@click=${this._startRestore}
|
||||
destructive
|
||||
>
|
||||
${this.localize(
|
||||
"ui.panel.page-onboarding.restore.details.restore.action"
|
||||
)}
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
protected willUpdate() {
|
||||
if (!this.hasUpdated) {
|
||||
this._selectedData = {
|
||||
homeassistant_included: true,
|
||||
folders: [],
|
||||
addons: [],
|
||||
homeassistant_version: this.backup.homeassistant_version,
|
||||
database_included: this.backup.database_included,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private _keyDown(ev: KeyboardEvent) {
|
||||
if (ev.key === "Enter" && this._encryptionKey !== "") {
|
||||
this._progressButtonElement.click();
|
||||
}
|
||||
}
|
||||
|
||||
private _signOut() {
|
||||
fireEvent(this, "sign-out");
|
||||
}
|
||||
|
||||
private _selectedBackupChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
this._selectedData = ev.detail.value;
|
||||
}
|
||||
|
||||
private _encryptionKeyChanged(ev): void {
|
||||
this._encryptionKey = ev.target.value;
|
||||
}
|
||||
|
||||
private async _startRestore(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as HaProgressButton;
|
||||
const agentId = Object.keys(this.backup.agents)[0];
|
||||
const backupProtected = this.backup.agents[agentId].protected;
|
||||
|
||||
if (
|
||||
this._loading ||
|
||||
(backupProtected && this._encryptionKey === "") ||
|
||||
!this.backup.homeassistant_included ||
|
||||
!this._selectedData
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._loading = true;
|
||||
const button = ev.currentTarget as HaProgressButton;
|
||||
this._error = undefined;
|
||||
this._encryptionKeyWrong = false;
|
||||
|
||||
const backupAgent = this.supervisor ? HASSIO_LOCAL_AGENT : CORE_LOCAL_AGENT;
|
||||
const backupAgent = Object.keys(this.backup.agents)[0];
|
||||
|
||||
try {
|
||||
await restoreOnboardingBackup({
|
||||
agent_id: backupAgent,
|
||||
backup_id: this.backup.backup_id,
|
||||
password: this._encryptionKey || undefined,
|
||||
restore_addons: this.selectedData.addons.map((addon) => addon.slug),
|
||||
restore_database: this.selectedData.database_included,
|
||||
restore_folders: this.selectedData.folders,
|
||||
restore_addons: this._selectedData.addons.map((addon) => addon.slug),
|
||||
restore_database: this._selectedData.database_included,
|
||||
restore_folders: this._selectedData.folders,
|
||||
});
|
||||
button.actionSuccess();
|
||||
fireEvent(this, "restore-started");
|
||||
@ -145,21 +294,81 @@ class OnboardingRestoreBackupRestore extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _back() {
|
||||
fireEvent(this, "restore-backup-back");
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
onBoardingStyles,
|
||||
css`
|
||||
:host {
|
||||
padding: 28px 20px 0;
|
||||
h1,
|
||||
p {
|
||||
text-align: left;
|
||||
}
|
||||
.card-actions {
|
||||
.description {
|
||||
font-size: 1rem;
|
||||
line-height: 1.5rem;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
ha-alert {
|
||||
display: block;
|
||||
margin-top: 16px;
|
||||
}
|
||||
ha-md-list {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
ha-md-list-item {
|
||||
--md-list-item-leading-space: 0;
|
||||
--md-list-item-trailing-space: 0;
|
||||
--md-list-item-two-line-container-height: 64px;
|
||||
--md-list-item-supporting-text-size: 1rem;
|
||||
--md-list-item-label-text-size: 0.875rem;
|
||||
|
||||
--md-list-item-label-text-color: var(--secondary-text-color);
|
||||
--md-list-item-supporting-text-color: var(--primary-text-color);
|
||||
}
|
||||
ha-md-list-item [slot="supporting-text"] {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
line-height: normal;
|
||||
}
|
||||
h2 {
|
||||
font-size: 22px;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 8px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
.supervisor-warning {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
ha-backup-data-picker {
|
||||
display: block;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.encryption {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.encryption ha-password-field {
|
||||
margin-top: 24px;
|
||||
}
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.actions.cloud {
|
||||
justify-content: space-between;
|
||||
}
|
||||
a ha-button {
|
||||
--mdc-theme-primary: var(--primary-color);
|
||||
white-space: nowrap;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
@ -171,5 +380,6 @@ declare global {
|
||||
}
|
||||
interface HASSDomEvents {
|
||||
"restore-started";
|
||||
"restore-backup-back";
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,12 @@
|
||||
import "@material/mwc-linear-progress/mwc-linear-progress";
|
||||
import { css, html, LitElement, nothing, type CSSResultGroup } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../components/ha-card";
|
||||
import "../../components/ha-spinner";
|
||||
import "../../components/ha-alert";
|
||||
import "../../components/ha-button";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
import type { BackupOnboardingInfo } from "../../data/backup_onboarding";
|
||||
import { onBoardingStyles } from "../styles";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { navigate } from "../../common/navigate";
|
||||
import { removeSearchParam } from "../../common/url/search-params";
|
||||
|
||||
@customElement("onboarding-restore-backup-status")
|
||||
class OnboardingRestoreBackupStatus extends LitElement {
|
||||
@ -20,22 +17,24 @@ class OnboardingRestoreBackupStatus extends LitElement {
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<ha-card
|
||||
.header=${this.localize(
|
||||
<h1>
|
||||
${this.localize(
|
||||
`ui.panel.page-onboarding.restore.${this.backupInfo.state === "restore_backup" ? "in_progress" : "failed"}`
|
||||
)}
|
||||
>
|
||||
</h1>
|
||||
${this.backupInfo.state === "restore_backup"
|
||||
? html` <p>
|
||||
${this.localize(
|
||||
`ui.panel.page-onboarding.restore.in_progress_description`
|
||||
)}
|
||||
</p>`
|
||||
: nothing}
|
||||
<div class="card-content">
|
||||
${this.backupInfo.state === "restore_backup"
|
||||
? html`
|
||||
<div class="loading">
|
||||
<ha-spinner></ha-spinner>
|
||||
<mwc-linear-progress indeterminate></mwc-linear-progress>
|
||||
</div>
|
||||
<p>
|
||||
${this.localize(
|
||||
"ui.panel.page-onboarding.restore.in_progress_description"
|
||||
)}
|
||||
</p>
|
||||
`
|
||||
: html`
|
||||
<ha-alert alert-type="error">
|
||||
@ -54,39 +53,28 @@ class OnboardingRestoreBackupStatus extends LitElement {
|
||||
`}
|
||||
</div>
|
||||
${this.backupInfo.state !== "restore_backup"
|
||||
? html`<div class="card-actions">
|
||||
<ha-button @click=${this._uploadAnother} destructive>
|
||||
${this.localize(
|
||||
`ui.panel.page-onboarding.restore.details.summary.upload_another`
|
||||
)}
|
||||
</ha-button>
|
||||
<ha-button @click=${this._home} destructive>
|
||||
${this.localize(
|
||||
`ui.panel.page-onboarding.restore.details.summary.home`
|
||||
)}
|
||||
? html`<div class="actions">
|
||||
<ha-button @click=${this._back}>
|
||||
${this.localize("ui.panel.page-onboarding.restore.back")}
|
||||
</ha-button>
|
||||
</div>`
|
||||
: nothing}
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private _uploadAnother() {
|
||||
fireEvent(this, "show-backup-upload");
|
||||
}
|
||||
|
||||
private _home() {
|
||||
navigate(`${location.pathname}?${removeSearchParam("page")}`);
|
||||
private _back() {
|
||||
fireEvent(this, "restore-backup-back");
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
onBoardingStyles,
|
||||
css`
|
||||
:host {
|
||||
padding: 28px 20px 0;
|
||||
h1,
|
||||
p {
|
||||
text-align: left;
|
||||
}
|
||||
.card-actions {
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
@ -104,6 +92,9 @@ class OnboardingRestoreBackupStatus extends LitElement {
|
||||
padding: 16px 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
mwc-linear-progress {
|
||||
width: 100%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { mdiFolderUpload } from "@mdi/js";
|
||||
import { css, html, LitElement, nothing, type CSSResultGroup } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../components/ha-card";
|
||||
import "../../components/ha-file-upload";
|
||||
import "../../components/ha-alert";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import "../../components/ha-icon-button-arrow-prev";
|
||||
import { fireEvent, type HASSDomEvent } from "../../common/dom/fire_event";
|
||||
import { showAlertDialog } from "../../dialogs/generic/show-dialog-box";
|
||||
import {
|
||||
@ -14,6 +13,9 @@ import {
|
||||
} from "../../data/backup";
|
||||
import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
import { uploadOnboardingBackup } from "../../data/backup_onboarding";
|
||||
import { onBoardingStyles } from "../styles";
|
||||
import { navigate } from "../../common/navigate";
|
||||
import { removeSearchParam } from "../../common/url/search-params";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
@ -32,12 +34,18 @@ class OnboardingRestoreBackupUpload extends LitElement {
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<ha-card
|
||||
.header=${this.localize(
|
||||
"ui.panel.page-onboarding.restore.upload_backup"
|
||||
<ha-icon-button-arrow-prev
|
||||
.label=${this.localize("ui.panel.page-onboarding.restore.back")}
|
||||
@click=${this._back}
|
||||
></ha-icon-button-arrow-prev>
|
||||
<h1>
|
||||
${this.localize("ui.panel.page-onboarding.restore.upload_backup")}
|
||||
</h1>
|
||||
<p>
|
||||
${this.localize(
|
||||
"ui.panel.page-onboarding.restore.upload_backup_subtitle"
|
||||
)}
|
||||
>
|
||||
<div class="card-content">
|
||||
</p>
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: nothing}
|
||||
@ -55,16 +63,12 @@ class OnboardingRestoreBackupUpload extends LitElement {
|
||||
.supports=${this.localize(
|
||||
"ui.panel.page-onboarding.restore.upload_supports_tar"
|
||||
)}
|
||||
.deleteLabel=${this.localize(
|
||||
"ui.panel.page-onboarding.restore.delete"
|
||||
)}
|
||||
.deleteLabel=${this.localize("ui.panel.page-onboarding.restore.delete")}
|
||||
.uploadingLabel=${this.localize(
|
||||
"ui.panel.page-onboarding.restore.uploading"
|
||||
)}
|
||||
@file-picked=${this._filePicked}
|
||||
></ha-file-upload>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -98,17 +102,20 @@ class OnboardingRestoreBackupUpload extends LitElement {
|
||||
typeof err.body === "string"
|
||||
? err.body
|
||||
: err.body?.message || err.message || "Unknown error occurred";
|
||||
} finally {
|
||||
this._uploading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private _back() {
|
||||
navigate(`${location.pathname}?${removeSearchParam("page")}`);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
onBoardingStyles,
|
||||
css`
|
||||
:host {
|
||||
width: 100%;
|
||||
h1,
|
||||
p {
|
||||
text-align: left;
|
||||
}
|
||||
.card-actions {
|
||||
display: flex;
|
||||
|
@ -1,16 +1,24 @@
|
||||
import { css } from "lit";
|
||||
|
||||
export const onBoardingStyles = css`
|
||||
.card-content {
|
||||
padding: 32px;
|
||||
}
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-weight: 400;
|
||||
font-size: 28px;
|
||||
line-height: 36px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
ha-icon-button-arrow-prev {
|
||||
margin-left: -12px;
|
||||
display: block;
|
||||
}
|
||||
p {
|
||||
font-size: 1rem;
|
||||
line-height: 1.5rem;
|
||||
text-align: center;
|
||||
margin-top: 0;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.footer {
|
||||
margin-top: 16px;
|
||||
|
@ -7,7 +7,6 @@ import { stringCompare } from "../../../../common/string/compare";
|
||||
import "../../../../components/ha-checkbox";
|
||||
import type { HaCheckbox } from "../../../../components/ha-checkbox";
|
||||
import "../../../../components/ha-formfield";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import "./ha-backup-formfield-label";
|
||||
|
||||
@ -30,6 +29,8 @@ export class HaBackupAddonsPicker extends LitElement {
|
||||
@property({ attribute: "hide-version", type: Boolean })
|
||||
public hideVersion = false;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
private _addons = memoizeOne((addons: BackupAddonItem[]) =>
|
||||
addons.sort((a, b) =>
|
||||
stringCompare(a.name, b.name, this.hass?.locale?.language)
|
||||
@ -56,6 +57,7 @@ export class HaBackupAddonsPicker extends LitElement {
|
||||
.id=${item.slug}
|
||||
.checked=${this.value?.includes(item.slug) || false}
|
||||
@change=${this._checkboxChanged}
|
||||
.disabled=${this.disabled}
|
||||
></ha-checkbox>
|
||||
</ha-formfield>
|
||||
`
|
||||
|
@ -17,7 +17,6 @@ import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import "../../../../components/ha-checkbox";
|
||||
import type { HaCheckbox } from "../../../../components/ha-checkbox";
|
||||
import "../../../../components/ha-formfield";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import type { BackupData } from "../../../../data/backup";
|
||||
import { fetchHassioAddonsInfo } from "../../../../data/hassio/addon";
|
||||
import { mdiHomeAssistant } from "../../../../resources/home-assistant-logo-svg";
|
||||
@ -62,6 +61,8 @@ export class HaBackupDataPicker extends LitElement {
|
||||
| "page-onboarding.restore"
|
||||
| "config.backup" = "config.backup";
|
||||
|
||||
@property({ type: Boolean, attribute: false }) public addonsDisabled = false;
|
||||
|
||||
@state() public _addonIcons: Record<string, boolean> = {};
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues): void {
|
||||
@ -304,6 +305,7 @@ export class HaBackupDataPicker extends LitElement {
|
||||
.indeterminate=${selectedItems.addons.length > 0 &&
|
||||
selectedItems.addons.length < addonsItems.length}
|
||||
@change=${this._sectionChanged}
|
||||
.disabled=${this.addonsDisabled}
|
||||
></ha-checkbox>
|
||||
</ha-formfield>
|
||||
<ha-backup-addons-picker
|
||||
@ -311,6 +313,7 @@ export class HaBackupDataPicker extends LitElement {
|
||||
.value=${selectedItems.addons}
|
||||
@value-changed=${this._addonsChanged}
|
||||
.addons=${addonsItems}
|
||||
.disabled=${this.addonsDisabled}
|
||||
>
|
||||
</ha-backup-addons-picker>
|
||||
</div>
|
||||
|
@ -1,77 +1,54 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-md-list";
|
||||
import "../../../../components/ha-md-list-item";
|
||||
import "../../../../components/ha-button";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import {
|
||||
formatDateTime,
|
||||
formatDateTimeWithBrowserDefaults,
|
||||
} from "../../../../common/datetime/format_date_time";
|
||||
import { formatDateTime } from "../../../../common/datetime/format_date_time";
|
||||
import {
|
||||
computeBackupSize,
|
||||
computeBackupType,
|
||||
type BackupContentExtended,
|
||||
} from "../../../../data/backup";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { bytesToString } from "../../../../util/bytes-to-string";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"show-backup-upload": undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("ha-backup-details-summary")
|
||||
class HaBackupDetailsSummary extends LitElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public localize!: LocalizeFunc;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Object }) public backup!: BackupContentExtended;
|
||||
|
||||
@property({ type: Boolean, attribute: "hassio" }) public isHassio = false;
|
||||
|
||||
@property({ attribute: "translation-key-panel" }) public translationKeyPanel:
|
||||
| "page-onboarding.restore"
|
||||
| "config.backup" = "config.backup";
|
||||
|
||||
@property({ type: Boolean, attribute: "show-upload-another" })
|
||||
public showUploadAnother = false;
|
||||
|
||||
render() {
|
||||
const backupDate = new Date(this.backup.date);
|
||||
const formattedDate = this.hass
|
||||
? formatDateTime(backupDate, this.hass.locale, this.hass.config)
|
||||
: formatDateTimeWithBrowserDefaults(backupDate);
|
||||
const formattedDate = formatDateTime(
|
||||
backupDate,
|
||||
this.hass.locale,
|
||||
this.hass.config
|
||||
);
|
||||
|
||||
return html`
|
||||
<ha-card>
|
||||
<div class="card-header">
|
||||
${this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.details.summary.title`
|
||||
)}
|
||||
${this.hass.localize("ui.panel.config.backup.details.summary.title")}
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<ha-md-list class="summary">
|
||||
${this.translationKeyPanel === "config.backup"
|
||||
? html`<ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.localize("ui.panel.config.backup.backup_type")}
|
||||
${this.hass.localize("ui.panel.config.backup.backup_type")}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
${this.localize(
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.backup.type.${computeBackupType(this.backup, this.isHassio)}`
|
||||
)}
|
||||
</span>
|
||||
</ha-md-list-item>`
|
||||
: nothing}
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.details.summary.size`
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.details.summary.size"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
@ -80,31 +57,18 @@ class HaBackupDetailsSummary extends LitElement {
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.details.summary.created`
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.details.summary.created"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text"> ${formattedDate} </span>
|
||||
<span slot="supporting-text">${formattedDate}</span>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
</div>
|
||||
${this.showUploadAnother
|
||||
? html`<div class="card-actions">
|
||||
<ha-button @click=${this._uploadAnother} destructive>
|
||||
${this.localize(
|
||||
`ui.panel.page-onboarding.restore.details.summary.upload_another`
|
||||
)}
|
||||
</ha-button>
|
||||
</div>`
|
||||
: nothing}
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private _uploadAnother() {
|
||||
fireEvent(this, "show-backup-upload");
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
max-width: 690px;
|
||||
|
@ -26,7 +26,7 @@ class DialogCloudAlreadyConnected extends LitElement {
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
this._params?.closeDialog();
|
||||
this._params?.closeDialog?.();
|
||||
this._params = undefined;
|
||||
this._obfuscateIp = true;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
@ -148,7 +148,7 @@ class DialogCloudAlreadyConnected extends LitElement {
|
||||
}
|
||||
|
||||
private _logInHere() {
|
||||
this._params?.logInHereAction();
|
||||
this._params?.logInHereAction?.();
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
|
@ -7,17 +7,31 @@ export interface CloudAlreadyConnectedParams {
|
||||
name?: string;
|
||||
version?: string;
|
||||
};
|
||||
logInHereAction: () => void;
|
||||
closeDialog: () => void;
|
||||
logInHereAction?: () => void;
|
||||
closeDialog?: () => void;
|
||||
}
|
||||
|
||||
export const showCloudAlreadyConnectedDialog = (
|
||||
element: HTMLElement,
|
||||
webhookDialogParams: CloudAlreadyConnectedParams
|
||||
): void => {
|
||||
) =>
|
||||
new Promise((resolve) => {
|
||||
const originalClose = webhookDialogParams.closeDialog;
|
||||
const originalLogInHereAction = webhookDialogParams.logInHereAction;
|
||||
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-cloud-already-connected",
|
||||
dialogImport: () => import("./dialog-cloud-already-connected"),
|
||||
dialogParams: webhookDialogParams,
|
||||
dialogParams: {
|
||||
...webhookDialogParams,
|
||||
closeDialog: () => {
|
||||
originalClose?.();
|
||||
resolve(false);
|
||||
},
|
||||
logInHereAction: () => {
|
||||
originalLogInHereAction?.();
|
||||
resolve(true);
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@ -0,0 +1,170 @@
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/buttons/ha-progress-button";
|
||||
import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-card";
|
||||
import type { HaTextField } from "../../../../components/ha-textfield";
|
||||
import "../../../../components/ha-textfield";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import { cloudForgotPassword } from "../../../../data/cloud";
|
||||
import { forgotPasswordHaCloud } from "../../../../data/onboarding";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
|
||||
@customElement("cloud-forgot-password-card")
|
||||
export class CloudForgotPasswordCard extends LitElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public localize!: LocalizeFunc;
|
||||
|
||||
@property({ attribute: "translation-key-panel" }) public translationKeyPanel:
|
||||
| "page-onboarding.restore.ha-cloud.forgot_password"
|
||||
| "config.cloud.forgot_password" = "config.cloud.forgot_password";
|
||||
|
||||
@property() public email?: string;
|
||||
|
||||
@property({ type: Boolean, attribute: "card-less" }) public cardLess = false;
|
||||
|
||||
@state() private _inProgress = false;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@query("#email", true) public emailField!: HaTextField;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (this.cardLess) {
|
||||
return this._renderContent();
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-card
|
||||
outlined
|
||||
.header=${this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.subtitle`
|
||||
)}
|
||||
>
|
||||
${this._renderContent()}
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderContent() {
|
||||
return html`
|
||||
<div class="card-content">
|
||||
<p>
|
||||
${this.localize(`ui.panel.${this.translationKeyPanel}.instructions`)}
|
||||
</p>
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: nothing}
|
||||
<ha-textfield
|
||||
autofocus
|
||||
id="email"
|
||||
label=${this.localize(`ui.panel.${this.translationKeyPanel}.email`)}
|
||||
.value=${this.email ?? ""}
|
||||
type="email"
|
||||
required
|
||||
.disabled=${this._inProgress}
|
||||
@keydown=${this._keyDown}
|
||||
.validationMessage=${this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.email_error_msg`
|
||||
)}
|
||||
></ha-textfield>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button
|
||||
@click=${this._handleEmailPasswordReset}
|
||||
.progress=${this._inProgress}
|
||||
>
|
||||
${this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.send_reset_email`
|
||||
)}
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _keyDown(ev: KeyboardEvent) {
|
||||
if (ev.key === "Enter") {
|
||||
this._handleEmailPasswordReset();
|
||||
}
|
||||
}
|
||||
|
||||
private _resetPassword = async (email: string) => {
|
||||
this._inProgress = true;
|
||||
|
||||
try {
|
||||
if (this.hass) {
|
||||
await cloudForgotPassword(this.hass, email);
|
||||
} else {
|
||||
// for onboarding
|
||||
await forgotPasswordHaCloud(email);
|
||||
}
|
||||
fireEvent(this, "cloud-email-changed", { value: email });
|
||||
this._inProgress = false;
|
||||
fireEvent(this, "cloud-done", {
|
||||
flashMessage: this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.check_your_email`
|
||||
),
|
||||
});
|
||||
} catch (err: any) {
|
||||
this._inProgress = false;
|
||||
const errCode = err && err.body && err.body.code;
|
||||
if (errCode === "usernotfound" && email !== email.toLowerCase()) {
|
||||
await this._resetPassword(email.toLowerCase());
|
||||
} else {
|
||||
this._error =
|
||||
err && err.body && err.body.message
|
||||
? err.body.message
|
||||
: "Unknown error";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private async _handleEmailPasswordReset() {
|
||||
const emailField = this.emailField;
|
||||
|
||||
const email = emailField.value;
|
||||
|
||||
if (!emailField.reportValidity()) {
|
||||
emailField.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
this._inProgress = true;
|
||||
|
||||
this._resetPassword(email);
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
ha-card {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
margin-top: 24px;
|
||||
}
|
||||
h1 {
|
||||
margin: 0;
|
||||
}
|
||||
ha-textfield {
|
||||
width: 100%;
|
||||
}
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"cloud-forgot-password-card": CloudForgotPasswordCard;
|
||||
}
|
||||
}
|
@ -1,13 +1,7 @@
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/buttons/ha-progress-button";
|
||||
import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-card";
|
||||
import type { HaTextField } from "../../../../components/ha-textfield";
|
||||
import "../../../../components/ha-textfield";
|
||||
import { cloudForgotPassword } from "../../../../data/cloud";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "./cloud-forgot-password-card";
|
||||
import "../../../../layouts/hass-subpage";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
@ -22,10 +16,6 @@ export class CloudForgotPassword extends LitElement {
|
||||
|
||||
@state() public _requestInProgress = false;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@query("#email", true) private _emailField!: HaTextField;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<hass-subpage
|
||||
@ -36,98 +26,16 @@ export class CloudForgotPassword extends LitElement {
|
||||
)}
|
||||
>
|
||||
<div class="content">
|
||||
<ha-card
|
||||
outlined
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.cloud.forgot_password.subtitle"
|
||||
)}
|
||||
>
|
||||
<div class="card-content">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.forgot_password.instructions"
|
||||
)}
|
||||
</p>
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
<ha-textfield
|
||||
autofocus
|
||||
id="email"
|
||||
label=${this.hass.localize(
|
||||
"ui.panel.config.cloud.forgot_password.email"
|
||||
)}
|
||||
.value=${this.email}
|
||||
type="email"
|
||||
required
|
||||
@keydown=${this._keyDown}
|
||||
.validationMessage=${this.hass.localize(
|
||||
"ui.panel.config.cloud.forgot_password.email_error_msg"
|
||||
)}
|
||||
></ha-textfield>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button
|
||||
@click=${this._handleEmailPasswordReset}
|
||||
.progress=${this._requestInProgress}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.forgot_password.send_reset_email"
|
||||
)}
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
<cloud-forgot-password-card
|
||||
.hass=${this.hass}
|
||||
.localize=${this.hass.localize}
|
||||
.email=${this.email}
|
||||
></cloud-forgot-password-card>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
private _keyDown(ev: KeyboardEvent) {
|
||||
if (ev.key === "Enter") {
|
||||
this._handleEmailPasswordReset();
|
||||
}
|
||||
}
|
||||
|
||||
private async _handleEmailPasswordReset() {
|
||||
const emailField = this._emailField;
|
||||
|
||||
const email = emailField.value;
|
||||
|
||||
if (!emailField.reportValidity()) {
|
||||
emailField.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
this._requestInProgress = true;
|
||||
|
||||
const doResetPassword = async (username: string) => {
|
||||
try {
|
||||
await cloudForgotPassword(this.hass, username);
|
||||
// @ts-ignore
|
||||
fireEvent(this, "email-changed", { value: username });
|
||||
this._requestInProgress = false;
|
||||
// @ts-ignore
|
||||
fireEvent(this, "cloud-done", {
|
||||
flashMessage: this.hass.localize(
|
||||
"ui.panel.config.cloud.forgot_password.check_your_email"
|
||||
),
|
||||
});
|
||||
} catch (err: any) {
|
||||
this._requestInProgress = false;
|
||||
const errCode = err && err.body && err.body.code;
|
||||
if (errCode === "usernotfound" && username !== username.toLowerCase()) {
|
||||
await doResetPassword(username.toLowerCase());
|
||||
} else {
|
||||
this._error =
|
||||
err && err.body && err.body.message
|
||||
? err.body.message
|
||||
: "Unknown error";
|
||||
}
|
||||
}
|
||||
};
|
||||
await doResetPassword(email);
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
haStyle,
|
||||
@ -135,25 +43,6 @@ export class CloudForgotPassword extends LitElement {
|
||||
.content {
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
ha-card {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
margin-top: 24px;
|
||||
}
|
||||
h1 {
|
||||
margin: 0;
|
||||
}
|
||||
ha-textfield {
|
||||
width: 100%;
|
||||
}
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.card-actions a {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import type { RouterOptions } from "../../../layouts/hass-router-page";
|
||||
import { HassRouterPage } from "../../../layouts/hass-router-page";
|
||||
import type { ValueChangedEvent, HomeAssistant, Route } from "../../../types";
|
||||
import "./account/cloud-account";
|
||||
import "./login/cloud-login";
|
||||
import "./login/cloud-login-panel";
|
||||
|
||||
const LOGGED_IN_URLS = ["account", "google-assistant", "alexa"];
|
||||
const NOT_LOGGED_IN_URLS = ["login", "register", "forgot-password"];
|
||||
@ -39,7 +39,7 @@ class HaConfigCloud extends HassRouterPage {
|
||||
},
|
||||
routes: {
|
||||
login: {
|
||||
tag: "cloud-login",
|
||||
tag: "cloud-login-panel",
|
||||
},
|
||||
register: {
|
||||
tag: "cloud-register",
|
||||
@ -90,7 +90,7 @@ class HaConfigCloud extends HassRouterPage {
|
||||
|
||||
protected createElement(tag: string) {
|
||||
const el = super.createElement(tag);
|
||||
el.addEventListener("email-changed", (ev) => {
|
||||
el.addEventListener("cloud-email-changed", (ev) => {
|
||||
this._loginEmail = (ev as ValueChangedEvent<string>).detail.value;
|
||||
});
|
||||
el.addEventListener("flash-message-changed", (ev) => {
|
||||
|
246
src/panels/config/cloud/login/cloud-login-panel.ts
Normal file
246
src/panels/config/cloud/login/cloud-login-panel.ts
Normal file
@ -0,0 +1,246 @@
|
||||
import "@material/mwc-list/mwc-list";
|
||||
import { mdiDeleteForever, mdiDotsVertical, mdiDownload } from "@mdi/js";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../../common/navigate";
|
||||
import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-icon-next";
|
||||
import "../../../../components/ha-list-item";
|
||||
import "../../../../components/ha-button-menu";
|
||||
import { removeCloudData } from "../../../../data/cloud";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../../layouts/hass-subpage";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import "../../ha-config-section";
|
||||
import "./cloud-login";
|
||||
import { showSupportPackageDialog } from "../account/show-dialog-cloud-support-package";
|
||||
import type { CloudLogin } from "./cloud-login";
|
||||
|
||||
@customElement("cloud-login-panel")
|
||||
export class CloudLoginPanel extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: "is-wide", type: Boolean }) public isWide = false;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property() public email?: string;
|
||||
|
||||
@property({ attribute: false }) public flashMessage?: string;
|
||||
|
||||
@query("cloud-login") private _cloudLoginElement!: CloudLogin;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<hass-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
header="Home Assistant Cloud"
|
||||
>
|
||||
<ha-button-menu slot="toolbar-icon" @action=${this._handleMenuAction}>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
|
||||
<ha-list-item graphic="icon">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.account.reset_cloud_data"
|
||||
)}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiDeleteForever}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
<ha-list-item graphic="icon">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.account.download_support_package"
|
||||
)}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiDownload}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</ha-button-menu>
|
||||
<div class="content">
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<span slot="header">Home Assistant Cloud</span>
|
||||
<div slot="introduction">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.introduction"
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.introduction2"
|
||||
)}
|
||||
<a
|
||||
href="https://www.nabucasa.com"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Nabu Casa, Inc</a
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.introduction2a"
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.introduction3"
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<a
|
||||
href="https://www.nabucasa.com"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.learn_more_link"
|
||||
)}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
${this.flashMessage
|
||||
? html`<ha-alert
|
||||
dismissable
|
||||
@alert-dismissed-clicked=${this._dismissFlash}
|
||||
>
|
||||
${this.flashMessage}
|
||||
</ha-alert>`
|
||||
: ""}
|
||||
|
||||
<cloud-login
|
||||
.hass=${this.hass}
|
||||
.email=${this.email}
|
||||
.localize=${this.hass.localize}
|
||||
@cloud-forgot-password=${this._handleForgotPassword}
|
||||
check-connection
|
||||
></cloud-login>
|
||||
|
||||
<ha-card outlined>
|
||||
<mwc-list>
|
||||
<ha-list-item @click=${this._handleRegister} twoline hasMeta>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.start_trial"
|
||||
)}
|
||||
<span slot="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.trial_info"
|
||||
)}
|
||||
</span>
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</mwc-list>
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleForgotPassword() {
|
||||
this._dismissFlash();
|
||||
fireEvent(this, "cloud-email-changed", {
|
||||
value: this._cloudLoginElement.emailField.value,
|
||||
});
|
||||
navigate("/config/cloud/forgot-password");
|
||||
}
|
||||
|
||||
private _handleRegister() {
|
||||
this._dismissFlash();
|
||||
|
||||
fireEvent(this, "cloud-email-changed", {
|
||||
value: this._cloudLoginElement.emailField.value,
|
||||
});
|
||||
navigate("/config/cloud/register");
|
||||
}
|
||||
|
||||
private _dismissFlash() {
|
||||
fireEvent(this, "flash-message-changed", { value: "" });
|
||||
}
|
||||
|
||||
private _handleMenuAction(ev) {
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
this._deleteCloudData();
|
||||
break;
|
||||
case 1:
|
||||
this._downloadSupportPackage();
|
||||
}
|
||||
}
|
||||
|
||||
private async _deleteCloudData() {
|
||||
const confirm = await showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.cloud.account.reset_data_confirm_title"
|
||||
),
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.cloud.account.reset_data_confirm_text"
|
||||
),
|
||||
confirmText: this.hass.localize("ui.panel.config.cloud.account.reset"),
|
||||
destructive: true,
|
||||
});
|
||||
if (!confirm) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await removeCloudData(this.hass);
|
||||
} catch (err: any) {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.cloud.account.reset_data_failed"
|
||||
),
|
||||
text: err?.message,
|
||||
});
|
||||
return;
|
||||
} finally {
|
||||
fireEvent(this, "ha-refresh-cloud-status");
|
||||
}
|
||||
}
|
||||
|
||||
private async _downloadSupportPackage() {
|
||||
showSupportPackageDialog(this);
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.content {
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
[slot="introduction"] {
|
||||
margin: -1em 0;
|
||||
}
|
||||
[slot="introduction"] a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
ha-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
ha-card .card-header {
|
||||
margin-bottom: -8px;
|
||||
}
|
||||
h1 {
|
||||
margin: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"cloud-login-panel": CloudLoginPanel;
|
||||
}
|
||||
|
||||
interface HASSDomEvents {
|
||||
"cloud-email-changed": { value: string };
|
||||
"flash-message-changed": { value: string };
|
||||
}
|
||||
}
|
@ -1,217 +1,126 @@
|
||||
import "@material/mwc-button";
|
||||
import "@material/mwc-list/mwc-list";
|
||||
import { mdiDeleteForever, mdiDotsVertical, mdiDownload } from "@mdi/js";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../../common/navigate";
|
||||
import "../../../../components/buttons/ha-progress-button";
|
||||
import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-icon-next";
|
||||
import "../../../../components/ha-list-item";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-password-field";
|
||||
import "../../../../components/ha-button-menu";
|
||||
import type { HaPasswordField } from "../../../../components/ha-password-field";
|
||||
import "../../../../components/ha-textfield";
|
||||
import type { HaTextField } from "../../../../components/ha-textfield";
|
||||
import { setAssistPipelinePreferred } from "../../../../data/assist_pipeline";
|
||||
import { cloudLogin, removeCloudData } from "../../../../data/cloud";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import { cloudLogin } from "../../../../data/cloud";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
showPromptDialog,
|
||||
} from "../../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../../layouts/hass-subpage";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import "../../ha-config-section";
|
||||
import { showSupportPackageDialog } from "../account/show-dialog-cloud-support-package";
|
||||
} from "../../../lovelace/custom-card-helpers";
|
||||
import { setAssistPipelinePreferred } from "../../../../data/assist_pipeline";
|
||||
import { showCloudAlreadyConnectedDialog } from "../dialog-cloud-already-connected/show-dialog-cloud-already-connected";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { loginHaCloud } from "../../../../data/onboarding";
|
||||
|
||||
@customElement("cloud-login")
|
||||
export class CloudLogin extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: "is-wide", type: Boolean }) public isWide = false;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
@property({ type: Boolean, attribute: "check-connection" })
|
||||
public checkConnection = false;
|
||||
|
||||
@property() public email?: string;
|
||||
|
||||
@property({ attribute: false }) public flashMessage?: string;
|
||||
@property({ attribute: false }) public localize!: LocalizeFunc;
|
||||
|
||||
@state() private _password?: string;
|
||||
@property({ attribute: "translation-key-panel" }) public translationKeyPanel:
|
||||
| "page-onboarding.restore.ha-cloud"
|
||||
| "config.cloud" = "config.cloud";
|
||||
|
||||
@state() private _requestInProgress = false;
|
||||
@property({ type: Boolean, attribute: "card-less" }) public cardLess = false;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@state() private _checkConnection = true;
|
||||
|
||||
@query("#email", true) private _emailField!: HaTextField;
|
||||
@query("#email", true) public emailField!: HaTextField;
|
||||
|
||||
@query("#password", true) private _passwordField!: HaPasswordField;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@state() private _inProgress = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (this.cardLess) {
|
||||
return this._renderLoginForm();
|
||||
}
|
||||
|
||||
return html`
|
||||
<hass-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
header="Home Assistant Cloud"
|
||||
>
|
||||
<ha-button-menu slot="toolbar-icon" @action=${this._handleMenuAction}>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
|
||||
<ha-list-item graphic="icon">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.account.reset_cloud_data"
|
||||
)}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiDeleteForever}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
<ha-list-item graphic="icon">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.account.download_support_package"
|
||||
)}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiDownload}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</ha-button-menu>
|
||||
<div class="content">
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<span slot="header">Home Assistant Cloud</span>
|
||||
<div slot="introduction">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.introduction"
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.introduction2"
|
||||
)}
|
||||
<a
|
||||
href="https://www.nabucasa.com"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Nabu Casa, Inc</a
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.introduction2a"
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.introduction3"
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<a
|
||||
href="https://www.nabucasa.com"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.learn_more_link"
|
||||
)}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
${this.flashMessage
|
||||
? html`<ha-alert
|
||||
dismissable
|
||||
@alert-dismissed-clicked=${this._dismissFlash}
|
||||
>
|
||||
${this.flashMessage}
|
||||
</ha-alert>`
|
||||
: ""}
|
||||
|
||||
<ha-card
|
||||
outlined
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.sign_in"
|
||||
.header=${this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.login.sign_in`
|
||||
)}
|
||||
>
|
||||
${this._renderLoginForm()}
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderLoginForm() {
|
||||
return html`
|
||||
<div class="card-content login-form">
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
: nothing}
|
||||
<ha-textfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.email"
|
||||
.label=${this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.login.email`
|
||||
)}
|
||||
id="email"
|
||||
name="username"
|
||||
type="email"
|
||||
autocomplete="username"
|
||||
required
|
||||
.value=${this.email}
|
||||
.value=${this.email ?? ""}
|
||||
@keydown=${this._keyDown}
|
||||
.disabled=${this._requestInProgress}
|
||||
.validationMessage=${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.email_error_msg"
|
||||
.disabled=${this._inProgress}
|
||||
.validationMessage=${this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.login.email_error_msg`
|
||||
)}
|
||||
></ha-textfield>
|
||||
<ha-password-field
|
||||
id="password"
|
||||
name="password"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.password"
|
||||
.label=${this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.login.password`
|
||||
)}
|
||||
.value=${this._password || ""}
|
||||
autocomplete="current-password"
|
||||
required
|
||||
minlength="8"
|
||||
@keydown=${this._keyDown}
|
||||
.disabled=${this._requestInProgress}
|
||||
.validationMessage=${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.password_error_msg"
|
||||
.disabled=${this._inProgress}
|
||||
.validationMessage=${this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.login.password_error_msg`
|
||||
)}
|
||||
></ha-password-field>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button
|
||||
@click=${this._handleLogin}
|
||||
.progress=${this._requestInProgress}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.sign_in"
|
||||
)}</ha-progress-button
|
||||
>
|
||||
<button
|
||||
class="link pwd-forgot-link"
|
||||
.disabled=${this._requestInProgress}
|
||||
<ha-button
|
||||
.disabled=${this._inProgress}
|
||||
@click=${this._handleForgotPassword}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.forgot_password"
|
||||
${this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.login.forgot_password`
|
||||
)}
|
||||
</button>
|
||||
</ha-button>
|
||||
<ha-progress-button
|
||||
unelevated
|
||||
@click=${this._handleLogin}
|
||||
.progress=${this._inProgress}
|
||||
>${this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.login.sign_in`
|
||||
)}</ha-progress-button
|
||||
>
|
||||
</div>
|
||||
</ha-card>
|
||||
|
||||
<ha-card outlined>
|
||||
<mwc-list>
|
||||
<ha-list-item @click=${this._handleRegister} twoline hasMeta>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.start_trial"
|
||||
)}
|
||||
<span slot="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.trial_info"
|
||||
)}
|
||||
</span>
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</mwc-list>
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -221,36 +130,100 @@ export class CloudLogin extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async _handleLogin() {
|
||||
const emailField = this._emailField;
|
||||
const passwordField = this._passwordField;
|
||||
|
||||
const email = emailField.value;
|
||||
const password = passwordField.value;
|
||||
|
||||
if (!emailField.reportValidity()) {
|
||||
passwordField.reportValidity();
|
||||
emailField.focus();
|
||||
return;
|
||||
private _handleCloudLoginError = async (
|
||||
err: any,
|
||||
email: string,
|
||||
password: string,
|
||||
checkConnection: boolean
|
||||
): Promise<"cancel" | "password-change" | string | undefined> => {
|
||||
const errCode = err && err.body && err.body.code;
|
||||
if (errCode === "mfarequired") {
|
||||
const totpCode = await showPromptDialog(this, {
|
||||
title: this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.login.totp_code_prompt_title`
|
||||
),
|
||||
inputLabel: this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.login.totp_code`
|
||||
),
|
||||
inputType: "text",
|
||||
defaultValue: "",
|
||||
confirmText: this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.login.submit`
|
||||
),
|
||||
dismissText: this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.login.cancel`
|
||||
),
|
||||
});
|
||||
if (totpCode !== null && totpCode !== "") {
|
||||
this._login(email, password, checkConnection, totpCode);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
if (errCode === "alreadyconnectederror") {
|
||||
const logInHere = await showCloudAlreadyConnectedDialog(this, {
|
||||
details: JSON.parse(err.body.message),
|
||||
});
|
||||
if (logInHere) {
|
||||
this._login(email, password, false);
|
||||
}
|
||||
|
||||
if (!passwordField.reportValidity()) {
|
||||
passwordField.focus();
|
||||
return;
|
||||
return logInHere ? undefined : "cancel";
|
||||
}
|
||||
if (errCode === "PasswordChangeRequired") {
|
||||
showAlertDialog(this, {
|
||||
title: this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.login.alert_password_change_required`
|
||||
),
|
||||
});
|
||||
return "password-change";
|
||||
}
|
||||
if (errCode === "usernotfound" && email !== email.toLowerCase()) {
|
||||
this._login(email.toLowerCase(), password, checkConnection);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
this._requestInProgress = true;
|
||||
switch (errCode) {
|
||||
case "UserNotConfirmed":
|
||||
return this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.login.alert_email_confirm_necessary`
|
||||
);
|
||||
case "mfarequired":
|
||||
return this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.login.alert_mfa_code_required`
|
||||
);
|
||||
case "mfaexpiredornotstarted":
|
||||
return this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.login.alert_mfa_expired_or_not_started`
|
||||
);
|
||||
case "invalidtotpcode":
|
||||
return this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.login.alert_totp_code_invalid`
|
||||
);
|
||||
default:
|
||||
return err && err.body && err.body.message
|
||||
? err.body.message
|
||||
: "Unknown error";
|
||||
}
|
||||
};
|
||||
|
||||
private _login = async (
|
||||
email: string,
|
||||
password: string,
|
||||
checkConnection: boolean,
|
||||
code?: string
|
||||
): Promise<undefined> => {
|
||||
if (!password && !code) {
|
||||
throw new Error("Password or code required");
|
||||
}
|
||||
|
||||
const doLogin = async (username: string, code?: string) => {
|
||||
try {
|
||||
if (this.hass) {
|
||||
const result = await cloudLogin({
|
||||
hass: this.hass,
|
||||
email: username,
|
||||
email,
|
||||
...(code ? { code } : { password }),
|
||||
check_connection: this._checkConnection,
|
||||
check_connection: checkConnection,
|
||||
});
|
||||
this.email = "";
|
||||
this._password = "";
|
||||
if (result.cloud_pipeline) {
|
||||
if (
|
||||
await showConfirmationDialog(this, {
|
||||
@ -265,180 +238,75 @@ export class CloudLogin extends LitElement {
|
||||
setAssistPipelinePreferred(this.hass, result.cloud_pipeline);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// for onboarding
|
||||
await loginHaCloud({
|
||||
email,
|
||||
...(code ? { code } : { password: password! }),
|
||||
});
|
||||
}
|
||||
this.email = "";
|
||||
fireEvent(this, "ha-refresh-cloud-status");
|
||||
} catch (err: any) {
|
||||
const errCode = err && err.body && err.body.code;
|
||||
if (errCode === "mfarequired") {
|
||||
const totpCode = await showPromptDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.cloud.login.totp_code_prompt_title"
|
||||
),
|
||||
inputLabel: this.hass.localize(
|
||||
"ui.panel.config.cloud.login.totp_code"
|
||||
),
|
||||
inputType: "text",
|
||||
defaultValue: "",
|
||||
confirmText: this.hass.localize(
|
||||
"ui.panel.config.cloud.login.submit"
|
||||
),
|
||||
});
|
||||
if (totpCode !== null && totpCode !== "") {
|
||||
await doLogin(username, totpCode);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (errCode === "alreadyconnectederror") {
|
||||
showCloudAlreadyConnectedDialog(this, {
|
||||
details: JSON.parse(err.body.message),
|
||||
logInHereAction: () => {
|
||||
this._checkConnection = false;
|
||||
doLogin(username);
|
||||
},
|
||||
closeDialog: () => {
|
||||
this._requestInProgress = false;
|
||||
const error = await this._handleCloudLoginError(
|
||||
err,
|
||||
email,
|
||||
password,
|
||||
checkConnection
|
||||
);
|
||||
|
||||
if (error === "cancel") {
|
||||
this._inProgress = false;
|
||||
this.email = "";
|
||||
this._password = "";
|
||||
},
|
||||
});
|
||||
this._passwordField.value = "";
|
||||
return;
|
||||
}
|
||||
if (errCode === "PasswordChangeRequired") {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.cloud.login.alert_password_change_required"
|
||||
),
|
||||
});
|
||||
navigate("/config/cloud/forgot-password");
|
||||
return;
|
||||
}
|
||||
if (errCode === "usernotfound" && username !== username.toLowerCase()) {
|
||||
await doLogin(username.toLowerCase());
|
||||
if (error === "password-change") {
|
||||
this._handleForgotPassword();
|
||||
return;
|
||||
}
|
||||
|
||||
this._password = "";
|
||||
this._requestInProgress = false;
|
||||
|
||||
switch (errCode) {
|
||||
case "UserNotConfirmed":
|
||||
this._error = this.hass.localize(
|
||||
"ui.panel.config.cloud.login.alert_email_confirm_necessary"
|
||||
);
|
||||
break;
|
||||
case "mfarequired":
|
||||
this._error = this.hass.localize(
|
||||
"ui.panel.config.cloud.login.alert_mfa_code_required"
|
||||
);
|
||||
break;
|
||||
case "mfaexpiredornotstarted":
|
||||
this._error = this.hass.localize(
|
||||
"ui.panel.config.cloud.login.alert_mfa_expired_or_not_started"
|
||||
);
|
||||
break;
|
||||
case "invalidtotpcode":
|
||||
this._error = this.hass.localize(
|
||||
"ui.panel.config.cloud.login.alert_totp_code_invalid"
|
||||
);
|
||||
break;
|
||||
default:
|
||||
this._error =
|
||||
err && err.body && err.body.message
|
||||
? err.body.message
|
||||
: "Unknown error";
|
||||
break;
|
||||
}
|
||||
|
||||
emailField.focus();
|
||||
this._inProgress = false;
|
||||
this._error = error;
|
||||
}
|
||||
};
|
||||
|
||||
await doLogin(email);
|
||||
private async _handleLogin() {
|
||||
if (!this._inProgress) {
|
||||
if (!this.emailField.reportValidity()) {
|
||||
this.emailField.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
private _handleRegister() {
|
||||
this._dismissFlash();
|
||||
// @ts-ignore
|
||||
fireEvent(this, "email-changed", { value: this._emailField.value });
|
||||
navigate("/config/cloud/register");
|
||||
if (!this._passwordField.reportValidity()) {
|
||||
this._passwordField.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
this._inProgress = true;
|
||||
|
||||
this._login(
|
||||
this.emailField.value,
|
||||
this._passwordField.value,
|
||||
this.checkConnection
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private _handleForgotPassword() {
|
||||
this._dismissFlash();
|
||||
// @ts-ignore
|
||||
fireEvent(this, "email-changed", { value: this._emailField.value });
|
||||
navigate("/config/cloud/forgot-password");
|
||||
}
|
||||
|
||||
private _dismissFlash() {
|
||||
// @ts-ignore
|
||||
fireEvent(this, "flash-message-changed", { value: "" });
|
||||
}
|
||||
|
||||
private _handleMenuAction(ev) {
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
this._deleteCloudData();
|
||||
break;
|
||||
case 1:
|
||||
this._downloadSupportPackage();
|
||||
}
|
||||
}
|
||||
|
||||
private async _deleteCloudData() {
|
||||
const confirm = await showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.cloud.account.reset_data_confirm_title"
|
||||
),
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.cloud.account.reset_data_confirm_text"
|
||||
),
|
||||
confirmText: this.hass.localize("ui.panel.config.cloud.account.reset"),
|
||||
destructive: true,
|
||||
});
|
||||
if (!confirm) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await removeCloudData(this.hass);
|
||||
} catch (err: any) {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.cloud.account.reset_data_failed"
|
||||
),
|
||||
text: err?.message,
|
||||
});
|
||||
return;
|
||||
} finally {
|
||||
fireEvent(this, "ha-refresh-cloud-status");
|
||||
}
|
||||
}
|
||||
|
||||
private async _downloadSupportPackage() {
|
||||
showSupportPackageDialog(this);
|
||||
fireEvent(this, "cloud-forgot-password");
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.content {
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
[slot="introduction"] {
|
||||
margin: -1em 0;
|
||||
}
|
||||
[slot="introduction"] a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
ha-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
ha-card .card-header {
|
||||
margin-bottom: -8px;
|
||||
}
|
||||
h1 {
|
||||
margin: 0;
|
||||
}
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@ -457,4 +325,14 @@ declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"cloud-login": CloudLogin;
|
||||
}
|
||||
|
||||
interface HASSDomEvents {
|
||||
"cloud-login": {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
"cloud-forgot-password": {
|
||||
email: string;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -141,7 +141,7 @@ export class CloudRegister extends LitElement {
|
||||
type="email"
|
||||
autocomplete="email"
|
||||
required
|
||||
.value=${this.email}
|
||||
.value=${this.email ?? ""}
|
||||
@keydown=${this._keyDown}
|
||||
validationMessage=${this.hass.localize(
|
||||
"ui.panel.config.cloud.register.email_error_msg"
|
||||
@ -260,9 +260,7 @@ export class CloudRegister extends LitElement {
|
||||
private _verificationEmailSent(email: string) {
|
||||
this._requestInProgress = false;
|
||||
this._password = "";
|
||||
// @ts-ignore
|
||||
fireEvent(this, "email-changed", { value: email });
|
||||
// @ts-ignore
|
||||
fireEvent(this, "cloud-email-changed", { value: email });
|
||||
fireEvent(this, "cloud-done", {
|
||||
flashMessage: this.hass.localize(
|
||||
"ui.panel.config.cloud.register.account_created"
|
||||
@ -304,4 +302,8 @@ declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"cloud-register": CloudRegister;
|
||||
}
|
||||
|
||||
interface HASSDomEvents {
|
||||
"cloud-done": { flashMessage: string };
|
||||
}
|
||||
}
|
||||
|
@ -4559,6 +4559,7 @@
|
||||
"password_error_msg": "Passwords are at least 8 characters",
|
||||
"totp_code_prompt_title": "Two-factor authentication",
|
||||
"totp_code": "TOTP code",
|
||||
"cancel": "Cancel",
|
||||
"submit": "Submit",
|
||||
"forgot_password": "Forgot password?",
|
||||
"start_trial": "Start your free 1 month trial",
|
||||
@ -8219,7 +8220,7 @@
|
||||
"welcome": {
|
||||
"header": "Welcome!",
|
||||
"start": "Create my smart home",
|
||||
"restore_backup": "Restore from backup",
|
||||
"or_restore": "Or restore",
|
||||
"vision": "Read our vision",
|
||||
"community": "Join our community",
|
||||
"download_app": "Download our app",
|
||||
@ -8252,7 +8253,7 @@
|
||||
},
|
||||
"core-config": {
|
||||
"location_header": "Home location",
|
||||
"intro_location": "Let's set up the location of your home so that you can display information such as the local weather and use sun-based or presence-based automations.",
|
||||
"intro_location": "This data is stored on your Home Assistant system, though some cloud-based integrations may use this data to function.",
|
||||
"location_address": "Powered by {openstreetmap} ({osm_privacy_policy}).",
|
||||
"osm_privacy_policy": "Privacy policy",
|
||||
"title_location_detect": "Do you want us to detect your location?",
|
||||
@ -8300,6 +8301,7 @@
|
||||
"restore": {
|
||||
"header": "Restore a backup",
|
||||
"upload_backup": "[%key:ui::panel::config::backup::dialogs::upload::title%]",
|
||||
"upload_backup_subtitle": "Upload a backup file from your device",
|
||||
"unsupported": {
|
||||
"title": "[%key:ui::panel::config::backup::dialogs::upload::unsupported::title%]",
|
||||
"text": "[%key:ui::panel::config::backup::dialogs::upload::unsupported::text%]"
|
||||
@ -8312,19 +8314,18 @@
|
||||
"uploading": "[%key:ui::components::file-upload::uploading%]",
|
||||
"details": {
|
||||
"home_assistant_missing": "This backup does not include your Home Assistant configuration, you cannot use it to restore your instance.",
|
||||
"addons_unsupported": "This backup includes add-ons and folders, which are not supported in this installation method of Home Assistant. You can still restore Home Assistant, but the unsupported files will not be restored.",
|
||||
"addons_unsupported": "Your installation method doesn’t support add-ons. If you wan’t to restore these, you have to install Home Assistant Operating System",
|
||||
"summary": {
|
||||
"title": "[%key:ui::panel::config::backup::details::summary::title%]",
|
||||
"size": "[%key:ui::panel::config::backup::details::summary::size%]",
|
||||
"created": "[%key:ui::panel::config::backup::details::summary::created%]",
|
||||
"upload_another": "Upload another",
|
||||
"home": "Home"
|
||||
"content": "Content"
|
||||
},
|
||||
"restore": {
|
||||
"title": "[%key:ui::panel::config::backup::details::restore::title%]",
|
||||
"action": "[%key:ui::panel::config::backup::details::restore::action%]",
|
||||
"title": "[%key:ui::panel::config::backup::dialogs::restore::title%]",
|
||||
"action": "Restore backup",
|
||||
"encryption": {
|
||||
"title": "[%key:ui::panel::config::backup::dialogs::restore::encryption::different_key%]",
|
||||
"label": "Encryption",
|
||||
"description": "[%key:ui::panel::config::backup::dialogs::restore::encryption::different_key%]",
|
||||
"description_cloud": "Home Assistant Cloud is the privacy-focused cloud. We don’t store your encryption key. Enter the encryption key you saved or use the emergency kit you have downloaded.",
|
||||
"incorrect_key": "[%key:ui::panel::config::backup::dialogs::restore::encryption::incorrect_key%]",
|
||||
"input_label": "[%key:ui::panel::config::backup::dialogs::restore::encryption::input_label%]"
|
||||
}
|
||||
@ -8362,7 +8363,7 @@
|
||||
"confirm_restore_partial_backup_title": "[%key:supervisor::backup::confirm_restore_partial_backup_title%]",
|
||||
"confirm_restore_partial_backup_text": "The backup will be restored. Depending on the size of the backup, this can take up to 45 min. Home Assistant needs to shutdown and the restore progress is running in the background. If it succeeds, Home Assistant will automatically start again and you see the login screen. If it fails it will bring you back to the onboarding.",
|
||||
"confirm_restore_full_backup_title": "[%key:supervisor::backup::confirm_restore_full_backup_title%]",
|
||||
"confirm_restore_full_backup_text": "Your entire system will be wiped and the backup will be restored. Depending on the size of the backup, this can take up to 45 min. Home Assistant needs to shutdown and the restore progress is running in the background. If it succeeds, Home Assistant will automatically start again and you see the login screen. If it fails it will bring you back to the onboarding.",
|
||||
"confirm_restore_full_backup_text": "Depending on the size of the backup, this can take up to 45 minutes. Home Assistant will restart and you will see the login screen when it’s restored.",
|
||||
"restore": "[%key:supervisor::backup::restore%]",
|
||||
"close": "[%key:ui::common::close%]",
|
||||
"cancel": "[%key:ui::common::cancel%]",
|
||||
@ -8376,6 +8377,50 @@
|
||||
"text": "Are you sure you want to cancel the restore process and return to the onboarding?",
|
||||
"yes": "[%key:ui::common::yes%]",
|
||||
"no": "[%key:ui::common::no%]"
|
||||
},
|
||||
"ha-cloud": {
|
||||
"description": "Restore from your Home Assistant Cloud backup.",
|
||||
"no_cloud_backup": "No backup available",
|
||||
"no_cloud_backup_description": "This Home Assistant Cloud account doesn’t have a backup stored. You can learn more how Cloud backups work at the Nabu Casa website.",
|
||||
"sign_out": "[%key:ui::panel::config::cloud::account::sign_out%]",
|
||||
"sign_out_progress": "Home Assistant cloud signing out...",
|
||||
"sign_out_success": "Home Assistant cloud signed out",
|
||||
"sign_out_error": "Failed to sign out from Home Assistant cloud",
|
||||
"learn_more": "Learn more",
|
||||
"sign_in_description": "Sign in to your Nabu Casa account and restore your Home Assistant Cloud backup.",
|
||||
"login": {
|
||||
"title": "[%key:ui::panel::config::cloud::login::title%]",
|
||||
"sign_in": "[%key:ui::panel::config::cloud::login::sign_in%]",
|
||||
"email": "[%key:ui::panel::config::cloud::login::email%]",
|
||||
"email_error_msg": "[%key:ui::panel::config::cloud::login::email_error_msg%]",
|
||||
"password": "[%key:ui::panel::config::cloud::login::password%]",
|
||||
"password_error_msg": "[%key:ui::panel::config::cloud::login::password_error_msg%]",
|
||||
"totp_code_prompt_title": "[%key:ui::panel::config::cloud::login::totp_code_prompt_title%]",
|
||||
"totp_code": "[%key:ui::panel::config::cloud::login::totp_code%]",
|
||||
"cancel": "[%key:ui::panel::config::cloud::login::cancel%]",
|
||||
"submit": "[%key:ui::panel::config::cloud::login::submit%]",
|
||||
"forgot_password": "[%key:ui::panel::config::cloud::login::forgot_password%]",
|
||||
"start_trial": "[%key:ui::panel::config::cloud::login::start_trial%]",
|
||||
"trial_info": "[%key:ui::panel::config::cloud::login::trial_info%]",
|
||||
"alert_password_change_required": "[%key:ui::panel::config::cloud::login::alert_password_change_required%]",
|
||||
"alert_email_confirm_necessary": "[%key:ui::panel::config::cloud::login::alert_email_confirm_necessary%]",
|
||||
"alert_mfa_code_required": "[%key:ui::panel::config::cloud::login::alert_mfa_code_required%]",
|
||||
"alert_mfa_expired_or_not_started": "[%key:ui::panel::config::cloud::login::alert_mfa_expired_or_not_started%]",
|
||||
"alert_totp_code_invalid": "[%key:ui::panel::config::cloud::login::alert_totp_code_invalid%]"
|
||||
},
|
||||
"forgot_password": {
|
||||
"title": "[%key:ui::panel::config::cloud::forgot_password::title%]",
|
||||
"subtitle": "[%key:ui::panel::config::cloud::forgot_password::subtitle%]",
|
||||
"instructions": "[%key:ui::panel::config::cloud::forgot_password::instructions%]",
|
||||
"email": "[%key:ui::panel::config::cloud::forgot_password::email%]",
|
||||
"email_error_msg": "[%key:ui::panel::config::cloud::forgot_password::email_error_msg%]",
|
||||
"send_reset_email": "[%key:ui::panel::config::cloud::forgot_password::send_reset_email%]",
|
||||
"check_your_email": "[%key:ui::panel::config::cloud::forgot_password::check_your_email%]"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"title": "How to restore?",
|
||||
"upload_description": "Upload and restore a Home Assistant backup to this system"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user