mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-22 16:56:35 +00:00
Revamp URL form (#12060)
This commit is contained in:
parent
ab5df0fe6e
commit
1e929ae78a
4
src/common/string/is_ip_address.ts
Normal file
4
src/common/string/is_ip_address.ts
Normal 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);
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
|
Loading…
x
Reference in New Issue
Block a user