mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-27 03:06:41 +00:00
Show local network URL used in Home Assistant URL settings (#22379)
* Show local network URL used in Home Assistant URL settings * full width alert * always display both urls and add copy buttons * remove mask button when editing * fix typo * type fix * update styling based on comments * fix bad copy/paste * Update src/components/ha-settings-row.ts Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com> * Update src/panels/config/network/ha-config-url-form.ts Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com> * Update src/panels/config/network/ha-config-url-form.ts Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com> * PR comment * remove advanced flag * make the value handling logic clearer * move obfuscateUrl to a util function * PR comments --------- Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
This commit is contained in:
parent
42f2341e06
commit
5f6396b187
@ -5,6 +5,8 @@ import { customElement, property } from "lit/decorators";
|
|||||||
export class HaSettingsRow extends LitElement {
|
export class HaSettingsRow extends LitElement {
|
||||||
@property({ type: Boolean, reflect: true }) public narrow = false;
|
@property({ type: Boolean, reflect: true }) public narrow = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean, reflect: true }) public slim = false; // remove padding and min-height
|
||||||
|
|
||||||
@property({ type: Boolean, attribute: "three-line" })
|
@property({ type: Boolean, attribute: "three-line" })
|
||||||
public threeLine = false;
|
public threeLine = false;
|
||||||
|
|
||||||
@ -112,6 +114,14 @@ export class HaSettingsRow extends LitElement {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
:host([slim]),
|
||||||
|
:host([slim]) .content,
|
||||||
|
:host([slim]) ::slotted(ha-switch) {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
:host([slim]) .body {
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,12 @@ export interface NetworkConfig {
|
|||||||
configured_adapters: string[];
|
configured_adapters: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface NetworkUrls {
|
||||||
|
internal: string;
|
||||||
|
external: string;
|
||||||
|
cloud: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const getNetworkConfig = (hass: HomeAssistant) =>
|
export const getNetworkConfig = (hass: HomeAssistant) =>
|
||||||
hass.callWS<NetworkConfig>({
|
hass.callWS<NetworkConfig>({
|
||||||
type: "network",
|
type: "network",
|
||||||
@ -41,3 +47,8 @@ export const setNetworkConfig = (
|
|||||||
configured_adapters: configured_adapters,
|
configured_adapters: configured_adapters,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const getNetworkUrls = (hass: HomeAssistant) =>
|
||||||
|
hass.callWS<NetworkUrls>({
|
||||||
|
type: "network/url",
|
||||||
|
});
|
||||||
|
@ -23,6 +23,7 @@ import {
|
|||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import { showToast } from "../../../../util/toast";
|
import { showToast } from "../../../../util/toast";
|
||||||
import { showCloudCertificateDialog } from "../dialog-cloud-certificate/show-dialog-cloud-certificate";
|
import { showCloudCertificateDialog } from "../dialog-cloud-certificate/show-dialog-cloud-certificate";
|
||||||
|
import { obfuscateUrl } from "../../../../util/url";
|
||||||
|
|
||||||
@customElement("cloud-remote-pref")
|
@customElement("cloud-remote-pref")
|
||||||
export class CloudRemotePref extends LitElement {
|
export class CloudRemotePref extends LitElement {
|
||||||
@ -142,7 +143,7 @@ export class CloudRemotePref extends LitElement {
|
|||||||
<ha-textfield
|
<ha-textfield
|
||||||
.value=${this._unmaskedUrl
|
.value=${this._unmaskedUrl
|
||||||
? `https://${remote_domain}`
|
? `https://${remote_domain}`
|
||||||
: "https://•••••••••••••••••.ui.nabu.casa"}
|
: obfuscateUrl(`https://${remote_domain}`)}
|
||||||
readonly
|
readonly
|
||||||
.suffix=${
|
.suffix=${
|
||||||
// reserve some space for the icon.
|
// reserve some space for the icon.
|
||||||
@ -153,7 +154,7 @@ export class CloudRemotePref extends LitElement {
|
|||||||
class="toggle-unmasked-url"
|
class="toggle-unmasked-url"
|
||||||
toggles
|
toggles
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
`ui.panel.config.cloud.account.remote.${this._unmaskedUrl ? "hide" : "show"}_url`
|
`ui.panel.config.common.${this._unmaskedUrl ? "hide" : "show"}_url`
|
||||||
)}
|
)}
|
||||||
@click=${this._toggleUnmaskedUrl}
|
@click=${this._toggleUnmaskedUrl}
|
||||||
.path=${this._unmaskedUrl ? mdiEyeOff : mdiEye}
|
.path=${this._unmaskedUrl ? mdiEyeOff : mdiEye}
|
||||||
@ -165,9 +166,7 @@ export class CloudRemotePref extends LitElement {
|
|||||||
unelevated
|
unelevated
|
||||||
>
|
>
|
||||||
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
|
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
|
||||||
${this.hass.localize(
|
${this.hass.localize("ui.panel.config.common.copy_link")}
|
||||||
"ui.panel.config.cloud.account.remote.copy_link"
|
|
||||||
)}
|
|
||||||
</ha-button>
|
</ha-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
nothing,
|
nothing,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { mdiContentCopy, mdiEyeOff, mdiEye } from "@mdi/js";
|
||||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||||
import { isIPAddress } from "../../../common/string/is_ip_address";
|
import { isIPAddress } from "../../../common/string/is_ip_address";
|
||||||
import "../../../components/ha-alert";
|
import "../../../components/ha-alert";
|
||||||
@ -18,7 +19,12 @@ import "../../../components/ha-textfield";
|
|||||||
import type { HaTextField } from "../../../components/ha-textfield";
|
import type { HaTextField } from "../../../components/ha-textfield";
|
||||||
import { CloudStatus, fetchCloudStatus } from "../../../data/cloud";
|
import { CloudStatus, fetchCloudStatus } from "../../../data/cloud";
|
||||||
import { saveCoreConfig } from "../../../data/core";
|
import { saveCoreConfig } from "../../../data/core";
|
||||||
|
import { getNetworkUrls, type NetworkUrls } from "../../../data/network";
|
||||||
import type { ValueChangedEvent, HomeAssistant } from "../../../types";
|
import type { ValueChangedEvent, HomeAssistant } from "../../../types";
|
||||||
|
import { copyToClipboard } from "../../../common/util/copy-clipboard";
|
||||||
|
import { showToast } from "../../../util/toast";
|
||||||
|
import type { HaSwitch } from "../../../components/ha-switch";
|
||||||
|
import { obfuscateUrl } from "../../../util/url";
|
||||||
|
|
||||||
@customElement("ha-config-url-form")
|
@customElement("ha-config-url-form")
|
||||||
class ConfigUrlForm extends LitElement {
|
class ConfigUrlForm extends LitElement {
|
||||||
@ -28,9 +34,11 @@ class ConfigUrlForm extends LitElement {
|
|||||||
|
|
||||||
@state() private _working = false;
|
@state() private _working = false;
|
||||||
|
|
||||||
@state() private _external_url?: string;
|
@state() private _urls?: NetworkUrls;
|
||||||
|
|
||||||
@state() private _internal_url?: string;
|
@state() private _external_url: string = "";
|
||||||
|
|
||||||
|
@state() private _internal_url: string = "";
|
||||||
|
|
||||||
@state() private _cloudStatus?: CloudStatus | null;
|
@state() private _cloudStatus?: CloudStatus | null;
|
||||||
|
|
||||||
@ -38,18 +46,29 @@ class ConfigUrlForm extends LitElement {
|
|||||||
|
|
||||||
@state() private _showCustomInternalUrl = false;
|
@state() private _showCustomInternalUrl = false;
|
||||||
|
|
||||||
|
@state() private _unmaskedExternalUrl = false;
|
||||||
|
|
||||||
|
@state() private _unmaskedInternalUrl = false;
|
||||||
|
|
||||||
|
@state() private _cloudChecked = false;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
const canEdit = ["storage", "default"].includes(
|
const canEdit = ["storage", "default"].includes(
|
||||||
this.hass.config.config_source
|
this.hass.config.config_source
|
||||||
);
|
);
|
||||||
const disabled = this._working || !canEdit;
|
const disabled = this._working || !canEdit;
|
||||||
|
|
||||||
if (!this.hass.userData?.showAdvanced || this._cloudStatus === undefined) {
|
if (this._cloudStatus === undefined || this._urls === undefined) {
|
||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
const internalUrl = this._internalUrlValue;
|
const internalUrl = this._showCustomInternalUrl
|
||||||
const externalUrl = this._externalUrlValue;
|
? this._internal_url
|
||||||
|
: this._urls?.internal || "";
|
||||||
|
const externalUrl = this._showCustomExternalUrl
|
||||||
|
? this._external_url
|
||||||
|
: (this._cloudChecked ? this._urls?.cloud : this._urls?.external) || "";
|
||||||
|
|
||||||
let hasCloud: boolean;
|
let hasCloud: boolean;
|
||||||
let remoteEnabled: boolean;
|
let remoteEnabled: boolean;
|
||||||
let httpUseHttps: boolean;
|
let httpUseHttps: boolean;
|
||||||
@ -95,49 +114,61 @@ class ConfigUrlForm extends LitElement {
|
|||||||
|
|
||||||
${hasCloud
|
${hasCloud
|
||||||
? html`
|
? html`
|
||||||
<div class="row">
|
<h4>
|
||||||
<div class="flex">
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.url.external_url_label"
|
||||||
|
)}
|
||||||
|
</h4>
|
||||||
|
<ha-settings-row slim>
|
||||||
|
<span slot="heading">
|
||||||
${this.hass.localize(
|
${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"
|
"ui.panel.config.url.external_use_ha_cloud"
|
||||||
)}
|
)}
|
||||||
>
|
</span>
|
||||||
<ha-switch
|
<ha-switch
|
||||||
.disabled=${disabled}
|
.disabled=${disabled}
|
||||||
.checked=${externalUrl === null}
|
.checked=${this._cloudChecked}
|
||||||
@change=${this._toggleCloud}
|
@change=${this._toggleCloud}
|
||||||
></ha-switch>
|
></ha-switch>
|
||||||
</ha-formfield>
|
</ha-settings-row>
|
||||||
</div>
|
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
${!this._showCustomExternalUrl
|
<div class="url-container">
|
||||||
? ""
|
<div class="textfield-container">
|
||||||
: html`
|
<ha-textfield
|
||||||
<div class="row">
|
name="external_url"
|
||||||
<div class="flex">
|
type="url"
|
||||||
${hasCloud
|
placeholder="https://example.duckdns.org:8123"
|
||||||
? ""
|
.value=${this._unmaskedExternalUrl ||
|
||||||
: this.hass.localize(
|
(this._showCustomExternalUrl && canEdit)
|
||||||
"ui.panel.config.url.external_url_label"
|
? externalUrl
|
||||||
)}
|
: obfuscateUrl(externalUrl)}
|
||||||
</div>
|
@change=${this._handleChange}
|
||||||
<ha-textfield
|
.disabled=${disabled || !this._showCustomExternalUrl}
|
||||||
class="flex"
|
.suffix=${
|
||||||
name="external_url"
|
// reserve some space for the icon.
|
||||||
type="url"
|
html`<div style="width: 24px"></div>`
|
||||||
.disabled=${disabled}
|
}
|
||||||
.value=${externalUrl || ""}
|
></ha-textfield>
|
||||||
@change=${this._handleChange}
|
${!this._showCustomExternalUrl || !canEdit
|
||||||
placeholder="https://example.duckdns.org:8123"
|
? html`
|
||||||
>
|
<ha-icon-button
|
||||||
</ha-textfield>
|
class="toggle-unmasked-url"
|
||||||
</div>
|
toggles
|
||||||
`}
|
.label=${this.hass.localize(
|
||||||
|
`ui.panel.config.common.${this._unmaskedExternalUrl ? "hide" : "show"}_url`
|
||||||
|
)}
|
||||||
|
@click=${this._toggleUnmaskedExternalUrl}
|
||||||
|
.path=${this._unmaskedExternalUrl ? mdiEyeOff : mdiEye}
|
||||||
|
></ha-icon-button>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
</div>
|
||||||
|
<ha-button .url=${externalUrl} @click=${this._copyURL}>
|
||||||
|
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
|
||||||
|
${this.hass.localize("ui.panel.config.common.copy_link")}
|
||||||
|
</ha-button>
|
||||||
|
</div>
|
||||||
${hasCloud || !isComponentLoaded(this.hass, "cloud")
|
${hasCloud || !isComponentLoaded(this.hass, "cloud")
|
||||||
? ""
|
? ""
|
||||||
: html`
|
: html`
|
||||||
@ -180,40 +211,65 @@ class ConfigUrlForm extends LitElement {
|
|||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
|
|
||||||
<div class="row">
|
<h4>
|
||||||
<div class="flex">
|
${this.hass.localize("ui.panel.config.url.internal_url_label")}
|
||||||
${this.hass.localize("ui.panel.config.url.internal_url_label")}
|
</h4>
|
||||||
</div>
|
<ha-settings-row slim>
|
||||||
|
<span slot="heading">
|
||||||
<ha-formfield
|
${this.hass.localize(
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.panel.config.url.internal_url_automatic"
|
"ui.panel.config.url.internal_url_automatic"
|
||||||
)}
|
)}
|
||||||
>
|
</span>
|
||||||
<ha-switch
|
<span slot="description">
|
||||||
.checked=${internalUrl === null}
|
${this.hass.localize(
|
||||||
@change=${this._toggleInternalAutomatic}
|
"ui.panel.config.url.internal_url_automatic_description"
|
||||||
></ha-switch>
|
)}
|
||||||
</ha-formfield>
|
</span>
|
||||||
</div>
|
<ha-switch
|
||||||
|
.disabled=${disabled}
|
||||||
|
.checked=${!this._showCustomInternalUrl}
|
||||||
|
@change=${this._toggleInternalAutomatic}
|
||||||
|
></ha-switch>
|
||||||
|
</ha-settings-row>
|
||||||
|
|
||||||
${!this._showCustomInternalUrl
|
<div class="url-container">
|
||||||
? ""
|
<div class="textfield-container">
|
||||||
: html`
|
<ha-textfield
|
||||||
<div class="row">
|
name="internal_url"
|
||||||
<div class="flex"></div>
|
type="url"
|
||||||
<ha-textfield
|
placeholder=${this.hass.localize(
|
||||||
class="flex"
|
"ui.panel.config.url.internal_url_placeholder"
|
||||||
name="internal_url"
|
)}
|
||||||
type="url"
|
.value=${this._unmaskedInternalUrl ||
|
||||||
placeholder="http://<some IP address>:8123"
|
(this._showCustomInternalUrl && canEdit)
|
||||||
.disabled=${disabled}
|
? internalUrl
|
||||||
.value=${internalUrl || ""}
|
: obfuscateUrl(internalUrl)}
|
||||||
@change=${this._handleChange}
|
@change=${this._handleChange}
|
||||||
>
|
.disabled=${disabled || !this._showCustomInternalUrl}
|
||||||
</ha-textfield>
|
.suffix=${
|
||||||
</div>
|
// reserve some space for the icon.
|
||||||
`}
|
html`<div style="width: 24px"></div>`
|
||||||
|
}
|
||||||
|
></ha-textfield>
|
||||||
|
${!this._showCustomInternalUrl || !canEdit
|
||||||
|
? html`
|
||||||
|
<ha-icon-button
|
||||||
|
class="toggle-unmasked-url"
|
||||||
|
toggles
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
`ui.panel.config.common.${this._unmaskedInternalUrl ? "hide" : "show"}_url`
|
||||||
|
)}
|
||||||
|
@click=${this._toggleUnmaskedInternalUrl}
|
||||||
|
.path=${this._unmaskedInternalUrl ? mdiEyeOff : mdiEye}
|
||||||
|
></ha-icon-button>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
</div>
|
||||||
|
<ha-button .url=${internalUrl} @click=${this._copyURL}>
|
||||||
|
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
|
||||||
|
${this.hass.localize("ui.panel.config.common.copy_link")}
|
||||||
|
</ha-button>
|
||||||
|
</div>
|
||||||
${
|
${
|
||||||
// If the user has configured a cert, show an error if
|
// If the user has configured a cert, show an error if
|
||||||
httpUseHttps && // there is no internal url configured
|
httpUseHttps && // there is no internal url configured
|
||||||
@ -253,46 +309,47 @@ class ConfigUrlForm extends LitElement {
|
|||||||
protected override firstUpdated(changedProps: PropertyValues) {
|
protected override firstUpdated(changedProps: PropertyValues) {
|
||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
|
|
||||||
this._showCustomInternalUrl = this._internalUrlValue !== null;
|
|
||||||
|
|
||||||
if (isComponentLoaded(this.hass, "cloud")) {
|
if (isComponentLoaded(this.hass, "cloud")) {
|
||||||
fetchCloudStatus(this.hass).then((cloudStatus) => {
|
fetchCloudStatus(this.hass).then((cloudStatus) => {
|
||||||
this._cloudStatus = cloudStatus;
|
this._cloudStatus = cloudStatus;
|
||||||
if (cloudStatus.logged_in) {
|
this._showCustomExternalUrl = !(
|
||||||
this._showCustomExternalUrl = this._externalUrlValue !== null;
|
this._cloudStatus.logged_in && !this.hass.config.external_url
|
||||||
} else {
|
);
|
||||||
this._showCustomExternalUrl = true;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this._cloudStatus = null;
|
this._cloudStatus = null;
|
||||||
this._showCustomExternalUrl = true;
|
|
||||||
}
|
}
|
||||||
|
this._fetchUrls();
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _internalUrlValue() {
|
private _toggleCloud(ev: Event) {
|
||||||
return this._internal_url !== undefined
|
this._cloudChecked = (ev.currentTarget as HaSwitch).checked;
|
||||||
? this._internal_url
|
this._showCustomExternalUrl = !this._cloudChecked;
|
||||||
: this.hass.config.internal_url;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _externalUrlValue() {
|
private _toggleInternalAutomatic(ev: Event) {
|
||||||
return this._external_url !== undefined
|
this._showCustomInternalUrl = !(ev.currentTarget as HaSwitch).checked;
|
||||||
? this._external_url
|
|
||||||
: this.hass.config.external_url;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _toggleCloud(ev) {
|
private _toggleUnmaskedInternalUrl() {
|
||||||
this._showCustomExternalUrl = !ev.currentTarget.checked;
|
this._unmaskedInternalUrl = !this._unmaskedInternalUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _toggleInternalAutomatic(ev) {
|
private _toggleUnmaskedExternalUrl() {
|
||||||
this._showCustomInternalUrl = !ev.currentTarget.checked;
|
this._unmaskedExternalUrl = !this._unmaskedExternalUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _copyURL(ev) {
|
||||||
|
const url = ev.currentTarget.url;
|
||||||
|
await copyToClipboard(url);
|
||||||
|
showToast(this, {
|
||||||
|
message: this.hass.localize("ui.common.copied_clipboard"),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleChange(ev: ValueChangedEvent<string>) {
|
private _handleChange(ev: ValueChangedEvent<string>) {
|
||||||
const target = ev.currentTarget as HaTextField;
|
const target = ev.currentTarget as HaTextField;
|
||||||
this[`_${target.name}`] = target.value || null;
|
this[`_${target.name}`] = target.value || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _save() {
|
private async _save() {
|
||||||
@ -307,6 +364,7 @@ class ConfigUrlForm extends LitElement {
|
|||||||
? this._internal_url || null
|
? this._internal_url || null
|
||||||
: null,
|
: null,
|
||||||
});
|
});
|
||||||
|
await this._fetchUrls();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
this._error = err.message || err;
|
this._error = err.message || err;
|
||||||
} finally {
|
} finally {
|
||||||
@ -314,6 +372,19 @@ class ConfigUrlForm extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _fetchUrls() {
|
||||||
|
this._urls = await getNetworkUrls(this.hass);
|
||||||
|
this._cloudChecked =
|
||||||
|
this._urls?.cloud === this._urls?.external &&
|
||||||
|
!this.hass.config.external_url;
|
||||||
|
this._showCustomInternalUrl = !!this.hass.config.internal_url;
|
||||||
|
this._showCustomExternalUrl = !(
|
||||||
|
this._cloudStatus?.logged_in && !this.hass.config.external_url
|
||||||
|
);
|
||||||
|
this._internal_url = this._urls?.internal ?? "";
|
||||||
|
this._external_url = this._urls?.external ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
.description {
|
.description {
|
||||||
@ -351,6 +422,31 @@ class ConfigUrlForm extends LitElement {
|
|||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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);
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1930,7 +1930,10 @@
|
|||||||
"multiselect": {
|
"multiselect": {
|
||||||
"failed": "Failed to update {number} items."
|
"failed": "Failed to update {number} items."
|
||||||
},
|
},
|
||||||
"learn_more": "Learn more"
|
"learn_more": "Learn more",
|
||||||
|
"show_url": "Show full URL",
|
||||||
|
"hide_url": "Hide URL",
|
||||||
|
"copy_link": "Copy link"
|
||||||
},
|
},
|
||||||
"updates": {
|
"updates": {
|
||||||
"caption": "Updates",
|
"caption": "Updates",
|
||||||
@ -2398,7 +2401,9 @@
|
|||||||
"enable_remote": "[%key:ui::common::enable%]",
|
"enable_remote": "[%key:ui::common::enable%]",
|
||||||
"internal_url_automatic": "Automatic",
|
"internal_url_automatic": "Automatic",
|
||||||
"internal_url_https_error_title": "Invalid local network URL",
|
"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."
|
"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.",
|
||||||
|
"internal_url_automatic_description": "Use the configured network settings",
|
||||||
|
"internal_url_placeholder": "http://<some IP address>:8123"
|
||||||
},
|
},
|
||||||
"hardware": {
|
"hardware": {
|
||||||
"caption": "Hardware",
|
"caption": "Hardware",
|
||||||
@ -3915,9 +3920,6 @@
|
|||||||
"info": "Home Assistant Cloud provides a secure remote access to your instance while away from home. For more information on remote access and these settings visit our security documentation.",
|
"info": "Home Assistant Cloud provides a secure remote access to your instance while away from home. For more information on remote access and these settings visit our security documentation.",
|
||||||
"info_instance_will_be_available": "Your instance will be available at your Nabu Casa URL.",
|
"info_instance_will_be_available": "Your instance will be available at your Nabu Casa URL.",
|
||||||
"link_learn_how_it_works": "Learn how it works",
|
"link_learn_how_it_works": "Learn how it works",
|
||||||
"show_url": "Show full URL",
|
|
||||||
"hide_url": "Hide URL",
|
|
||||||
"copy_link": "Copy link",
|
|
||||||
"security_options": "Security options",
|
"security_options": "Security options",
|
||||||
"external_activation": "Allow external activation of remote access",
|
"external_activation": "Allow external activation of remote access",
|
||||||
"external_activation_secondary": "If you disable remote access on this page, having this setting enabled allows you to reactivate it remotely via your Nabu Casa account.",
|
"external_activation_secondary": "If you disable remote access on this page, having this setting enabled allows you to reactivate it remotely via your Nabu Casa account.",
|
||||||
|
9
src/util/url.ts
Normal file
9
src/util/url.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export function obfuscateUrl(url: string) {
|
||||||
|
if (url.endsWith(".ui.nabu.casa")) {
|
||||||
|
return "https://•••••••••••••••••.ui.nabu.casa";
|
||||||
|
}
|
||||||
|
// hide any words that look like they might be a hostname or IP address
|
||||||
|
return url.replace(/(?<=:\/\/)[\w-]+|(?<=\.)[\w-]+/g, (match) =>
|
||||||
|
"•".repeat(match.length)
|
||||||
|
);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user