Revamp URL form (#12060)

This commit is contained in:
Paulus Schoutsen 2022-03-16 14:14:25 -07:00 committed by GitHub
parent ab5df0fe6e
commit 1e929ae78a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 240 additions and 41 deletions

View File

@ -0,0 +1,4 @@
const regexp =
/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
export const isIPAddress = (input: string): boolean => regexp.test(input);

View File

@ -6,6 +6,7 @@ import { AutomationConfig } from "./automation";
interface CloudStatusNotLoggedIn {
logged_in: false;
cloud: "disconnected" | "connecting" | "connected";
http_use_ssl: boolean;
}
export interface GoogleEntityConfig {
@ -59,6 +60,7 @@ export interface CloudStatusLoggedIn {
remote_connected: boolean;
remote_certificate: undefined | CertificateInformation;
http_use_ssl: boolean;
active_subscription: boolean;
}
export type CloudStatus = CloudStatusNotLoggedIn | CloudStatusLoggedIn;

View File

@ -1,12 +1,25 @@
import "@material/mwc-button/mwc-button";
import "@polymer/paper-input/paper-input";
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import "../../../components/ha-card";
import "../../../components/ha-switch";
import "../../../components/ha-alert";
import "../../../components/ha-formfield";
import "../../../components/ha-textfield";
import type { HaTextField } from "../../../components/ha-textfield";
import { CloudStatus, fetchCloudStatus } from "../../../data/cloud";
import { saveCoreConfig } from "../../../data/core";
import type { PolymerChangedEvent } from "../../../polymer-types";
import type { HomeAssistant } from "../../../types";
import { isIPAddress } from "../../../common/string/is_ip_address";
@customElement("ha-config-url-form")
class ConfigUrlForm extends LitElement {
@ -20,18 +33,48 @@ class ConfigUrlForm extends LitElement {
@state() private _internal_url?: string;
@state() private _cloudStatus?: CloudStatus | null;
@state() private _showCustomExternalUrl = false;
@state() private _showCustomInternalUrl = false;
protected render(): TemplateResult {
const canEdit = ["storage", "default"].includes(
this.hass.config.config_source
);
const disabled = this._working || !canEdit;
if (!this.hass.userData?.showAdvanced) {
if (!this.hass.userData?.showAdvanced || this._cloudStatus === undefined) {
return html``;
}
const internalUrl = this._internalUrlValue;
const externalUrl = this._externalUrlValue;
let hasCloud: boolean;
let remoteEnabled: boolean;
let httpUseHttps: boolean;
if (this._cloudStatus === null) {
hasCloud = false;
remoteEnabled = false;
httpUseHttps = false;
} else {
httpUseHttps = this._cloudStatus.http_use_ssl;
if (this._cloudStatus.logged_in) {
hasCloud = true;
remoteEnabled =
this._cloudStatus.active_subscription &&
this._cloudStatus.prefs.remote_enabled;
} else {
hasCloud = false;
remoteEnabled = false;
}
}
return html`
<ha-card>
<ha-card .header=${this.hass.localize("ui.panel.config.url.caption")}>
<div class="card-content">
${!canEdit
? html`
@ -43,46 +86,147 @@ class ConfigUrlForm extends LitElement {
`
: ""}
${this._error ? html`<div class="error">${this._error}</div>` : ""}
<div class="row">
<div class="flex">
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.external_url"
)}
</div>
<paper-input
class="flex"
.label=${this.hass.localize(
"ui.panel.config.core.section.core.core_config.external_url"
)}
name="external_url"
type="url"
.disabled=${disabled}
.value=${this._externalUrlValue}
@value-changed=${this._handleChange}
>
</paper-input>
<div class="description">
${this.hass.localize("ui.panel.config.url.description")}
</div>
${hasCloud
? html`
<div class="row">
<div class="flex">
${this.hass.localize(
"ui.panel.config.url.external_url_label"
)}
</div>
<ha-formfield
.label=${this.hass.localize(
"ui.panel.config.url.external_use_ha_cloud"
)}
>
<ha-switch
.disabled=${disabled}
.checked=${externalUrl === null}
@change=${this._toggleCloud}
></ha-switch>
</ha-formfield>
</div>
`
: ""}
${!this._showCustomExternalUrl
? ""
: html`
<div class="row">
<div class="flex">
${hasCloud
? ""
: this.hass.localize(
"ui.panel.config.url.external_url_label"
)}
</div>
<ha-textfield
class="flex"
name="external_url"
type="url"
.disabled=${disabled}
.value=${externalUrl || ""}
@change=${this._handleChange}
placeholder="https://example.duckdns.org:8123"
>
</ha-textfield>
</div>
`}
${hasCloud || !isComponentLoaded(this.hass, "cloud")
? ""
: html`
<div class="row">
<div class="flex"></div>
<a href="/config/cloud"
>${this.hass.localize(
"ui.panel.config.url.external_get_ha_cloud"
)}</a
>
</div>
`}
${!this._showCustomExternalUrl && hasCloud
? html`
${remoteEnabled
? ""
: html`
<ha-alert alert-type="error">
${this.hass.localize(
"ui.panel.config.url.ha_cloud_remote_not_enabled"
)}
<a href="/config/cloud" slot="action"
><mwc-button
.label=${this.hass.localize(
"ui.panel.config.url.enable_remote"
)}
></mwc-button
></a>
</ha-alert>
`}
`
: ""}
<div class="row">
<div class="flex">
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.internal_url"
)}
${this.hass.localize("ui.panel.config.url.internal_url_label")}
</div>
<paper-input
class="flex"
<ha-formfield
.label=${this.hass.localize(
"ui.panel.config.core.section.core.core_config.internal_url"
"ui.panel.config.url.internal_url_automatic"
)}
name="internal_url"
type="url"
.disabled=${disabled}
.value=${this._internalUrlValue}
@value-changed=${this._handleChange}
>
</paper-input>
<ha-switch
.checked=${internalUrl === null}
@change=${this._toggleInternalAutomatic}
></ha-switch>
</ha-formfield>
</div>
${!this._showCustomInternalUrl
? ""
: html`
<div class="row">
<div class="flex"></div>
<ha-textfield
class="flex"
name="internal_url"
type="url"
placeholder="http://<some IP address>:8123"
.disabled=${disabled}
.value=${internalUrl || ""}
@change=${this._handleChange}
>
</ha-textfield>
</div>
`}
${
// If the user has configured a cert, show an error if
httpUseHttps && // there is no internal url configured
(!internalUrl ||
// the internal url does not start with https
!internalUrl.startsWith("https://") ||
// the internal url points at an IP address
isIPAddress(new URL(internalUrl).hostname))
? html`
<ha-alert
.alertType=${this._showCustomInternalUrl
? "info"
: "warning"}
.title=${this.hass.localize(
"ui.panel.config.url.intenral_url_https_error_title"
)}
>
${this.hass.localize(
"ui.panel.config.url.internal_url_https_error_description"
)}
</ha-alert>
`
: ""
}
</div>
<div class="card-actions">
<mwc-button @click=${this._save} .disabled=${disabled}>
@ -95,6 +239,24 @@ class ConfigUrlForm extends LitElement {
`;
}
protected override firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this._showCustomInternalUrl = this._internalUrlValue !== null;
if (isComponentLoaded(this.hass, "cloud")) {
fetchCloudStatus(this.hass).then((cloudStatus) => {
if (cloudStatus.logged_in) {
this._cloudStatus = cloudStatus;
this._showCustomExternalUrl = this._externalUrlValue !== null;
}
});
} else {
this._cloudStatus = null;
this._showCustomExternalUrl = true;
}
}
private get _internalUrlValue() {
return this._internal_url !== undefined
? this._internal_url
@ -107,9 +269,17 @@ class ConfigUrlForm extends LitElement {
: this.hass.config.external_url;
}
private _toggleCloud(ev) {
this._showCustomExternalUrl = !ev.currentTarget.checked;
}
private _toggleInternalAutomatic(ev) {
this._showCustomInternalUrl = !ev.currentTarget.checked;
}
private _handleChange(ev: PolymerChangedEvent<string>) {
const target = ev.currentTarget as PaperInputElement;
this[`_${target.name}`] = target.value;
const target = ev.currentTarget as HaTextField;
this[`_${target.name}`] = target.value || null;
}
private async _save() {
@ -117,8 +287,12 @@ class ConfigUrlForm extends LitElement {
this._error = undefined;
try {
await saveCoreConfig(this.hass, {
external_url: this._external_url || null,
internal_url: this._internal_url || null,
external_url: this._showCustomExternalUrl
? this._external_url || null
: null,
internal_url: this._showCustomInternalUrl
? this._internal_url || null
: null,
});
} catch (err: any) {
this._error = err.message || err;
@ -129,11 +303,15 @@ class ConfigUrlForm extends LitElement {
static get styles(): CSSResultGroup {
return css`
.description {
margin-bottom: 1em;
}
.row {
display: flex;
flex-direction: row;
margin: 0 -8px;
align-items: center;
padding: 8px 0;
}
.secondary {
@ -154,6 +332,10 @@ class ConfigUrlForm extends LitElement {
.card-actions {
text-align: right;
}
a {
color: var(--primary-color);
}
`;
}
}

View File

@ -1355,13 +1355,24 @@
"metric_example": "Celsius, kilograms",
"find_currency_value": "Find your value",
"save_button": "Save",
"external_url": "External URL",
"internal_url": "Internal URL",
"currency": "Currency"
}
}
}
},
"url": {
"caption": "Home Assistant URL",
"description": "Configure what website addresses Home Assistant should share with other devices when they need to fetch data from Home Assistant (eg. to play text-to-speech or other hosted media).",
"internal_url_label": "Local Network",
"external_url_label": "Internet",
"external_use_ha_cloud": "Use Home Assistant Cloud",
"external_get_ha_cloud": "Access from anywhere using Home Assistant Cloud",
"ha_cloud_remote_not_enabled": "Your Home Assistant Cloud remote connection is currently not enabled.",
"enable_remote": "[%key:ui::common::enable%]",
"internal_url_automatic": "Automatic",
"internal_url_https_error_title": "Invalid local network URL",
"internal_url_https_error_description": "You have configured an HTTPS certificate in Home Assistant. This means that your internal URL needs to be set to a domain covered by the certficate."
},
"info": {
"caption": "Info",
"copy_menu": "Copy menu",