ha-frontend/src/panels/config/cloud/account/cloud-remote-pref.ts

491 lines
15 KiB
TypeScript

import { mdiContentCopy, mdiEye, mdiEyeOff, mdiHelpCircle } from "@mdi/js";
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import { copyToClipboard } from "../../../../common/util/copy-clipboard";
import "../../../../components/ha-alert";
import "../../../../components/ha-button";
import "../../../../components/ha-card";
import "../../../../components/ha-expansion-panel";
import "../../../../components/ha-settings-row";
import "../../../../components/ha-switch";
// eslint-disable-next-line
import { formatDate } from "../../../../common/datetime/format_date";
import type { HaSwitch } from "../../../../components/ha-switch";
import {
CloudStatusLoggedIn,
connectCloudRemote,
disconnectCloudRemote,
updateCloudPref,
} from "../../../../data/cloud";
import type { HomeAssistant } from "../../../../types";
import { showToast } from "../../../../util/toast";
import { showCloudCertificateDialog } from "../dialog-cloud-certificate/show-dialog-cloud-certificate";
import { showAlertDialog } from "../../../lovelace/custom-card-helpers";
@customElement("cloud-remote-pref")
export class CloudRemotePref extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public cloudStatus?: CloudStatusLoggedIn;
@state() private _unmaskedUrl = false;
protected render() {
if (!this.cloudStatus) {
return nothing;
}
const { remote_enabled, remote_allow_remote_enable, strict_connection } =
this.cloudStatus.prefs;
const {
remote_connected,
remote_domain,
remote_certificate,
remote_certificate_status,
} = this.cloudStatus;
if (!remote_certificate || remote_certificate_status !== "ready") {
return html`
<ha-card
outlined
header=${this.hass.localize(
"ui.panel.config.cloud.account.remote.title"
)}
>
<div class="preparing">
${remote_certificate_status === "error"
? this.hass.localize(
"ui.panel.config.cloud.account.remote.cerificate_error"
)
: remote_certificate_status === "loading"
? this.hass.localize(
"ui.panel.config.cloud.account.remote.cerificate_loading"
)
: remote_certificate_status === "loaded"
? this.hass.localize(
"ui.panel.config.cloud.account.remote.cerificate_loaded"
)
: this.hass.localize(
"ui.panel.config.cloud.account.remote.access_is_being_prepared"
)}
</div>
</ha-card>
`;
}
return html`
<ha-card
outlined
header=${this.hass.localize(
"ui.panel.config.cloud.account.remote.title"
)}
>
<div class="header-actions">
<a
href="https://www.nabucasa.com/config/remote/"
target="_blank"
rel="noreferrer"
class="icon-link"
>
<ha-icon-button
.label=${this.hass.localize(
"ui.panel.config.cloud.account.remote.link_learn_how_it_works"
)}
.path=${mdiHelpCircle}
></ha-icon-button>
</a>
<ha-switch
.checked=${remote_enabled}
@change=${this._toggleChanged}
></ha-switch>
</div>
<div class="card-content">
${!remote_connected && remote_enabled
? html`
<ha-alert
.title=${this.hass.localize(
`ui.panel.config.cloud.account.remote.reconnecting`
)}
></ha-alert>
`
: strict_connection === "drop_connection"
? html`<ha-alert
alert-type="warning"
.title=${this.hass.localize(
`ui.panel.config.cloud.account.remote.drop_connection_warning_title`
)}
>${this.hass.localize(
`ui.panel.config.cloud.account.remote.drop_connection_warning`
)}</ha-alert
>`
: nothing}
${this.hass.localize("ui.panel.config.cloud.account.remote.info")}
<br />
${this.hass.localize(
`ui.panel.config.cloud.account.remote.${
remote_connected
? "instance_is_available"
: "instance_will_be_available"
}`
)}
${this.hass.localize(
"ui.panel.config.cloud.account.remote.nabu_casa_url"
)}.
<div class="url-container">
<div class="textfield-container">
<ha-textfield
.value=${this._unmaskedUrl
? `https://${remote_domain}`
: "https://•••••••••••••••••.ui.nabu.casa"}
readonly
.suffix=${
// reserve some space for the icon.
html`<div style="width: 24px"></div>`
}
></ha-textfield>
<ha-icon-button
class="toggle-unmasked-url"
toggles
.label=${this.hass.localize(
`ui.panel.config.cloud.account.remote.${this._unmaskedUrl ? "hide" : "show"}_url`
)}
@click=${this._toggleUnmaskedUrl}
.path=${this._unmaskedUrl ? mdiEyeOff : mdiEye}
></ha-icon-button>
</div>
<ha-button
.url=${`https://${remote_domain}`}
@click=${this._copyURL}
unelevated
>
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.cloud.account.remote.copy_link"
)}
</ha-button>
</div>
<ha-settings-row>
<span slot="heading"
>${this.hass.localize(
"ui.panel.config.cloud.account.remote.strict_connection"
)}</span
>
<span slot="description"
>${this.hass.localize(
"ui.panel.config.cloud.account.remote.strict_connection_secondary"
)}</span
>
<ha-switch
.checked=${strict_connection === "disabled"}
@change=${this._strictConnectionToggleChanged}
></ha-switch>
</ha-settings-row>
${strict_connection !== "disabled"
? html`<ha-settings-row>
<span slot="heading"
>${this.hass.localize(
"ui.panel.config.cloud.account.remote.strict_connection_link"
)}</span
>
<span slot="description"
>${this.hass.localize(
"ui.panel.config.cloud.account.remote.strict_connection_link_secondary"
)}</span
>
<ha-button @click=${this._createLoginUrl}
>${this.hass.localize(
"ui.panel.config.cloud.account.remote.strict_connection_create_link"
)}</ha-button
>
</ha-settings-row>`
: nothing}
<ha-expansion-panel
outlined
.header=${this.hass.localize(
"ui.panel.config.cloud.account.remote.advanced_options"
)}
>
${strict_connection !== "disabled"
? html` <ha-settings-row>
<span slot="heading"
>${this.hass.localize(
"ui.panel.config.cloud.account.remote.guard_page"
)}</span
>
<span slot="description"
>${this.hass.localize(
"ui.panel.config.cloud.account.remote.guard_page_secondary"
)}</span
>
<ha-switch
.checked=${strict_connection === "guard_page"}
@change=${this._dropConnectionToggleChanged}
></ha-switch>
</ha-settings-row>`
: nothing}
<ha-settings-row>
<span slot="heading"
>${this.hass.localize(
"ui.panel.config.cloud.account.remote.external_activation"
)}</span
>
<span slot="description"
>${this.hass.localize(
"ui.panel.config.cloud.account.remote.external_activation_secondary"
)}</span
>
<ha-switch
.checked=${remote_allow_remote_enable}
@change=${this._toggleAllowRemoteEnabledChanged}
></ha-switch>
</ha-settings-row>
<ha-settings-row>
<span slot="heading"
>${this.hass.localize(
"ui.panel.config.cloud.account.remote.certificate_info"
)}</span
>
<span slot="description"
>${this.cloudStatus!.remote_certificate
? this.hass.localize(
"ui.panel.config.cloud.account.remote.certificate_expire",
{
date: formatDate(
new Date(
this.cloudStatus.remote_certificate.expire_date
),
this.hass.locale,
this.hass.config
),
}
)
: nothing}</span
>
<ha-button @click=${this._openCertInfo}>
${this.hass.localize(
"ui.panel.config.cloud.account.remote.more_info"
)}
</ha-button>
</ha-settings-row>
</ha-expansion-panel>
</div>
</ha-card>
`;
}
private _openCertInfo() {
showCloudCertificateDialog(this, {
certificateInfo: this.cloudStatus!.remote_certificate!,
});
}
private _toggleUnmaskedUrl(): void {
this._unmaskedUrl = !this._unmaskedUrl;
}
private async _toggleChanged(ev) {
const toggle = ev.target as HaSwitch;
try {
if (toggle.checked) {
await connectCloudRemote(this.hass);
} else {
await disconnectCloudRemote(this.hass);
}
fireEvent(this, "ha-refresh-cloud-status");
} catch (err: any) {
alert(err.message);
toggle.checked = !toggle.checked;
}
}
private async _toggleAllowRemoteEnabledChanged(ev) {
const toggle = ev.target as HaSwitch;
try {
await updateCloudPref(this.hass, {
remote_allow_remote_enable: toggle.checked,
});
fireEvent(this, "ha-refresh-cloud-status");
} catch (err: any) {
alert(err.message);
toggle.checked = !toggle.checked;
}
}
private async _strictConnectionToggleChanged(ev) {
const toggle = ev.target as HaSwitch;
try {
await updateCloudPref(this.hass, {
strict_connection: toggle.checked ? "disabled" : "guard_page",
});
fireEvent(this, "ha-refresh-cloud-status");
} catch (err: any) {
alert(err.message);
toggle.checked = !toggle.checked;
}
}
private async _dropConnectionToggleChanged(ev) {
const toggle = ev.target as HaSwitch;
try {
await updateCloudPref(this.hass, {
strict_connection: toggle.checked ? "guard_page" : "drop_connection",
});
fireEvent(this, "ha-refresh-cloud-status");
} catch (err: any) {
alert(err.message);
toggle.checked = !toggle.checked;
}
}
private async _copyURL(ev): Promise<void> {
const url = ev.currentTarget.url;
await copyToClipboard(url);
showToast(this, {
message: this.hass.localize("ui.common.copied_clipboard"),
});
}
private async _createLoginUrl() {
try {
const result = await this.hass.callService(
"cloud",
"create_temporary_strict_connection_url",
undefined,
undefined,
false,
true
);
showAlertDialog(this, {
title: this.hass.localize(
"ui.panel.config.cloud.account.remote.strict_connection_link"
),
text: html`${this.hass.localize(
"ui.panel.config.cloud.account.remote.strict_connection_link_created_message"
)}
<div
style="display: flex; align-items: center; gap: 8px; margin-top: 8px;"
>
<ha-textfield .value=${result.response.url} readonly></ha-textfield>
<ha-button
style="flex-basis: 180px;"
.url=${result.response.url}
@click=${this._copyURL}
unelevated
>
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.cloud.account.remote.copy_link"
)}
</ha-button>
</div>`,
});
} catch (err: any) {
showAlertDialog(this, { text: err.message });
}
}
static get styles(): CSSResultGroup {
return css`
.preparing {
padding: 0 16px 16px;
}
a {
color: var(--primary-color);
}
.header-actions {
position: absolute;
right: 16px;
inset-inline-end: 16px;
inset-inline-start: initial;
top: 24px;
display: flex;
flex-direction: row;
}
.header-actions .icon-link {
margin-top: -16px;
margin-right: 8px;
margin-inline-end: 8px;
margin-inline-start: initial;
direction: var(--direction);
color: var(--secondary-text-color);
}
.warning {
font-weight: bold;
margin-bottom: 1em;
}
.break-word {
overflow-wrap: break-word;
}
.connection-status {
position: absolute;
right: 24px;
top: 24px;
inset-inline-end: 24px;
inset-inline-start: initial;
}
.card-actions {
display: flex;
}
.card-actions a {
text-decoration: none;
}
ha-expansion-panel {
margin-top: 8px;
}
ha-settings-row {
padding: 0;
}
ha-expansion-panel {
--expansion-panel-content-padding: 0 16px;
--expansion-panel-summary-padding: 0 16px;
}
ha-alert {
display: block;
margin-bottom: 16px;
}
.url-container {
display: flex;
align-items: center;
gap: 8px;
margin-top: 8px;
}
.textfield-container {
position: relative;
flex: 1;
}
.textfield-container ha-textfield {
display: block;
}
.toggle-unmasked-url {
position: absolute;
top: 8px;
right: 8px;
inset-inline-start: initial;
inset-inline-end: 8px;
--mdc-icon-button-size: 40px;
--mdc-icon-size: 20px;
color: var(--secondary-text-color);
direction: var(--direction);
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"cloud-remote-pref": CloudRemotePref;
}
}