Add UI to set/update core config (#3208)

* Add UI to set/update core config

* Types

* Disable editor in config.yaml mode

* Fix type
This commit is contained in:
Paulus Schoutsen 2019-05-21 20:12:07 -07:00 committed by GitHub
parent be0bef3f1b
commit 575eb22608
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 834 additions and 46 deletions

View File

@ -75,8 +75,9 @@
"es6-object-assign": "^1.1.0",
"fecha": "^3.0.2",
"fuse.js": "^3.4.4",
"google-timezones-json": "^1.0.2",
"hls.js": "^0.12.4",
"home-assistant-js-websocket": "^4.1.2",
"home-assistant-js-websocket": "^4.2.1",
"intl-messageformat": "^2.2.0",
"jquery": "^3.3.1",
"js-yaml": "^3.13.0",

View File

@ -1,15 +1,11 @@
import { HomeAssistant } from "../../types";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { UnsubscribeFunc, Connection } from "home-assistant-js-websocket";
export const subscribeOne = async <T>(
hass: HomeAssistant,
subscribe: (
hass: HomeAssistant,
onChange: (items: T) => void
) => UnsubscribeFunc
conn: Connection,
subscribe: (conn: Connection, onChange: (items: T) => void) => UnsubscribeFunc
) =>
new Promise<T>((resolve) => {
const unsub = subscribe(hass, (items) => {
const unsub = subscribe(conn, (items) => {
unsub();
resolve(items);
});

View File

@ -0,0 +1,13 @@
import timezones from "google-timezones-json";
export const createTimezoneListEl = () => {
const list = document.createElement("datalist");
list.id = "timezones";
Object.keys(timezones).forEach((key) => {
const option = document.createElement("option");
option.value = key;
option.innerHTML = timezones[key];
list.appendChild(option);
});
return list;
};

View File

@ -1,4 +1,4 @@
import { createCollection } from "home-assistant-js-websocket";
import { createCollection, Connection } from "home-assistant-js-websocket";
import { HomeAssistant } from "../types";
import { compare } from "../common/string/compare";
import { debounce } from "../common/util/debounce";
@ -57,13 +57,13 @@ const subscribeAreaRegistryUpdates = (conn, store) =>
);
export const subscribeAreaRegistry = (
hass: HomeAssistant,
conn: Connection,
onChange: (areas: AreaRegistryEntry[]) => void
) =>
createCollection<AreaRegistryEntry[]>(
"_areaRegistry",
fetchAreaRegistry,
subscribeAreaRegistryUpdates,
hass.connection,
conn,
onChange
);

25
src/data/core.ts Normal file
View File

@ -0,0 +1,25 @@
import { HomeAssistant } from "../types";
import { HassConfig } from "home-assistant-js-websocket";
export interface ConfigUpdateValues {
location_name: string;
latitude: number;
longitude: number;
elevation: number;
unit_system: "metric" | "imperial";
time_zone: string;
}
export const saveCoreConfig = (
hass: HomeAssistant,
values: Partial<ConfigUpdateValues>
) =>
hass.callWS<HassConfig>({
type: "config/core/update",
...values,
});
export const detectCoreConfig = (hass: HomeAssistant) =>
hass.callWS<Partial<ConfigUpdateValues>>({
type: "config/core/detect",
});

View File

@ -1,5 +1,5 @@
import { HomeAssistant } from "../types";
import { createCollection } from "home-assistant-js-websocket";
import { createCollection, Connection } from "home-assistant-js-websocket";
import { debounce } from "../common/util/debounce";
export interface DeviceRegistryEntry {
@ -50,13 +50,13 @@ const subscribeDeviceRegistryUpdates = (conn, store) =>
);
export const subscribeDeviceRegistry = (
hass: HomeAssistant,
conn: Connection,
onChange: (devices: DeviceRegistryEntry[]) => void
) =>
createCollection<DeviceRegistryEntry[]>(
"_dr",
fetchDeviceRegistry,
subscribeDeviceRegistryUpdates,
hass.connection,
conn,
onChange
);

View File

@ -1,4 +1,4 @@
import { createCollection } from "home-assistant-js-websocket";
import { createCollection, Connection } from "home-assistant-js-websocket";
import { HomeAssistant } from "../types";
import computeStateName from "../common/entity/compute_state_name";
import { debounce } from "../common/util/debounce";
@ -67,13 +67,13 @@ const subscribeEntityRegistryUpdates = (conn, store) =>
);
export const subscribeEntityRegistry = (
hass: HomeAssistant,
conn: Connection,
onChange: (entities: EntityRegistryEntry[]) => void
) =>
createCollection<EntityRegistryEntry[]>(
"_entityRegistry",
fetchEntityRegistry,
subscribeEntityRegistryUpdates,
hass.connection,
conn,
onChange
);

View File

@ -1,6 +1,9 @@
import { handleFetchPromise } from "../util/hass-call-api";
import { HomeAssistant } from "../types";
// tslint:disable-next-line: no-empty-interface
export interface OnboardingCoreConfigStepResponse {}
export interface OnboardingUserStepResponse {
auth_code: string;
}
@ -11,6 +14,7 @@ export interface OnboardingIntegrationStepResponse {
export interface OnboardingResponses {
user: OnboardingUserStepResponse;
core_config: OnboardingCoreConfigStepResponse;
integration: OnboardingIntegrationStepResponse;
}
@ -39,6 +43,12 @@ export const onboardUserStep = (params: {
})
);
export const onboardCoreConfigStep = (hass: HomeAssistant) =>
hass.callApi<OnboardingCoreConfigStepResponse>(
"POST",
"onboarding/core_config"
);
export const onboardIntegrationStep = (
hass: HomeAssistant,
params: { client_id: string }

View File

@ -219,15 +219,18 @@ class ConfigFlowDialog extends LitElement {
}
private async _fetchDevices(configEntryId) {
this._unsubDevices = subscribeDeviceRegistry(this.hass, (devices) => {
this._unsubDevices = subscribeDeviceRegistry(
this.hass.connection,
(devices) => {
this._devices = devices.filter((device) =>
device.config_entries.includes(configEntryId)
);
});
}
);
}
private async _fetchAreas() {
this._unsubAreas = subscribeAreaRegistry(this.hass, (areas) => {
this._unsubAreas = subscribeAreaRegistry(this.hass.connection, (areas) => {
this._areas = areas;
});
}

View File

@ -16,4 +16,5 @@ export const demoConfig: HassConfig = {
config_dir: "/config",
version: "DEMO",
whitelist_external_dirs: [],
config_source: "storage",
};

View File

@ -10,6 +10,7 @@ import {
createConnection,
genClientId,
Auth,
subscribeConfig,
} from "home-assistant-js-websocket";
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
import {
@ -24,6 +25,8 @@ import "./onboarding-create-user";
import "./onboarding-loading";
import { hassUrl } from "../data/auth";
import { HassElement } from "../state/hass-element";
import { subscribeOne } from "../common/util/subscribe-one";
import { subscribeUser } from "../data/ws-user";
interface OnboardingEvent<T extends ValidOnboardingStep> {
type: T;
@ -61,6 +64,13 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
.language=${this.language}
></onboarding-create-user>
`;
} else if (step.step === "core_config") {
return html`
<onboarding-core-config
.hass=${this.hass}
.onboardingLocalize=${this.localize}
></onboarding-core-config>
`;
} else if (step.step === "integration") {
return html`
<onboarding-integrations
@ -75,6 +85,7 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
super.firstUpdated(changedProps);
this._fetchOnboardingSteps();
import("./onboarding-integrations");
import("./onboarding-core-config");
registerServiceWorker(false);
this.addEventListener("onboarding-step", (ev) => this._handleStepDone(ev));
}
@ -106,6 +117,7 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
const auth = await getAuth({
hassUrl,
});
history.replaceState(null, "", location.pathname);
await this._connectHass(auth);
}
@ -138,6 +150,8 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
} finally {
this._loading = false;
}
} else if (stepResult.type === "core_config") {
// We do nothing
} else if (stepResult.type === "integration") {
const result = stepResult.result as OnboardingResponses["integration"];
this._loading = true;
@ -161,6 +175,12 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
private async _connectHass(auth: Auth) {
const conn = await createConnection({ auth });
// Make sure config and user info is loaded before we initialize.
// It is needed for the core config step.
await Promise.all([
subscribeOne(conn, subscribeConfig),
subscribeOne(conn, subscribeUser),
]);
this.initializeHass(auth, conn);
// Load config strings for integrations
(this as any)._loadFragmentTranslations(this.hass!.language, "config");

View File

@ -0,0 +1,320 @@
import {
LitElement,
customElement,
property,
TemplateResult,
html,
CSSResult,
css,
} from "lit-element";
import "@material/mwc-button/mwc-button";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-radio-group/paper-radio-group";
import "@polymer/paper-radio-button/paper-radio-button";
// tslint:disable-next-line: no-duplicate-imports
import { PaperInputElement } from "@polymer/paper-input/paper-input";
import { HomeAssistant } from "../types";
import {
ConfigUpdateValues,
detectCoreConfig,
saveCoreConfig,
} from "../data/core";
import { UNIT_C } from "../common/const";
import { PolymerChangedEvent } from "../polymer-types";
import { onboardCoreConfigStep } from "../data/onboarding";
import { fireEvent } from "../common/dom/fire_event";
import { LocalizeFunc } from "../common/translations/localize";
import { createTimezoneListEl } from "../components/timezone-datalist";
@customElement("onboarding-core-config")
class OnboardingCoreConfig extends LitElement {
@property() public hass!: HomeAssistant;
@property() public onboardingLocalize!: LocalizeFunc;
@property() private _working = false;
@property() private _name!: ConfigUpdateValues["location_name"];
@property() private _latitude!: string;
@property() private _longitude!: string;
@property() private _elevation!: string;
@property() private _unitSystem!: ConfigUpdateValues["unit_system"];
@property() private _timeZone!: string;
protected render(): TemplateResult | void {
return html`
<p>
${this.onboardingLocalize(
"ui.panel.page-onboarding.core-config.intro",
"name",
this.hass.user!.name
)}
</p>
<paper-input
.label=${this.onboardingLocalize(
"ui.panel.page-onboarding.core-config.location_name"
)}
name="name"
.disabled=${this._working}
.value=${this._nameValue}
@value-changed=${this._handleChange}
></paper-input>
<div class="middle-text">
<p>
${this.onboardingLocalize(
"ui.panel.page-onboarding.core-config.intro_location"
)}
</p>
<div class="row">
<div>
${this.onboardingLocalize(
"ui.panel.page-onboarding.core-config.intro_location_detect"
)}
</div>
<mwc-button @click=${this._detect}>
${this.onboardingLocalize(
"ui.panel.page-onboarding.core-config.button_detect"
)}
</mwc-button>
</div>
</div>
<div class="row">
<paper-input
class="flex"
.label=${this.hass.localize(
"ui.panel.config.core.section.core.core_config.latitude"
)}
name="latitude"
.disabled=${this._working}
.value=${this._latitudeValue}
@value-changed=${this._handleChange}
></paper-input>
<paper-input
class="flex"
.label=${this.hass.localize(
"ui.panel.config.core.section.core.core_config.longitude"
)}
name="longitude"
.disabled=${this._working}
.value=${this._longitudeValue}
@value-changed=${this._handleChange}
></paper-input>
</div>
<div class="row">
<paper-input
class="flex"
.label=${this.hass.localize(
"ui.panel.config.core.section.core.core_config.time_zone"
)}
name="timeZone"
list="timezones"
.disabled=${this._working}
.value=${this._timeZoneValue}
@value-changed=${this._handleChange}
></paper-input>
<paper-input
class="flex"
.label=${this.hass.localize(
"ui.panel.config.core.section.core.core_config.elevation"
)}
name="elevation"
type="number"
.disabled=${this._working}
.value=${this._elevationValue}
@value-changed=${this._handleChange}
>
<span slot="suffix">
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.elevation_meters"
)}
</span>
</paper-input>
</div>
<div class="row">
<div class="flex">
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.unit_system"
)}
</div>
<paper-radio-group class="flex" .selected=${this._unitSystemValue}>
<paper-radio-button name="metric" .disabled=${this._working}>
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.unit_system_metric"
)}
<div class="secondary">
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.metric_example"
)}
</div>
</paper-radio-button>
<paper-radio-button name="imperial" .disabled=${this._working}>
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.unit_system_imperial"
)}
<div class="secondary">
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.imperial_example"
)}
</div>
</paper-radio-button>
</paper-radio-group>
</div>
<div class="footer">
<mwc-button @click=${this._save} .disabled=${this._working}>
${this.onboardingLocalize(
"ui.panel.page-onboarding.core-config.finish"
)}
</mwc-button>
</div>
`;
}
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
setTimeout(
() => this.shadowRoot!.querySelector("paper-input")!.focus(),
100
);
this.addEventListener("keypress", (ev) => {
if (ev.keyCode === 13) {
this._save(ev);
}
});
const input = this.shadowRoot!.querySelector(
"[name=timeZone]"
) as PaperInputElement;
input.inputElement.appendChild(createTimezoneListEl());
}
private get _nameValue() {
return this._name !== undefined
? this._name
: this.onboardingLocalize(
"ui.panel.page-onboarding.core-config.location_name_default"
);
}
private get _latitudeValue() {
return this._latitude !== undefined
? this._latitude
: this.hass.config.latitude;
}
private get _longitudeValue() {
return this._longitude !== undefined
? this._longitude
: this.hass.config.longitude;
}
private get _elevationValue() {
return this._elevation !== undefined
? this._elevation
: this.hass.config.elevation;
}
private get _timeZoneValue() {
return this._timeZone !== undefined
? this._timeZone
: this.hass.config.time_zone;
}
private get _unitSystemValue() {
return this._unitSystem !== undefined
? this._unitSystem
: this.hass.config.unit_system.temperature === UNIT_C
? "metric"
: "imperial";
}
private _handleChange(ev: PolymerChangedEvent<string>) {
const target = ev.currentTarget as PaperInputElement;
this[`_${target.name}`] = target.value;
}
private async _detect() {
this._working = true;
try {
const values = await detectCoreConfig(this.hass);
for (const key in values) {
if (key === "unit_system") {
this._unitSystem = values[key]!;
} else if (key === "time_zone") {
this._timeZone = values[key]!;
} else {
this[`_${key}`] = values[key];
}
}
} catch (err) {
alert(`Failed to detect location information: ${err.message}`);
} finally {
this._working = false;
}
}
private async _save(ev) {
ev.preventDefault();
this._working = true;
try {
await saveCoreConfig(this.hass, {
location_name: this._nameValue,
latitude: Number(this._latitudeValue),
longitude: Number(this._longitudeValue),
elevation: Number(this._elevationValue),
unit_system: this._unitSystemValue,
time_zone: this._timeZoneValue,
});
const result = await onboardCoreConfigStep(this.hass);
fireEvent(this, "onboarding-step", {
type: "core_config",
result,
});
} catch (err) {
this._working = false;
alert("FAIL");
}
}
static get styles(): CSSResult {
return css`
.row {
display: flex;
flex-direction: row;
margin: 0 -8px;
align-items: center;
}
.secondary {
color: var(--secondary-text-color);
}
.flex {
flex: 1;
}
.middle-text {
margin: 24px 0;
}
.row > * {
margin: 0 8px;
}
.footer {
margin-top: 16px;
text-align: right;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"onboarding-core-config": OnboardingCoreConfig;
}
}

View File

@ -122,9 +122,12 @@ class HaConfigAreaRegistry extends LitElement {
protected updated(changedProps) {
super.updated(changedProps);
if (!this._unsubAreas) {
this._unsubAreas = subscribeAreaRegistry(this.hass, (areas) => {
this._unsubAreas = subscribeAreaRegistry(
this.hass.connection,
(areas) => {
this._areas = areas;
});
}
);
}
}

View File

@ -0,0 +1,255 @@
import {
LitElement,
customElement,
property,
TemplateResult,
html,
CSSResult,
css,
} from "lit-element";
import "@material/mwc-button/mwc-button";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-radio-group/paper-radio-group";
import "@polymer/paper-radio-button/paper-radio-button";
import { HomeAssistant } from "../../../types";
import "../../../components/ha-card";
import { PolymerChangedEvent } from "../../../polymer-types";
// tslint:disable-next-line: no-duplicate-imports
import { PaperInputElement } from "@polymer/paper-input/paper-input";
import { UNIT_C } from "../../../common/const";
import { ConfigUpdateValues, saveCoreConfig } from "../../../data/core";
import { createTimezoneListEl } from "../../../components/timezone-datalist";
@customElement("ha-config-core-form")
class ConfigCoreForm extends LitElement {
@property() public hass!: HomeAssistant;
@property() private _working = false;
@property() private _latitude!: string;
@property() private _longitude!: string;
@property() private _elevation!: string;
@property() private _unitSystem!: ConfigUpdateValues["unit_system"];
@property() private _timeZone!: string;
protected render(): TemplateResult | void {
const isStorage = this.hass.config.config_source === "storage";
const disabled = this._working || !isStorage;
return html`
<ha-card
.header=${this.hass.localize(
"ui.panel.config.core.section.core.form.heading"
)}
>
<div class="card-content">
${!isStorage
? html`
<p>
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.edit_requires_storage"
)}
</p>
`
: ""}
<div class="row">
<paper-input
class="flex"
.label=${this.hass.localize(
"ui.panel.config.core.section.core.core_config.latitude"
)}
name="latitude"
.disabled=${disabled}
.value=${this._latitudeValue}
@value-changed=${this._handleChange}
></paper-input>
<paper-input
class="flex"
.label=${this.hass.localize(
"ui.panel.config.core.section.core.core_config.longitude"
)}
name="longitude"
.disabled=${disabled}
.value=${this._longitudeValue}
@value-changed=${this._handleChange}
></paper-input>
</div>
<div class="row">
<div class="flex">
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.time_zone"
)}
</div>
<paper-input
class="flex"
.label=${this.hass.localize(
"ui.panel.config.core.section.core.core_config.time_zone"
)}
name="timeZone"
list="timezones"
.disabled=${disabled}
.value=${this._timeZoneValue}
@value-changed=${this._handleChange}
></paper-input>
</div>
<div class="row">
<div class="flex">
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.elevation"
)}
</div>
<paper-input
class="flex"
.label=${this.hass.localize(
"ui.panel.config.core.section.core.core_config.elevation"
)}
name="elevation"
type="number"
.disabled=${disabled}
.value=${this._elevationValue}
@value-changed=${this._handleChange}
>
<span slot="suffix">
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.elevation_meters"
)}
</span>
</paper-input>
</div>
<div class="row">
<div class="flex">
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.unit_system"
)}
</div>
<paper-radio-group class="flex" .selected=${this._unitSystemValue}>
<paper-radio-button name="metric" .disabled=${disabled}>
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.unit_system_metric"
)}
<div class="secondary">
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.metric_example"
)}
</div>
</paper-radio-button>
<paper-radio-button name="imperial" .disabled=${disabled}>
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.unit_system_imperial"
)}
<div class="secondary">
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.imperial_example"
)}
</div>
</paper-radio-button>
</paper-radio-group>
</div>
</div>
<div class="card-actions">
<mwc-button @click=${this._save} .disabled=${disabled}>
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.save_button"
)}
</mwc-button>
</div>
</ha-card>
`;
}
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
const input = this.shadowRoot!.querySelector(
"[name=timeZone]"
) as PaperInputElement;
input.inputElement.appendChild(createTimezoneListEl());
}
private get _latitudeValue() {
return this._latitude !== undefined
? this._latitude
: this.hass.config.latitude;
}
private get _longitudeValue() {
return this._longitude !== undefined
? this._longitude
: this.hass.config.longitude;
}
private get _elevationValue() {
return this._elevation !== undefined
? this._elevation
: this.hass.config.elevation;
}
private get _timeZoneValue() {
return this._timeZone !== undefined
? this._timeZone
: this.hass.config.time_zone;
}
private get _unitSystemValue() {
return this._unitSystem !== undefined
? this._unitSystem
: this.hass.config.unit_system.temperature === UNIT_C
? "metric"
: "imperial";
}
private _handleChange(ev: PolymerChangedEvent<string>) {
const target = ev.currentTarget as PaperInputElement;
this[`_${target.name}`] = target.value;
}
private async _save() {
this._working = true;
try {
await saveCoreConfig(this.hass, {
latitude: Number(this._latitudeValue),
longitude: Number(this._longitudeValue),
elevation: Number(this._elevationValue),
unit_system: this._unitSystemValue,
time_zone: this._timeZoneValue,
});
} catch (err) {
alert("FAIL");
} finally {
this._working = false;
}
}
static get styles(): CSSResult {
return css`
.row {
display: flex;
flex-direction: row;
margin: 0 -8px;
align-items: center;
}
.secondary {
color: var(--secondary-text-color);
}
.flex {
flex: 1;
}
.row > * {
margin: 0 8px;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-config-core-form": ConfigCoreForm;
}
}

View File

@ -0,0 +1,94 @@
import {
LitElement,
customElement,
property,
TemplateResult,
html,
} from "lit-element";
import "@material/mwc-button/mwc-button";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-radio-group/paper-radio-group";
import "@polymer/paper-radio-button/paper-radio-button";
import { HomeAssistant } from "../../../types";
import "../../../components/ha-card";
import { PolymerChangedEvent } from "../../../polymer-types";
// tslint:disable-next-line: no-duplicate-imports
import { PaperInputElement } from "@polymer/paper-input/paper-input";
import { ConfigUpdateValues, saveCoreConfig } from "../../../data/core";
@customElement("ha-config-name-form")
class ConfigNameForm extends LitElement {
@property() public hass!: HomeAssistant;
@property() private _working = false;
@property() private _name!: ConfigUpdateValues["location_name"];
protected render(): TemplateResult | void {
const isStorage = this.hass.config.config_source === "storage";
const disabled = this._working || !isStorage;
return html`
<ha-card>
<div class="card-content">
${!isStorage
? html`
<p>
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.edit_requires_storage"
)}
</p>
`
: ""}
<paper-input
class="flex"
.label=${this.hass.localize(
"ui.panel.config.core.section.core.core_config.location_name"
)}
name="name"
.disabled=${disabled}
.value=${this._nameValue}
@value-changed=${this._handleChange}
></paper-input>
</div>
<div class="card-actions">
<mwc-button @click=${this._save} .disabled=${disabled}>
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.save_button"
)}
</mwc-button>
</div>
</ha-card>
`;
}
private get _nameValue() {
return this._name !== undefined
? this._name
: this.hass.config.location_name;
}
private _handleChange(ev: PolymerChangedEvent<string>) {
const target = ev.currentTarget as PaperInputElement;
this[`_${target.name}`] = target.value;
}
private async _save() {
this._working = true;
try {
await saveCoreConfig(this.hass, {
location_name: this._nameValue,
});
} catch (err) {
alert("FAIL");
} finally {
this._working = false;
}
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-config-name-form": ConfigNameForm;
}
}

View File

@ -12,6 +12,9 @@ import "../ha-config-section";
import isComponentLoaded from "../../../common/config/is_component_loaded";
import LocalizeMixin from "../../../mixins/localize-mixin";
import "./ha-config-name-form";
import "./ha-config-core-form";
/*
* @appliesMixin LocalizeMixin
*/
@ -57,6 +60,9 @@ class HaConfigSectionCore extends LocalizeMixin(PolymerElement) {
>[[localize('ui.panel.config.core.section.core.introduction')]]</span
>
<ha-config-name-form hass="[[hass]]"></ha-config-name-form>
<ha-config-core-form hass="[[hass]]"></ha-config-core-form>
<ha-card
header="[[localize('ui.panel.config.core.section.core.validation.heading')]]"
>

View File

@ -118,11 +118,14 @@ class HaConfigEntityRegistry extends LitElement {
protected updated(changedProps) {
super.updated(changedProps);
if (!this._unsubEntities) {
this._unsubEntities = subscribeEntityRegistry(this.hass, (entities) => {
this._unsubEntities = subscribeEntityRegistry(
this.hass.connection,
(entities) => {
this._entities = entities.sort((ent1, ent2) =>
compare(ent1.entity_id, ent2.entity_id)
);
});
}
);
}
}

View File

@ -96,7 +96,7 @@ class HaConfigIntegrations extends NavigateMixin(PolymerElement) {
connectedCallback() {
super.connectedCallback();
this._loadData();
this._unsubAreas = subscribeAreaRegistry(this.hass, (areas) => {
this._unsubAreas = subscribeAreaRegistry(this.hass.connection, (areas) => {
this._areas = areas;
});

View File

@ -90,9 +90,12 @@ class ZHADeviceCard extends LitElement {
this._userGivenName = this.device!.user_given_name;
}
if (!this._unsubAreas) {
this._unsubAreas = subscribeAreaRegistry(this.hass, (areas) => {
this._unsubAreas = subscribeAreaRegistry(
this.hass.connection,
(areas) => {
this._areas = areas;
});
}
);
}
super.update(changedProperties);
}

View File

@ -62,6 +62,10 @@ export class HuiEmptyStateCard extends LitElement implements LovelaceCard {
padding: 16px;
}
.card-actions a {
text-decoration: none;
}
mwc-button {
margin-left: -8px;
}

View File

@ -352,15 +352,15 @@ export const generateLovelaceConfig = async (
// so that we don't serve up stale data after changing areas.
if (!subscribedRegistries) {
subscribedRegistries = true;
subscribeAreaRegistry(hass, () => undefined);
subscribeDeviceRegistry(hass, () => undefined);
subscribeEntityRegistry(hass, () => undefined);
subscribeAreaRegistry(hass.connection, () => undefined);
subscribeDeviceRegistry(hass.connection, () => undefined);
subscribeEntityRegistry(hass.connection, () => undefined);
}
const [areas, devices, entities] = await Promise.all([
subscribeOne(hass, subscribeAreaRegistry),
subscribeOne(hass, subscribeDeviceRegistry),
subscribeOne(hass, subscribeEntityRegistry),
subscribeOne(hass.connection, subscribeAreaRegistry),
subscribeOne(hass.connection, subscribeDeviceRegistry),
subscribeOne(hass.connection, subscribeEntityRegistry),
]);
const registries = { areas, devices, entities };

View File

@ -310,6 +310,9 @@ class HUIRoot extends LitElement {
ha-app-layout {
min-height: 100%;
}
paper-menu-button {
padding: 0;
}
paper-tabs {
margin-left: 12px;
--paper-tabs-selection-bar-color: var(--text-primary-color, #fff);

View File

@ -581,6 +581,21 @@
"core": {
"header": "Configuration and Server Control",
"introduction": "Changing your configuration can be a tiresome process. We know. This section will try to make your life a little bit easier.",
"core_config": {
"edit_requires_storage": "Editor disabled because config stored in configuration.yaml.",
"location_name": "Name of your Home Assistant installation",
"latitude": "Latitude",
"longitude": "Longitude",
"elevation": "Elevation",
"elevation_meters": "meters",
"time_zone": "Time Zone",
"unit_system": "Unit System",
"unit_system_imperial": "Imperial",
"unit_system_metric": "Metric",
"imperial_example": "Fahrenheit, pounds",
"metric_example": "Celsius, kilograms",
"save_button": "Save"
},
"validation": {
"heading": "Configuration validation",
"introduction": "Validate your configuration if you recently made some changes to your configuration and want to make sure that it is all valid",
@ -1179,6 +1194,14 @@
"password_not_match": "Passwords don't match"
}
},
"core-config": {
"intro": "Hello {name}, welcome to Home Assistant. How would you like to name your home?",
"intro_location": "We would like to know where you live. This information will help with displaying information and setting up sun-based automations. This data is never shared outside of your network.",
"intro_location_detect": "We can help you fill in this information by making a one-time request to an external service.",
"location_name_default": "Home",
"button_detect": "Detect",
"finish": "Next"
},
"integration": {
"intro": "Devices and services are represented in Home Assistant as integrations. You can set them up now, or do it later from the configuration screen.",
"more_integrations": "More",

View File

@ -6807,6 +6807,11 @@ glogg@^1.0.0:
dependencies:
sparkles "^1.0.0"
google-timezones-json@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/google-timezones-json/-/google-timezones-json-1.0.2.tgz#6800000d7ebc2dd660611aad8c1c68196db78cab"
integrity sha512-UWXQ7BpSCW8erDespU2I4cri22xsKgwOCyhsJal0OJhi2tFpwJpsYNJt4vCiFPL1p2HzCGiS713LKpNR25n9Kg==
got@^5.0.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/got/-/got-5.7.1.tgz#5f81635a61e4a6589f180569ea4e381680a51f35"
@ -7242,10 +7247,10 @@ hoek@6.x.x:
resolved "https://registry.yarnpkg.com/hoek/-/hoek-6.1.3.tgz#73b7d33952e01fe27a38b0457294b79dd8da242c"
integrity sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==
home-assistant-js-websocket@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/home-assistant-js-websocket/-/home-assistant-js-websocket-4.1.2.tgz#dbcdb4b67df8d189d29bbf5603771d5bc80ef031"
integrity sha512-/I0m6FTDEq3LkzFc4tmgHJHTj9gWA6Wn/fgaa1ghIJJY0Yqb3x6whovN5pRNFsl6bnKzOCR+nmJ2ruVTBa5mVQ==
home-assistant-js-websocket@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/home-assistant-js-websocket/-/home-assistant-js-websocket-4.2.1.tgz#8acdf2a404b4204669213d8405cca027b8b1de1c"
integrity sha512-lF4owDhAAUY70FNvTzgg6MAEOpKbJDLsRDX3gW48muna03s3CRGQzbLmy621pJWK757CkXSW/rWbr34r3Wyi8Q==
homedir-polyfill@^1.0.0, homedir-polyfill@^1.0.1:
version "1.0.3"