Add connection check and dialog with results for cloud login (#24301)

This commit is contained in:
Joakim Sørensen 2025-02-24 09:37:17 +01:00 committed by GitHub
parent 783132ae46
commit a438fc5e41
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 247 additions and 0 deletions

View File

@ -73,6 +73,7 @@ export interface CloudWebhook {
interface CloudLoginBase {
hass: HomeAssistant;
email: string;
check_connection?: boolean;
}
export interface CloudLoginPassword extends CloudLoginBase {

View File

@ -10,6 +10,7 @@ import "../../../components/ha-svg-icon";
import "../../../components/ha-textfield";
import type { HaTextField } from "../../../components/ha-textfield";
import { cloudLogin } from "../../../data/cloud";
import { showCloudAlreadyConnectedDialog } from "../../../panels/config/cloud/dialog-cloud-already-connected/show-dialog-cloud-already-connected";
import type { HomeAssistant } from "../../../types";
import {
showAlertDialog,
@ -25,6 +26,8 @@ export class CloudStepSignin extends LitElement {
@state() private _error?: string;
@state() private _checkConnection = true;
@query("#email", true) private _emailField!: HaTextField;
@query("#password", true) private _passwordField!: HaPasswordField;
@ -115,6 +118,7 @@ export class CloudStepSignin extends LitElement {
hass: this.hass,
email: username,
...(code ? { code } : { password }),
check_connection: this._checkConnection,
});
} catch (err: any) {
const errCode = err && err.body && err.body.code;
@ -139,6 +143,20 @@ export class CloudStepSignin extends LitElement {
}
}
if (errCode === "alreadyconnectederror") {
showCloudAlreadyConnectedDialog(this, {
details: JSON.parse(err.body.message),
logInHereAction: () => {
this._checkConnection = false;
doLogin(username);
},
closeDialog: () => {
this._requestInProgress = false;
},
});
return;
}
if (errCode === "usernotfound" && username !== username.toLowerCase()) {
await doLogin(username.toLowerCase());
return;

View File

@ -0,0 +1,171 @@
import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, state } from "lit/decorators";
import { mdiEye, mdiEyeOff } from "@mdi/js";
import { formatDateTime } from "../../../../common/datetime/format_date_time";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-alert";
import "../../../../components/ha-button";
import "../../../../components/ha-icon-button";
import { createCloseHeading } from "../../../../components/ha-dialog";
import { haStyleDialog } from "../../../../resources/styles";
import type { HomeAssistant } from "../../../../types";
import type { CloudAlreadyConnectedParams as CloudAlreadyConnectedDialogParams } from "./show-dialog-cloud-already-connected";
import { obfuscateUrl } from "../../../../util/url";
@customElement("dialog-cloud-already-connected")
class DialogCloudAlreadyConnected extends LitElement {
public hass!: HomeAssistant;
@state() private _params?: CloudAlreadyConnectedDialogParams;
@state() private _obfuscateIp = true;
public showDialog(params: CloudAlreadyConnectedDialogParams) {
this._params = params;
}
public closeDialog() {
this._params?.closeDialog();
this._params = undefined;
this._obfuscateIp = true;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render() {
if (!this._params) {
return nothing;
}
const { details } = this._params;
return html`
<ha-dialog
open
@closed=${this.closeDialog}
.heading=${createCloseHeading(
this.hass,
this.hass.localize(
"ui.panel.config.cloud.dialog_already_connected.heading"
)
)}
>
<div class="intro">
<span>
${this.hass.localize(
"ui.panel.config.cloud.dialog_already_connected.description"
)}
</span>
<b>
${this.hass.localize(
"ui.panel.config.cloud.dialog_already_connected.other_home_assistant"
)}
</b>
</div>
<div class="instance-details">
<div class="instance-detail">
<span>
${this.hass.localize(
"ui.panel.config.cloud.dialog_already_connected.ip_address"
)}:
</span>
<div class="obfuscated">
<span>
${this._obfuscateIp
? obfuscateUrl(details.remote_ip_address)
: details.remote_ip_address}
</span>
<ha-icon-button
class="toggle-unmasked-url"
.label=${this.hass.localize(
`ui.panel.config.cloud.dialog_already_connected.obfuscated_ip.${this._obfuscateIp ? "hide" : "show"}`
)}
@click=${this._toggleObfuscateIp}
.path=${this._obfuscateIp ? mdiEye : mdiEyeOff}
></ha-icon-button>
</div>
</div>
<div class="instance-detail">
<span>
${this.hass.localize(
"ui.panel.config.cloud.dialog_already_connected.connected_at"
)}:
</span>
<span>
${formatDateTime(
new Date(details.connected_at),
this.hass.locale,
this.hass.config
)}
</span>
</div>
</div>
<ha-alert
alert-type="info"
.title=${this.hass.localize(
"ui.panel.config.cloud.dialog_already_connected.info_backups.title"
)}
>
${this.hass.localize(
"ui.panel.config.cloud.dialog_already_connected.info_backups.description"
)}
</ha-alert>
<ha-button @click=${this.closeDialog} slot="secondaryAction">
${this.hass!.localize("ui.common.cancel")}
</ha-button>
<ha-button @click=${this._logInHere} slot="primaryAction">
${this.hass!.localize(
"ui.panel.config.cloud.dialog_already_connected.login_here"
)}
</ha-button>
</ha-dialog>
`;
}
private _toggleObfuscateIp() {
this._obfuscateIp = !this._obfuscateIp;
}
private _logInHere() {
this._params?.logInHereAction();
this.closeDialog();
}
static get styles(): CSSResultGroup {
return [
haStyleDialog,
css`
ha-dialog {
--mdc-dialog-max-width: 535px;
}
.intro b {
display: block;
margin-top: 16px;
}
.instance-details {
display: flex;
flex-direction: column;
margin-bottom: 16px;
}
.instance-detail {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.obfuscated {
align-items: center;
display: flex;
flex-direction: row;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-cloud-already-connected": DialogCloudAlreadyConnected;
}
}

View File

@ -0,0 +1,21 @@
import { fireEvent } from "../../../../common/dom/fire_event";
export interface CloudAlreadyConnectedParams {
details: {
remote_ip_address: string;
connected_at: string;
};
logInHereAction: () => void;
closeDialog: () => void;
}
export const showCloudAlreadyConnectedDialog = (
element: HTMLElement,
webhookDialogParams: CloudAlreadyConnectedParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-cloud-already-connected",
dialogImport: () => import("./dialog-cloud-already-connected"),
dialogParams: webhookDialogParams,
});
};

View File

@ -28,6 +28,7 @@ import { haStyle } from "../../../../resources/styles";
import type { HomeAssistant } from "../../../../types";
import "../../ha-config-section";
import { showSupportPackageDialog } from "../account/show-dialog-cloud-support-package";
import { showCloudAlreadyConnectedDialog } from "../dialog-cloud-already-connected/show-dialog-cloud-already-connected";
@customElement("cloud-login")
export class CloudLogin extends LitElement {
@ -47,6 +48,8 @@ export class CloudLogin extends LitElement {
@state() private _error?: string;
@state() private _checkConnection = true;
@query("#email", true) private _emailField!: HaTextField;
@query("#password", true) private _passwordField!: HaPasswordField;
@ -244,6 +247,7 @@ export class CloudLogin extends LitElement {
hass: this.hass,
email: username,
...(code ? { code } : { password }),
check_connection: this._checkConnection,
});
this.email = "";
this._password = "";
@ -283,6 +287,21 @@ export class CloudLogin extends LitElement {
return;
}
}
if (errCode === "alreadyconnectederror") {
showCloudAlreadyConnectedDialog(this, {
details: JSON.parse(err.body.message),
logInHereAction: () => {
this._checkConnection = false;
doLogin(username);
},
closeDialog: () => {
this._requestInProgress = false;
this.email = "";
this._password = "";
},
});
return;
}
if (errCode === "PasswordChangeRequired") {
showAlertDialog(this, {
title: this.hass.localize(

View File

@ -4730,6 +4730,23 @@
"fingerprint": "Certificate fingerprint:",
"close": "Close"
},
"dialog_already_connected": {
"heading": "Account linked to other Home Assistant",
"description": "We noticed that another instance is currently connected to your Home Assistant Cloud account. Your Home Assistant Cloud account can only be signed into one Home Assistant instance at a time. If you log in here, the other instance will be disconnected along with its Cloud services.",
"other_home_assistant": "Other Home Assistant",
"ip_address": "IP Address",
"connected_at": "Connected at",
"obfuscated_ip": {
"show": "Show IP address",
"hide": "Hide IP address"
},
"info_backups": {
"title": "Home Assistant Cloud backups",
"description": "Your Cloud backup may be overwritten if you proceed. We strongly recommend downloading your current backup from your Nabu Casa account page before continuing."
},
"close": "Close",
"login_here": "Log in here"
},
"dialog_cloudhook": {
"webhook_for": "Webhook for {name}",
"managed_by_integration": "This webhook is managed by an integration and cannot be disabled.",