Merge pull request #3502 from home-assistant/dev

20190820.0
This commit is contained in:
Paulus Schoutsen 2019-08-20 00:14:18 -07:00 committed by GitHub
commit 831b23347e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 1077 additions and 556 deletions

View File

@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name="home-assistant-frontend",
version="20190815.0",
version="20190820.0",
description="The Home Assistant frontend",
url="https://github.com/home-assistant/home-assistant-polymer",
author="The Home Assistant Authors",

View File

@ -1,8 +1,4 @@
import { HomeAssistant } from "../types";
import { createCollection } from "home-assistant-js-websocket";
import { debounce } from "../common/util/debounce";
import { LocalizeFunc } from "../common/translations/localize";
import { DataEntryFlowStep, DataEntryFlowProgress } from "./data_entry_flow";
export interface ConfigEntry {
entry_id: string;
@ -14,114 +10,34 @@ export interface ConfigEntry {
supports_options: boolean;
}
export const createConfigFlow = (hass: HomeAssistant, handler: string) =>
hass.callApi<DataEntryFlowStep>("POST", "config/config_entries/flow", {
handler,
});
export const fetchConfigFlow = (hass: HomeAssistant, flowId: string) =>
hass.callApi<DataEntryFlowStep>(
"GET",
`config/config_entries/flow/${flowId}`
);
export const handleConfigFlowStep = (
hass: HomeAssistant,
flowId: string,
data: { [key: string]: any }
) =>
hass.callApi<DataEntryFlowStep>(
"POST",
`config/config_entries/flow/${flowId}`,
data
);
export const deleteConfigFlow = (hass: HomeAssistant, flowId: string) =>
hass.callApi("DELETE", `config/config_entries/flow/${flowId}`);
export const getConfigFlowsInProgress = (hass: HomeAssistant) =>
hass.callApi<DataEntryFlowProgress[]>("GET", "config/config_entries/flow");
export const getConfigFlowHandlers = (hass: HomeAssistant) =>
hass.callApi<string[]>("GET", "config/config_entries/flow_handlers");
const fetchConfigFlowInProgress = (conn) =>
conn.sendMessagePromise({
type: "config/entity_registry/list",
});
const subscribeConfigFlowInProgressUpdates = (conn, store) =>
debounce(
conn.subscribeEvents(
() =>
fetchConfigFlowInProgress(conn).then((flows) =>
store.setState(flows, true)
),
500,
true
),
"config_entry_discovered"
);
export const subscribeConfigFlowInProgress = (
hass: HomeAssistant,
onChange: (flows: DataEntryFlowProgress[]) => void
) =>
createCollection<DataEntryFlowProgress[]>(
"_configFlowProgress",
fetchConfigFlowInProgress,
subscribeConfigFlowInProgressUpdates,
hass.connection,
onChange
);
export interface ConfigEntrySystemOptions {
disable_new_entities: boolean;
}
export const getConfigEntries = (hass: HomeAssistant) =>
hass.callApi<ConfigEntry[]>("GET", "config/config_entries/entry");
export const localizeConfigFlowTitle = (
localize: LocalizeFunc,
flow: DataEntryFlowProgress
) => {
const placeholders = flow.context.title_placeholders || {};
const placeholderKeys = Object.keys(placeholders);
if (placeholderKeys.length === 0) {
return localize(`component.${flow.handler}.config.title`);
}
const args: string[] = [];
placeholderKeys.forEach((key) => {
args.push(key);
args.push(placeholders[key]);
});
return localize(`component.${flow.handler}.config.flow_title`, ...args);
};
export const deleteConfigEntry = (hass: HomeAssistant, configEntryId: string) =>
hass.callApi<{
require_restart: boolean;
}>("DELETE", `config/config_entries/entry/${configEntryId}`);
// Options flow
export const createOptionsFlow = (hass: HomeAssistant, handler: string) =>
hass.callApi<DataEntryFlowStep>(
"POST",
"config/config_entries/options/flow",
{
handler,
}
);
export const fetchOptionsFlow = (hass: HomeAssistant, flowId: string) =>
hass.callApi<DataEntryFlowStep>(
"GET",
`config/config_entries/options/flow/${flowId}`
);
export const handleOptionsFlowStep = (
export const getConfigEntrySystemOptions = (
hass: HomeAssistant,
flowId: string,
data: { [key: string]: any }
configEntryId: string
) =>
hass.callApi<DataEntryFlowStep>(
"POST",
`config/config_entries/options/flow/${flowId}`,
data
);
hass.callWS<ConfigEntrySystemOptions>({
type: "config_entries/system_options/list",
entry_id: configEntryId,
});
export const deleteOptionsFlow = (hass: HomeAssistant, flowId: string) =>
hass.callApi("DELETE", `config/config_entries/options/flow/${flowId}`);
export const updateConfigEntrySystemOptions = (
hass: HomeAssistant,
configEntryId: string,
params: Partial<ConfigEntrySystemOptions>
) =>
hass.callWS({
type: "config_entries/system_options/update",
entry_id: configEntryId,
...params,
});

83
src/data/config_flow.ts Normal file
View File

@ -0,0 +1,83 @@
import { HomeAssistant } from "../types";
import { DataEntryFlowStep, DataEntryFlowProgress } from "./data_entry_flow";
import { debounce } from "../common/util/debounce";
import { createCollection } from "home-assistant-js-websocket";
import { LocalizeFunc } from "../common/translations/localize";
export const createConfigFlow = (hass: HomeAssistant, handler: string) =>
hass.callApi<DataEntryFlowStep>("POST", "config/config_entries/flow", {
handler,
});
export const fetchConfigFlow = (hass: HomeAssistant, flowId: string) =>
hass.callApi<DataEntryFlowStep>(
"GET",
`config/config_entries/flow/${flowId}`
);
export const handleConfigFlowStep = (
hass: HomeAssistant,
flowId: string,
data: { [key: string]: any }
) =>
hass.callApi<DataEntryFlowStep>(
"POST",
`config/config_entries/flow/${flowId}`,
data
);
export const deleteConfigFlow = (hass: HomeAssistant, flowId: string) =>
hass.callApi("DELETE", `config/config_entries/flow/${flowId}`);
export const getConfigFlowsInProgress = (hass: HomeAssistant) =>
hass.callApi<DataEntryFlowProgress[]>("GET", "config/config_entries/flow");
export const getConfigFlowHandlers = (hass: HomeAssistant) =>
hass.callApi<string[]>("GET", "config/config_entries/flow_handlers");
const fetchConfigFlowInProgress = (conn) =>
conn.sendMessagePromise({
type: "config_entries/flow/progress",
});
const subscribeConfigFlowInProgressUpdates = (conn, store) =>
conn.subscribeEvents(
debounce(
() =>
fetchConfigFlowInProgress(conn).then((flows) =>
store.setState(flows, true)
),
500,
true
),
"config_entry_discovered"
);
export const subscribeConfigFlowInProgress = (
hass: HomeAssistant,
onChange: (flows: DataEntryFlowProgress[]) => void
) =>
createCollection<DataEntryFlowProgress[]>(
"_configFlowProgress",
fetchConfigFlowInProgress,
subscribeConfigFlowInProgressUpdates,
hass.connection,
onChange
);
export const localizeConfigFlowTitle = (
localize: LocalizeFunc,
flow: DataEntryFlowProgress
) => {
const placeholders = flow.context.title_placeholders || {};
const placeholderKeys = Object.keys(placeholders);
if (placeholderKeys.length === 0) {
return localize(`component.${flow.handler}.config.title`);
}
const args: string[] = [];
placeholderKeys.forEach((key) => {
args.push(key);
args.push(placeholders[key]);
});
return localize(`component.${flow.handler}.config.flow_title`, ...args);
};

View File

@ -9,12 +9,13 @@ export interface EntityRegistryEntry {
platform: string;
config_entry_id?: string;
device_id?: string;
disabled_by?: string;
disabled_by: string | null;
}
export interface EntityRegistryEntryUpdateParams {
name: string | null;
new_entity_id: string;
name?: string | null;
disabled_by?: string | null;
new_entity_id?: string;
}
export const computeEntityRegistryName = (

31
src/data/options_flow.ts Normal file
View File

@ -0,0 +1,31 @@
import { HomeAssistant } from "../types";
import { DataEntryFlowStep } from "./data_entry_flow";
export const createOptionsFlow = (hass: HomeAssistant, handler: string) =>
hass.callApi<DataEntryFlowStep>(
"POST",
"config/config_entries/options/flow",
{
handler,
}
);
export const fetchOptionsFlow = (hass: HomeAssistant, flowId: string) =>
hass.callApi<DataEntryFlowStep>(
"GET",
`config/config_entries/options/flow/${flowId}`
);
export const handleOptionsFlowStep = (
hass: HomeAssistant,
flowId: string,
data: { [key: string]: any }
) =>
hass.callApi<DataEntryFlowStep>(
"POST",
`config/config_entries/options/flow/${flowId}`,
data
);
export const deleteOptionsFlow = (hass: HomeAssistant, flowId: string) =>
hass.callApi("DELETE", `config/config_entries/options/flow/${flowId}`);

View File

@ -0,0 +1,175 @@
import {
LitElement,
html,
css,
CSSResult,
TemplateResult,
customElement,
property,
} from "lit-element";
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
import "@polymer/paper-input/paper-input";
import "../../components/dialog/ha-paper-dialog";
import { HomeAssistant } from "../../types";
import { ConfigEntrySystemOptionsDialogParams } from "./show-dialog-config-entry-system-options";
import {
getConfigEntrySystemOptions,
updateConfigEntrySystemOptions,
} from "../../data/config_entries";
import { PolymerChangedEvent } from "../../polymer-types";
import { haStyleDialog } from "../../resources/styles";
@customElement("dialog-config-entry-system-options")
class DialogConfigEntrySystemOptions extends LitElement {
@property() public hass!: HomeAssistant;
@property() private _disableNewEntities!: boolean;
@property() private _error?: string;
@property() private _params?: ConfigEntrySystemOptionsDialogParams;
@property() private _loading?: boolean;
@property() private _submitting?: boolean;
public async showDialog(
params: ConfigEntrySystemOptionsDialogParams
): Promise<void> {
this._params = params;
this._error = undefined;
this._loading = true;
const systemOptions = await getConfigEntrySystemOptions(
this.hass,
params.entry.entry_id
);
this._loading = false;
this._disableNewEntities = systemOptions.disable_new_entities;
await this.updateComplete;
}
protected render(): TemplateResult | void {
if (!this._params) {
return html``;
}
return html`
<ha-paper-dialog
with-backdrop
opened
@opened-changed="${this._openedChanged}"
>
<h2>
${this.hass.localize("ui.dialogs.config_entry_system_options.title")}
</h2>
<paper-dialog-scrollable>
${this._loading
? html`
<div class="init-spinner">
<paper-spinner-lite active></paper-spinner-lite>
</div>
`
: html`
${this._error
? html`
<div class="error">${this._error}</div>
`
: ""}
<div class="form">
<paper-toggle-button
.checked=${!this._disableNewEntities}
@checked-changed=${this._disableNewEntitiesChanged}
.disabled=${this._submitting}
>
<div>
${this.hass.localize(
"ui.dialogs.config_entry_system_options.enable_new_entities_label"
)}
</div>
<div class="secondary">
${this.hass.localize(
"ui.dialogs.config_entry_system_options.enable_new_entities_description"
)}
</div>
</paper-toggle-button>
</div>
`}
</paper-dialog-scrollable>
${!this._loading
? html`
<div class="paper-dialog-buttons">
<mwc-button
@click="${this._updateEntry}"
.disabled=${this._submitting}
>
${this.hass.localize(
"ui.panel.config.entity_registry.editor.update"
)}
</mwc-button>
</div>
`
: ""}
</ha-paper-dialog>
`;
}
private _disableNewEntitiesChanged(ev: PolymerChangedEvent<boolean>): void {
this._error = undefined;
this._disableNewEntities = !ev.detail.value;
}
private async _updateEntry(): Promise<void> {
this._submitting = true;
try {
await updateConfigEntrySystemOptions(
this.hass,
this._params!.entry.entry_id,
{
disable_new_entities: this._disableNewEntities,
}
);
this._params = undefined;
} catch (err) {
this._error = err.message || "Unknown error";
} finally {
this._submitting = false;
}
}
private _openedChanged(ev: PolymerChangedEvent<boolean>): void {
if (!(ev.detail as any).value) {
this._params = undefined;
}
}
static get styles(): CSSResult[] {
return [
haStyleDialog,
css`
ha-paper-dialog {
min-width: 400px;
max-width: 500px;
}
.init-spinner {
padding: 50px 100px;
text-align: center;
}
.form {
padding-bottom: 24px;
color: var(--primary-text-color);
}
.secondary {
color: var(--secondary-text-color);
}
.error {
color: var(--google-red-500);
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-config-entry-system-options": DialogConfigEntrySystemOptions;
}
}

View File

@ -0,0 +1,24 @@
import { fireEvent } from "../../common/dom/fire_event";
import { ConfigEntry } from "../../data/config_entries";
export interface ConfigEntrySystemOptionsDialogParams {
entry: ConfigEntry;
// updateEntry: (
// updates: Partial<EntityRegistryEntryUpdateParams>
// ) => Promise<unknown>;
// removeEntry: () => Promise<boolean>;
}
export const loadConfigEntrySystemOptionsDialog = () =>
import(/* webpackChunkName: "config-entry-system-options" */ "./dialog-config-entry-system-options");
export const showConfigEntrySystemOptionsDialog = (
element: HTMLElement,
systemLogDetailParams: ConfigEntrySystemOptionsDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-config-entry-system-options",
dialogImport: loadConfigEntrySystemOptionsDialog,
dialogParams: systemLogDetailParams,
});
};

View File

@ -4,7 +4,7 @@ import {
handleConfigFlowStep,
deleteConfigFlow,
createConfigFlow,
} from "../../data/config_entries";
} from "../../data/config_flow";
import { html } from "lit-element";
import { localizeKey } from "../../common/translations/localize";
import {

View File

@ -3,14 +3,14 @@ import {
handleOptionsFlowStep,
deleteOptionsFlow,
createOptionsFlow,
ConfigEntry,
} from "../../data/config_entries";
} from "../../data/options_flow";
import { html } from "lit-element";
import { localizeKey } from "../../common/translations/localize";
import {
showFlowDialog,
loadDataEntryFlowDialog,
} from "./show-dialog-data-entry-flow";
import { ConfigEntry } from "../../data/config_entries";
export const loadOptionsFlowDialog = loadDataEntryFlowDialog;

View File

@ -14,16 +14,16 @@ import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item";
import "@material/mwc-button/mwc-button";
import "../../../components/dialog/ha-paper-dialog";
import "../../components/dialog/ha-paper-dialog";
import { DeviceRegistryDetailDialogParams } from "./show-dialog-device-registry-detail";
import { PolymerChangedEvent } from "../../../polymer-types";
import { haStyleDialog } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import { PolymerChangedEvent } from "../../polymer-types";
import { haStyleDialog } from "../../resources/styles";
import { HomeAssistant } from "../../types";
import {
subscribeAreaRegistry,
AreaRegistryEntry,
} from "../../../data/area_registry";
} from "../../data/area_registry";
@customElement("dialog-device-registry-detail")
class DialogDeviceRegistryDetail extends LitElement {
@ -74,7 +74,7 @@ class DialogDeviceRegistryDetail extends LitElement {
opened
@opened-changed="${this._openedChanged}"
>
<h2>${device.name}</h2>
<h2>${device.name || "Unnamed device"}</h2>
<paper-dialog-scrollable>
${this._error
? html`

View File

@ -1,8 +1,8 @@
import { fireEvent } from "../../../common/dom/fire_event";
import { fireEvent } from "../../common/dom/fire_event";
import {
DeviceRegistryEntry,
DeviceRegistryEntryMutableParams,
} from "../../../data/device_registry";
} from "../../data/device_registry";
export interface DeviceRegistryDetailDialogParams {
device: DeviceRegistryEntry;

View File

@ -14,12 +14,7 @@ import {
showConfigFlowDialog,
} from "../dialogs/config-flow/show-dialog-config-flow";
import { HomeAssistant } from "../types";
import {
getConfigFlowsInProgress,
getConfigEntries,
ConfigEntry,
localizeConfigFlowTitle,
} from "../data/config_entries";
import { getConfigEntries, ConfigEntry } from "../data/config_entries";
import { compare } from "../common/string/compare";
import "./integration-badge";
import { LocalizeFunc } from "../common/translations/localize";
@ -28,6 +23,10 @@ import { fireEvent } from "../common/dom/fire_event";
import { onboardIntegrationStep } from "../data/onboarding";
import { genClientId } from "home-assistant-js-websocket";
import { DataEntryFlowProgress } from "../data/data_entry_flow";
import {
localizeConfigFlowTitle,
getConfigFlowsInProgress,
} from "../data/config_flow";
@customElement("onboarding-integrations")
class OnboardingIntegrations extends LitElement {

View File

@ -2,12 +2,13 @@ import {
LitElement,
html,
css,
PropertyDeclarations,
CSSResult,
TemplateResult,
property,
} from "lit-element";
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-toggle-button/paper-toggle-button";
import "../../../components/dialog/ha-paper-dialog";
@ -20,21 +21,13 @@ import { HassEntity } from "home-assistant-js-websocket";
import computeStateName from "../../../common/entity/compute_state_name";
class DialogEntityRegistryDetail extends LitElement {
public hass!: HomeAssistant;
private _name!: string;
private _entityId!: string;
private _error?: string;
private _params?: EntityRegistryDetailDialogParams;
private _submitting?: boolean;
static get properties(): PropertyDeclarations {
return {
_error: {},
_name: {},
_entityId: {},
_params: {},
};
}
@property() public hass!: HomeAssistant;
@property() private _name!: string;
@property() private _entityId!: string;
@property() private _disabledBy!: string | null;
@property() private _error?: string;
@property() private _params?: EntityRegistryDetailDialogParams;
@property() private _submitting?: boolean;
public async showDialog(
params: EntityRegistryDetailDialogParams
@ -43,6 +36,7 @@ class DialogEntityRegistryDetail extends LitElement {
this._error = undefined;
this._name = this._params.entry.name || "";
this._entityId = this._params.entry.entity_id;
this._disabledBy = this._params.entry.disabled_by;
await this.updateComplete;
}
@ -62,7 +56,11 @@ class DialogEntityRegistryDetail extends LitElement {
opened
@opened-changed="${this._openedChanged}"
>
<h2>${entry.entity_id}</h2>
<h2>
${stateObj
? computeStateName(stateObj)
: entry.name || entry.entity_id}
</h2>
<paper-dialog-scrollable>
${!stateObj
? html`
@ -96,6 +94,35 @@ class DialogEntityRegistryDetail extends LitElement {
.invalid=${invalidDomainUpdate}
.disabled=${this._submitting}
></paper-input>
<div class="row">
<paper-toggle-button
.checked=${!this._disabledBy}
@checked-changed=${this._disabledByChanged}
>
<div>
<div>
${this.hass.localize(
"ui.panel.config.entity_registry.editor.enabled_label"
)}
</div>
<div class="secondary">
${this._disabledBy && this._disabledBy !== "user"
? this.hass.localize(
"ui.panel.config.entity_registry.editor.enabled_cause",
"cause",
this.hass.localize(
`config_entry.disabled_by.${this._disabledBy}`
)
)
: ""}
${this.hass.localize(
"ui.panel.config.entity_registry.editor.enabled_description"
)}
<br />Note: this might not work yet with all integrations.
</div>
</div>
</paper-toggle-button>
</div>
</div>
</paper-dialog-scrollable>
<div class="paper-dialog-buttons">
@ -136,6 +163,7 @@ class DialogEntityRegistryDetail extends LitElement {
try {
await this._params!.updateEntry({
name: this._name.trim() || null,
disabled_by: this._disabledBy,
new_entity_id: this._entityId.trim(),
});
this._params = undefined;
@ -162,6 +190,9 @@ class DialogEntityRegistryDetail extends LitElement {
this._params = undefined;
}
}
private _disabledByChanged(ev: PolymerChangedEvent<boolean>): void {
this._disabledBy = ev.detail.value ? null : "user";
}
static get styles(): CSSResult[] {
return [
@ -169,6 +200,7 @@ class DialogEntityRegistryDetail extends LitElement {
css`
ha-paper-dialog {
min-width: 400px;
max-width: 450px;
}
.form {
padding-bottom: 24px;
@ -179,6 +211,13 @@ class DialogEntityRegistryDetail extends LitElement {
.error {
color: var(--google-red-500);
}
.row {
margin-top: 8px;
color: var(--primary-text-color);
}
.secondary {
color: var(--secondary-text-color);
}
`,
];
}

View File

@ -31,6 +31,7 @@ import {
} from "./show-dialog-entity-registry-detail";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { compare } from "../../../common/string/compare";
import { classMap } from "lit-html/directives/class-map";
class HaConfigEntityRegistry extends LitElement {
@property() public hass!: HomeAssistant;
@ -82,7 +83,11 @@ class HaConfigEntityRegistry extends LitElement {
${this._entities.map((entry) => {
const state = this.hass!.states[entry.entity_id];
return html`
<paper-icon-item @click=${this._openEditEntry} .entry=${entry}>
<paper-icon-item
@click=${this._openEditEntry}
.entry=${entry}
class=${classMap({ "disabled-entry": !!entry.disabled_by })}
>
<ha-icon
slot="item-icon"
.icon=${state
@ -92,15 +97,20 @@ class HaConfigEntityRegistry extends LitElement {
<paper-item-body two-line>
<div class="name">
${computeEntityRegistryName(this.hass!, entry) ||
this.hass!.localize(
"ui.panel.config.entity_registry.picker.unavailable"
)}
`(${this.hass!.localize("state.default.unavailable")})`}
</div>
<div class="secondary entity-id">
${entry.entity_id}
</div>
</paper-item-body>
<div class="platform">${entry.platform}</div>
<div class="platform">
${entry.platform}
${entry.disabled_by
? html`
<br />(disabled)
`
: ""}
</div>
</paper-icon-item>
`;
})}
@ -171,15 +181,23 @@ Deleting an entry will not remove the entity from Home Assistant. To do this, yo
color: var(--primary-color);
}
ha-card {
margin-bottom: 24px;
direction: ltr;
overflow: hidden;
}
paper-icon-item {
cursor: pointer;
color: var(--primary-text-color);
}
ha-icon {
margin-left: 8px;
}
.platform {
text-align: right;
margin: 0 0 0 8px;
}
.disabled-entry {
color: var(--secondary-text-color);
}
`;
}
}

View File

@ -3,13 +3,13 @@ import "@polymer/paper-item/paper-item-body";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../components/ha-card";
import "../../../layouts/hass-subpage";
import "../../../../components/ha-card";
import "../../../../layouts/hass-subpage";
import { EventsMixin } from "../../../mixins/events-mixin";
import LocalizeMixIn from "../../../mixins/localize-mixin";
import "../../../components/entity/state-badge";
import { computeEntityRegistryName } from "../../../data/entity_registry";
import { EventsMixin } from "../../../../mixins/events-mixin";
import LocalizeMixIn from "../../../../mixins/localize-mixin";
import "../../../../components/entity/state-badge";
import { computeEntityRegistryName } from "../../../../data/entity_registry";
/*
* @appliesMixin LocalizeMixIn

View File

@ -0,0 +1,204 @@
import memoizeOne from "memoize-one";
import "../../../../layouts/hass-subpage";
import "../../../../layouts/hass-error-screen";
import "../../../../components/entity/state-badge";
import { compare } from "../../../../common/string/compare";
import "./ha-device-card";
import "./ha-ce-entities-card";
import { showOptionsFlowDialog } from "../../../../dialogs/config-flow/show-dialog-options-flow";
import { property, LitElement, CSSResult, css, html } from "lit-element";
import { navigate } from "../../../../common/navigate";
import { HomeAssistant } from "../../../../types";
import {
ConfigEntry,
deleteConfigEntry,
} from "../../../../data/config_entries";
import { EntityRegistryEntry } from "../../../../data/entity_registry";
import { DeviceRegistryEntry } from "../../../../data/device_registry";
import { AreaRegistryEntry } from "../../../../data/area_registry";
import { fireEvent } from "../../../../common/dom/fire_event";
import { showConfigEntrySystemOptionsDialog } from "../../../../dialogs/config-entry-system-options/show-dialog-config-entry-system-options";
class HaConfigEntryPage extends LitElement {
@property() public hass!: HomeAssistant;
@property() public narrow!: boolean;
@property() public configEntryId!: string;
@property() public configEntries!: ConfigEntry[];
@property() public entityRegistryEntries!: EntityRegistryEntry[];
@property() public deviceRegistryEntries!: DeviceRegistryEntry[];
@property() public areas!: AreaRegistryEntry[];
private get _configEntry(): ConfigEntry | undefined {
return this.configEntries
? this.configEntries.find(
(entry) => entry.entry_id === this.configEntryId
)
: undefined;
}
private _computeConfigEntryDevices = memoizeOne(
(configEntry: ConfigEntry, devices: DeviceRegistryEntry[]) => {
if (!devices) {
return [];
}
return devices
.filter((device) =>
device.config_entries.includes(configEntry.entry_id)
)
.sort(
(dev1, dev2) =>
Number(!!dev1.via_device_id) - Number(!!dev2.via_device_id) ||
compare(dev1.name || "", dev2.name || "")
);
}
);
private _computeNoDeviceEntities = memoizeOne(
(configEntry: ConfigEntry, entities: EntityRegistryEntry[]) => {
if (!entities) {
return [];
}
return entities.filter(
(ent) => !ent.device_id && ent.config_entry_id === configEntry.entry_id
);
}
);
protected render() {
const configEntry = this._configEntry;
if (!configEntry) {
return html`
<hass-error-screen error="Integration not found."></hass-error-screen>
`;
}
const configEntryDevices = this._computeConfigEntryDevices(
configEntry,
this.deviceRegistryEntries
);
const noDeviceEntities = this._computeNoDeviceEntities(
configEntry,
this.entityRegistryEntries
);
return html`
<hass-subpage .header=${configEntry.title}>
${configEntry.supports_options
? html`
<paper-icon-button
slot="toolbar-icon"
icon="hass:settings"
@click=${this._showSettings}
></paper-icon-button>
`
: ""}
<paper-icon-button
slot="toolbar-icon"
icon="hass:message-settings-variant"
@click=${this._showSystemOptions}
></paper-icon-button>
<paper-icon-button
slot="toolbar-icon"
icon="hass:delete"
@click=${this._removeEntry}
></paper-icon-button>
<div class="content">
${configEntryDevices.length === 0 && noDeviceEntities.length === 0
? html`
<p>
${this.hass.localize(
"ui.panel.config.integrations.config_entry.no_devices"
)}
</p>
`
: ""}
${configEntryDevices.map(
(device) => html`
<ha-device-card
class="card"
.hass=${this.hass}
.areas=${this.areas}
.devices=${this.deviceRegistryEntries}
.device=${device}
.entities=${this.entityRegistryEntries}
.narrow=${this.narrow}
></ha-device-card>
`
)}
${noDeviceEntities.length > 0
? html`
<ha-ce-entities-card
class="card"
.heading=${this.hass.localize(
"ui.panel.config.integrations.config_entry.no_device"
)}
.entities=${noDeviceEntities}
.hass=${this.hass}
.narrow=${this.narrow}
></ha-ce-entities-card>
`
: ""}
</div>
</hass-subpage>
`;
}
private _showSettings() {
showOptionsFlowDialog(this, this._configEntry!);
}
private _showSystemOptions() {
showConfigEntrySystemOptionsDialog(this, {
entry: this._configEntry!,
});
}
private _removeEntry() {
if (
!confirm(
this.hass.localize(
"ui.panel.config.integrations.config_entry.delete_confirm"
)
)
) {
return;
}
deleteConfigEntry(this.hass, this.configEntryId).then((result) => {
fireEvent(this, "hass-reload-entries");
if (result.require_restart) {
alert(
this.hass.localize(
"ui.panel.config.integrations.config_entry.restart_confirm"
)
);
}
navigate(this, "/config/integrations/dashboard", true);
});
}
static get styles(): CSSResult {
return css`
.content {
display: flex;
flex-wrap: wrap;
padding: 4px;
justify-content: center;
}
.card {
box-sizing: border-box;
display: flex;
flex: 1 0 300px;
min-width: 0;
max-width: 500px;
padding: 8px;
}
`;
}
}
customElements.define("ha-config-entry-page", HaConfigEntryPage);

View File

@ -6,24 +6,23 @@ import "@polymer/paper-listbox/paper-listbox";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../components/ha-card";
import "../../../layouts/hass-subpage";
import "../../../../components/ha-card";
import "../../../../layouts/hass-subpage";
import { EventsMixin } from "../../../mixins/events-mixin";
import LocalizeMixin from "../../../mixins/localize-mixin";
import computeStateName from "../../../common/entity/compute_state_name";
import "../../../components/entity/state-badge";
import { compare } from "../../../common/string/compare";
import { EventsMixin } from "../../../../mixins/events-mixin";
import LocalizeMixin from "../../../../mixins/localize-mixin";
import computeStateName from "../../../../common/entity/compute_state_name";
import "../../../../components/entity/state-badge";
import { compare } from "../../../../common/string/compare";
import {
subscribeDeviceRegistry,
updateDeviceRegistryEntry,
} from "../../../data/device_registry";
import { subscribeAreaRegistry } from "../../../data/area_registry";
} from "../../../../data/device_registry";
import { subscribeAreaRegistry } from "../../../../data/area_registry";
import {
showDeviceRegistryDetailDialog,
loadDeviceRegistryDetailDialog,
} from "./show-dialog-device-registry-detail";
showDeviceRegistryDetailDialog,
} from "../../../../dialogs/device-registry-detail/show-dialog-device-registry-detail";
function computeEntityName(hass, entity) {
if (entity.name) return entity.name;

View File

@ -23,7 +23,7 @@ import {
loadConfigFlowDialog,
showConfigFlowDialog,
} from "../../../dialogs/config-flow/show-dialog-config-flow";
import { localizeConfigFlowTitle } from "../../../data/config_entries";
import { localizeConfigFlowTitle } from "../../../data/config_flow";
/*
* @appliesMixin LocalizeMixin
@ -113,7 +113,7 @@ class HaConfigManagerDashboard extends LocalizeMixin(
</div>
</template>
<template is="dom-repeat" items="[[entries]]">
<a href="/config/integrations/[[item.entry_id]]">
<a href="/config/integrations/config_entry/[[item.entry_id]]">
<paper-item>
<paper-item-body two-line>
<div>
@ -176,8 +176,6 @@ class HaConfigManagerDashboard extends LocalizeMixin(
*/
progress: Array,
handlers: Array,
rtl: {
type: Boolean,
reflectToAttribute: true,

View File

@ -1,184 +0,0 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../layouts/hass-subpage";
import "../../../components/entity/state-badge";
import { compare } from "../../../common/string/compare";
import "./ha-device-card";
import "./ha-ce-entities-card";
import { EventsMixin } from "../../../mixins/events-mixin";
import LocalizeMixin from "../../../mixins/localize-mixin";
import NavigateMixin from "../../../mixins/navigate-mixin";
import { showOptionsFlowDialog } from "../../../dialogs/config-flow/show-dialog-options-flow";
class HaConfigEntryPage extends NavigateMixin(
EventsMixin(LocalizeMixin(PolymerElement))
) {
static get template() {
return html`
<style>
.content {
display: flex;
flex-wrap: wrap;
padding: 4px;
justify-content: center;
}
.card {
box-sizing: border-box;
display: flex;
flex: 1 0 300px;
min-width: 0;
max-width: 500px;
padding: 8px;
}
</style>
<hass-subpage header="[[configEntry.title]]">
<template is="dom-if" if="[[configEntry.supports_options]]">
<paper-icon-button
slot="toolbar-icon"
icon="hass:settings"
on-click="_showSettings"
></paper-icon-button>
</template>
<paper-icon-button
slot="toolbar-icon"
icon="hass:delete"
on-click="_removeEntry"
></paper-icon-button>
<div class="content">
<template
is="dom-if"
if="[[_computeIsEmpty(_configEntryDevices, _noDeviceEntities)]]"
>
<p>
[[localize('ui.panel.config.integrations.config_entry.no_devices')]]
</p>
</template>
<template is="dom-repeat" items="[[_configEntryDevices]]" as="device">
<ha-device-card
class="card"
hass="[[hass]]"
areas="[[areas]]"
devices="[[devices]]"
device="[[device]]"
entities="[[entities]]"
narrow="[[narrow]]"
></ha-device-card>
</template>
<template is="dom-if" if="[[_noDeviceEntities.length]]">
<ha-ce-entities-card
class="card"
heading="[[localize('ui.panel.config.integrations.config_entry.no_device')]]"
entities="[[_noDeviceEntities]]"
hass="[[hass]]"
narrow="[[narrow]]"
></ha-ce-entities-card>
</template>
</div>
</hass-subpage>
`;
}
static get properties() {
return {
hass: Object,
isWide: Boolean,
narrow: Boolean,
configEntry: {
type: Object,
value: null,
},
_configEntryDevices: {
type: Array,
computed: "_computeConfigEntryDevices(configEntry, devices)",
},
/**
* All entity registry entries for this config entry that do not belong
* to a device.
*/
_noDeviceEntities: {
type: Array,
computed: "_computeNoDeviceEntities(configEntry, entities)",
},
/**
* Area registry entries
*/
areas: Array,
/**
* Device registry entries
*/
devices: Array,
/**
* Existing entries.
*/
entries: Array,
/**
* Entity Registry entries.
*/
entities: Array,
};
}
_computeConfigEntryDevices(configEntry, devices) {
if (!devices) return [];
return devices
.filter((device) => device.config_entries.includes(configEntry.entry_id))
.sort(
(dev1, dev2) =>
!!dev1.via_device_id - !!dev2.via_device_id ||
compare(dev1.name, dev2.name)
);
}
_computeNoDeviceEntities(configEntry, entities) {
if (!entities) return [];
return entities.filter(
(ent) => !ent.device_id && ent.config_entry_id === configEntry.entry_id
);
}
_computeIsEmpty(configEntryDevices, noDeviceEntities) {
return configEntryDevices.length === 0 && noDeviceEntities.length === 0;
}
_showSettings() {
showOptionsFlowDialog(this, this.configEntry);
}
_removeEntry() {
if (
!confirm(
this.localize(
"ui.panel.config.integrations.config_entry.delete_confirm"
)
)
)
return;
const entryId = this.configEntry.entry_id;
this.hass
.callApi("delete", `config/config_entries/entry/${entryId}`)
.then((result) => {
this.fire("hass-reload-entries");
if (result.require_restart) {
alert(
this.localize(
"ui.panel.config.integrations.config_entry.restart_confirm"
)
);
}
this.navigate("/config/integrations/dashboard", true);
});
}
}
customElements.define("ha-config-entry-page", HaConfigEntryPage);

View File

@ -1,161 +0,0 @@
import "@polymer/app-route/app-route";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { Debouncer } from "@polymer/polymer/lib/utils/debounce";
import { timeOut } from "@polymer/polymer/lib/utils/async";
import "./ha-config-entries-dashboard";
import "./ha-config-entry-page";
import NavigateMixin from "../../../mixins/navigate-mixin";
import { compare } from "../../../common/string/compare";
import { subscribeAreaRegistry } from "../../../data/area_registry";
class HaConfigIntegrations extends NavigateMixin(PolymerElement) {
static get template() {
return html`
<app-route
route="[[route]]"
pattern="/:page"
data="{{_routeData}}"
tail="{{_routeTail}}"
></app-route>
<template is="dom-if" if="[[_configEntry]]">
<ha-config-entry-page
hass="[[hass]]"
config-entry="[[_configEntry]]"
areas="[[_areas]]"
entries="[[_entries]]"
entities="[[_entities]]"
devices="[[_devices]]"
narrow="[[narrow]]"
></ha-config-entry-page>
</template>
<template is="dom-if" if="[[!_configEntry]]">
<ha-config-entries-dashboard
hass="[[hass]]"
entries="[[_entries]]"
entities="[[_entities]]"
handlers="[[_handlers]]"
progress="[[_progress]]"
></ha-config-entries-dashboard>
</template>
`;
}
static get properties() {
return {
hass: Object,
isWide: Boolean,
narrow: Boolean,
route: Object,
_configEntry: {
type: Object,
computed: "_computeConfigEntry(_routeData, _entries)",
},
/**
* Existing entries.
*/
_entries: Array,
/**
* Entity Registry entries.
*/
_entities: Array,
/**
* Device Registry entries.
*/
_devices: Array,
/**
* Area Registry entries.
*/
_areas: Array,
/**
* Current flows that are in progress and have not been started by a user.
* For example, can be discovered devices that require more config.
*/
_progress: Array,
_handlers: Array,
_routeData: Object,
_routeTail: Object,
};
}
ready() {
super.ready();
this.addEventListener("hass-reload-entries", () => this._loadData());
}
connectedCallback() {
super.connectedCallback();
this._loadData();
this._unsubAreas = subscribeAreaRegistry(this.hass.connection, (areas) => {
this._areas = areas;
});
this.hass.connection
.subscribeEvents(() => {
this._debouncer = Debouncer.debounce(
this._debouncer,
timeOut.after(500),
() => this._loadData()
);
}, "config_entry_discovered")
.then((unsub) => {
this._unsubEvents = unsub;
});
}
disconnectedCallback() {
super.disconnectedCallback();
if (this._unsubEvents) this._unsubEvents();
if (this._unsubAreas) this._unsubAreas();
}
_loadData() {
this.hass.callApi("get", "config/config_entries/entry").then((entries) => {
this._entries = entries.sort((conf1, conf2) =>
compare(conf1.title, conf2.title)
);
});
this.hass.callApi("get", "config/config_entries/flow").then((progress) => {
this._progress = progress;
});
this.hass
.callApi("get", "config/config_entries/flow_handlers")
.then((handlers) => {
this._handlers = handlers;
});
this.hass
.callWS({ type: "config/entity_registry/list" })
.then((entities) => {
this._entities = entities;
});
this.hass
.callWS({ type: "config/device_registry/list" })
.then((devices) => {
this._devices = devices;
});
}
_computeConfigEntry(routeData, entries) {
return (
!!entries &&
!!routeData &&
entries.find((ent) => ent.entry_id === routeData.page)
);
}
}
customElements.define("ha-config-integrations", HaConfigIntegrations);

View File

@ -0,0 +1,140 @@
import "@polymer/app-route/app-route";
import "./ha-config-entries-dashboard";
import "./config-entry/ha-config-entry-page";
import { compare } from "../../../common/string/compare";
import {
subscribeAreaRegistry,
AreaRegistryEntry,
} from "../../../data/area_registry";
import {
HassRouterPage,
RouterOptions,
} from "../../../layouts/hass-router-page";
import { property, customElement, PropertyValues } from "lit-element";
import { HomeAssistant } from "../../../types";
import { ConfigEntry, getConfigEntries } from "../../../data/config_entries";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../../../data/entity_registry";
import {
DeviceRegistryEntry,
subscribeDeviceRegistry,
} from "../../../data/device_registry";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { DataEntryFlowProgress } from "../../../data/data_entry_flow";
import { subscribeConfigFlowInProgress } from "../../../data/config_flow";
declare global {
interface HASSDomEvents {
"hass-reload-entries": undefined;
}
}
@customElement("ha-config-integrations")
class HaConfigIntegrations extends HassRouterPage {
@property() public hass!: HomeAssistant;
@property() public narrow!: boolean;
protected routerOptions: RouterOptions = {
defaultPage: "dashboard",
preloadAll: true,
routes: {
dashboard: {
tag: "ha-config-entries-dashboard",
},
config_entry: {
tag: "ha-config-entry-page",
},
},
};
@property() private _configEntries?: ConfigEntry[];
@property() private _configEntriesInProgress?: DataEntryFlowProgress[];
@property() private _entityRegistryEntries?: EntityRegistryEntry[];
@property() private _deviceRegistryEntries?: DeviceRegistryEntry[];
@property() private _areas?: AreaRegistryEntry[];
private _unsubs?: UnsubscribeFunc[];
public connectedCallback() {
super.connectedCallback();
if (!this.hass) {
return;
}
this._loadData();
}
public disconnectedCallback() {
super.disconnectedCallback();
if (this._unsubs) {
while (this._unsubs.length) {
this._unsubs.pop()!();
}
this._unsubs = undefined;
}
}
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
this.addEventListener("hass-reload-entries", () => this._loadData());
}
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (!this._unsubs && changedProps.has("hass")) {
this._loadData();
}
}
protected updatePageEl(pageEl) {
pageEl.hass = this.hass;
if (this._currentPage === "dashboard") {
pageEl.entities = this._entityRegistryEntries;
pageEl.entries = this._configEntries;
pageEl.progress = this._configEntriesInProgress;
return;
}
pageEl.entityRegistryEntries = this._entityRegistryEntries;
pageEl.configEntries = this._configEntries;
pageEl.configEntryId = this.routeTail.path.substr(1);
pageEl.deviceRegistryEntries = this._deviceRegistryEntries;
pageEl.areas = this._areas;
pageEl.narrow = this.narrow;
}
private _loadData() {
getConfigEntries(this.hass).then((configEntries) => {
this._configEntries = configEntries.sort((conf1, conf2) =>
compare(conf1.title, conf2.title)
);
});
if (this._unsubs) {
return;
}
this._unsubs = [
subscribeAreaRegistry(this.hass.connection, (areas) => {
this._areas = areas;
}),
subscribeEntityRegistry(this.hass.connection, (entries) => {
this._entityRegistryEntries = entries;
}),
subscribeDeviceRegistry(this.hass.connection, (entries) => {
this._deviceRegistryEntries = entries;
}),
subscribeConfigFlowInProgress(this.hass, (flowsInProgress) => {
this._configEntriesInProgress = flowsInProgress;
}),
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-config-integrations": HaConfigIntegrations;
}
}

View File

@ -117,9 +117,11 @@ class HaPanelDevInfo extends LitElement {
</mwc-button>
</p>
</div>
<system-health-card .hass=${this.hass}></system-health-card>
<system-log-card .hass=${this.hass}></system-log-card>
<error-log-card .hass=${this.hass}></error-log-card>
<div class="content">
<system-health-card .hass=${this.hass}></system-health-card>
<system-log-card .hass=${this.hass}></system-log-card>
<error-log-card .hass=${this.hass}></error-log-card>
</div>
`;
}
@ -155,7 +157,6 @@ class HaPanelDevInfo extends LitElement {
}
.content {
padding: 16px 0px 16px 0;
direction: ltr;
}

View File

@ -7,6 +7,7 @@ import {
css,
CSSResult,
} from "lit-element";
import "@polymer/paper-icon-button/paper-icon-button";
import { HomeAssistant } from "../../../types";
import { fireEvent } from "../../../common/dom/fire_event";
@ -31,13 +32,29 @@ export class HuiEntityEditor extends LitElement {
<div class="entities">
${this.entities.map((entityConf, index) => {
return html`
<ha-entity-picker
.hass="${this.hass}"
.value="${entityConf.entity}"
.index="${index}"
@change="${this._valueChanged}"
allow-custom-entity
></ha-entity-picker>
<div class="entity">
<ha-entity-picker
.hass="${this.hass}"
.value="${entityConf.entity}"
.index="${index}"
@change="${this._valueChanged}"
allow-custom-entity
></ha-entity-picker>
<paper-icon-button
title="Move entity down"
icon="hass:arrow-down"
.index="${index}"
@click="${this._entityDown}"
?disabled="${index === this.entities!.length - 1}"
></paper-icon-button>
<paper-icon-button
title="Move entity up"
icon="hass:arrow-up"
.index="${index}"
@click="${this._entityUp}"
?disabled="${index === 0}"
></paper-icon-button>
</div>
`;
})}
<ha-entity-picker
@ -60,6 +77,30 @@ export class HuiEntityEditor extends LitElement {
fireEvent(this, "entities-changed", { entities: newConfigEntities });
}
private _entityUp(ev: Event): void {
const target = ev.target! as EditorTarget;
const newEntities = this.entities!.concat();
[newEntities[target.index! - 1], newEntities[target.index!]] = [
newEntities[target.index!],
newEntities[target.index! - 1],
];
fireEvent(this, "entities-changed", { entities: newEntities });
}
private _entityDown(ev: Event): void {
const target = ev.target! as EditorTarget;
const newEntities = this.entities!.concat();
[newEntities[target.index! + 1], newEntities[target.index!]] = [
newEntities[target.index!],
newEntities[target.index! + 1],
];
fireEvent(this, "entities-changed", { entities: newEntities });
}
private _valueChanged(ev: Event): void {
const target = ev.target! as EditorTarget;
const newConfigEntities = this.entities!.concat();
@ -81,6 +122,13 @@ export class HuiEntityEditor extends LitElement {
.entities {
padding-left: 20px;
}
.entity {
display: flex;
align-items: flex-end;
}
.entity ha-entity-picker {
flex-grow: 1;
}
`;
}
}

View File

@ -68,6 +68,7 @@ export class HuiCardEditor extends LitElement {
if (this._yamlEditor) {
this._yamlEditor.codemirror.refresh();
}
fireEvent(this as HTMLElement, "iron-resize");
}, 1);
this._error = undefined;
} catch (err) {

View File

@ -367,6 +367,13 @@
"system-users": "Users",
"system-read-only": "Read-Only Users"
},
"config_entry": {
"disabled_by": {
"user": "User",
"integration": "Integration",
"config_entry": "Config Entry"
}
},
"ui": {
"auth_store": {
"ask": "Do you want to save this login?",
@ -538,6 +545,11 @@
"success": {
"description": "Options successfully saved."
}
},
"config_entry_system_options": {
"title": "System Options",
"enable_new_entities_label": "Enable newly added entities.",
"enable_new_entities_description": "If disabled, newly discovered entities will not be automatically added to Home Assistant."
}
},
"duration": {
@ -847,12 +859,14 @@
"header": "Entity Registry",
"introduction": "Home Assistant keeps a registry of every entity it has ever seen that can be uniquely identified. Each of these entities will have an entity ID assigned which will be reserved for just this entity.",
"introduction2": "Use the entity registry to override the name, change the entity ID or remove the entry from Home Assistant. Note, removing the entity registry entry won't remove the entity. To do that, follow the link below and remove it from the integrations page.",
"integrations_page": "Integrations page",
"unavailable": "(unavailable)"
"integrations_page": "Integrations page"
},
"editor": {
"unavailable": "This entity is not currently available.",
"default_name": "New Area",
"enabled_label": "Enable entity",
"enabled_cause": "Disabled by {cause}.",
"enabled_description": "Disabled entities will not be added to Home Assistant.",
"delete": "DELETE",
"update": "UPDATE"
}

View File

@ -320,7 +320,7 @@
"title": "Esdeveniments"
},
"templates": {
"title": "Plantilles"
"title": "Plantilla"
},
"mqtt": {
"title": "MQTT"
@ -349,10 +349,10 @@
"introduction": "Aquí pots configurar Home Assistant i els seus components. Encara no és possible configurar-ho tot des de la interfície d'usuari, però hi estem treballant.",
"core": {
"caption": "General",
"description": "Valida la configuració i controla el servidor",
"description": "Canviar la configuració general de Home Assistant",
"section": {
"core": {
"header": "Configuració i control del servidor",
"header": "Configuració general",
"introduction": "Sabem que canviar la configuració pot ser un procés molest. Aquesta secció intenta facilitar-te una mica més la vida.",
"core_config": {
"edit_requires_storage": "L'editor està desactivat ja que la configuració es troba a configuration.yaml.",
@ -1294,6 +1294,14 @@
"updater": {
"title": "Instruccions d'actualització"
}
},
"options_flow": {
"form": {
"header": "Opcions"
},
"success": {
"description": "Opcions guardades amb èxit."
}
}
},
"auth_store": {

View File

@ -1294,6 +1294,14 @@
"updater": {
"title": "Opdateringsvejledning"
}
},
"options_flow": {
"form": {
"header": "Indstillinger"
},
"success": {
"description": "Indstillingerne blev gemt."
}
}
},
"auth_store": {

View File

@ -777,7 +777,8 @@
"core": "Hauptsystem neu laden",
"group": "Gruppen neu laden",
"automation": "Automatisierungen neu laden",
"script": "Skripte neu laden"
"script": "Skripte neu laden",
"scene": "Szenen neu laden"
},
"server_management": {
"heading": "Serververwaltung",
@ -1293,6 +1294,14 @@
"updater": {
"title": "Update-Anweisungen"
}
},
"options_flow": {
"form": {
"header": "Optionen"
},
"success": {
"description": "Optionen wurden erfolgreich gespeichert."
}
}
},
"auth_store": {

View File

@ -1294,6 +1294,14 @@
"updater": {
"title": "Update Instructions"
}
},
"options_flow": {
"form": {
"header": "Options"
},
"success": {
"description": "Options successfully saved."
}
}
},
"auth_store": {

View File

@ -352,7 +352,7 @@
"description": "Cambiar la configuración general de Home Assistant",
"section": {
"core": {
"header": "Configuración y control del servidor",
"header": "Configuración general",
"introduction": "Cambiar tu configuración puede ser un proceso tedioso. Lo sabemos. Esta sección tratará de hacer tu vida un poco más fácil.",
"core_config": {
"edit_requires_storage": "Editor deshabilitado debido a la configuración almacenada en configuration.yaml.",
@ -1294,6 +1294,14 @@
"updater": {
"title": "Instrucciones de actualización"
}
},
"options_flow": {
"form": {
"header": "Opciones"
},
"success": {
"description": "Las opciones se guardaron correctamente."
}
}
},
"auth_store": {
@ -1316,7 +1324,7 @@
"climate": "Climatización",
"configurator": "Configurador",
"conversation": "Conversación",
"cover": "Cubierta",
"cover": "Persiana",
"device_tracker": "Rastreador de dispositivo",
"fan": "Ventilador",
"history_graph": "Historial gráfico",

View File

@ -326,7 +326,7 @@
"title": "MQTT"
},
"info": {
"title": "Info"
"title": "Informazioni"
}
}
},
@ -622,7 +622,7 @@
"instance": "Esempio",
"index": "Indice",
"unknown": "sconosciuto",
"wakeup_interval": "Intervallo di sveglia"
"wakeup_interval": "Intervallo di riattivazione"
},
"values": {
"header": "Valori del nodo"
@ -634,7 +634,7 @@
"config_parameter": "Parametro di configurazione",
"config_value": "Valore di configurazione",
"true": "Vero",
"false": "False",
"false": "Falso",
"set_config_parameter": "Imposta parametro di configurazione"
}
},
@ -773,7 +773,7 @@
},
"reloading": {
"heading": "Ricaricamento della configurazione",
"introduction": "Alcune parti di Home Assistant possono essere ricaricate senza richiedere il riavvio. Cliccando su ricarica si rimuoverà la loro configurazione corrente e si caricherà quella nuova.",
"introduction": "Alcune parti di Home Assistant possono essere ricaricate senza richiedere il riavvio. Cliccando su ricarica si rimuoverà la loro configurazione attuale e si caricherà la versione aggiornata.",
"core": "Ricarica core",
"group": "Ricarica i gruppi",
"automation": "Ricarica automazioni",
@ -1075,7 +1075,7 @@
"demo_by": "di {name}",
"next_demo": "Prossima demo",
"introduction": "Benvenuto a casa! Questa è la demo di Home Assistant, qui pubblichiamo le migliori interfacce utente create dalla nostra community.",
"learn_more": "Ulteriori informazioni su Home Assistant"
"learn_more": "Scopri di più su Home Assistant"
}
},
"config": {
@ -1294,6 +1294,14 @@
"updater": {
"title": "Istruzioni per l'aggiornamento"
}
},
"options_flow": {
"form": {
"header": "Opzioni"
},
"success": {
"description": "Opzioni salvate correttamente."
}
}
},
"auth_store": {

View File

@ -1294,6 +1294,14 @@
"updater": {
"title": "업데이트 방법"
}
},
"options_flow": {
"form": {
"header": "옵션"
},
"success": {
"description": "옵션이 성공적으로 저장되었습니다."
}
}
},
"auth_store": {

View File

@ -1294,6 +1294,14 @@
"updater": {
"title": "Instruktioune fir d'Mise à jour"
}
},
"options_flow": {
"form": {
"header": "Optiounen"
},
"success": {
"description": "Optiounen erfollegräich gespäichert."
}
}
},
"auth_store": {

View File

@ -352,7 +352,7 @@
"description": "Wijzig je algemene Home Assistant configuratie",
"section": {
"core": {
"header": "Configuratie en serverbeheer",
"header": "Algemene Configuratie",
"introduction": "Het aanpassen van je configuratie kan een moeizaam proces zijn. Dat weten we. Dit onderdeel probeert je het leven iets makkelijker te maken.",
"core_config": {
"edit_requires_storage": "Editor uitgeschakeld omdat de configuratie is opgeslagen in configuration.yaml",
@ -777,7 +777,8 @@
"core": "Herlaad kern",
"group": "Herlaad groepen",
"automation": "Herlaad automatiseringen",
"script": "Herlaad scripts"
"script": "Herlaad scripts",
"scene": "Herlaad scenes"
},
"server_management": {
"heading": "Serverbeheer",
@ -1293,6 +1294,14 @@
"updater": {
"title": "Update-instructies"
}
},
"options_flow": {
"form": {
"header": "Instellingen"
},
"success": {
"description": "Instellingen succesvol opgeslagen."
}
}
},
"auth_store": {

View File

@ -1294,6 +1294,14 @@
"updater": {
"title": "Instrukcje aktualizacji"
}
},
"options_flow": {
"form": {
"header": "Opcje"
},
"success": {
"description": "Opcje zapisane pomyślnie."
}
}
},
"auth_store": {

View File

@ -608,8 +608,8 @@
"services": {
"start_network": "Включить",
"stop_network": "Отключить",
"heal_network": "Исправить",
"test_network": "Тестировать",
"heal_network": "Исправить сеть",
"test_network": "Тестировать ",
"soft_reset": "Сброс",
"save_config": "Сохранить конфигурацию",
"add_node_secure": "Добавить защищенный узел",
@ -1294,6 +1294,14 @@
"updater": {
"title": "Инструкция по обновлению"
}
},
"options_flow": {
"form": {
"header": "Параметры"
},
"success": {
"description": "Параметры успешно сохранены."
}
}
},
"auth_store": {

View File

@ -732,7 +732,8 @@
"core": "Znovu načítať jadro",
"group": "Znovu načítať skupiny",
"automation": "Znovu načítať automatizácie",
"script": "Znovu načítať skripty"
"script": "Znovu načítať skripty",
"scene": "Znovu načítať scény"
},
"server_management": {
"heading": "Správa servera",

View File

@ -620,12 +620,21 @@
"common": {
"value": "Vrednost",
"instance": "Instanca",
"index": "Indeks"
"index": "Indeks",
"unknown": "Neznano",
"wakeup_interval": "Interval bujenja"
},
"values": {
"header": "Vrednosti vozlišča"
},
"node_config": {
"header": "Možnosti konfiguracije vozlišča",
"seconds": "Sekund",
"set_wakeup": "Nastavite Interval bujenja",
"config_parameter": "Vrednostni parameter",
"config_value": "vrednost nastavite",
"true": "Prav",
"false": "Lažno",
"set_config_parameter": "Nastavite Config Parameter"
}
},
@ -768,7 +777,8 @@
"core": "Ponovno naloži jedro",
"group": "Ponovno naloži skupine",
"automation": "Ponovno naloži avtomatizacije",
"script": "Ponovno naloži skripte"
"script": "Ponovno naloži skripte",
"scene": "Ponovno naloži scene"
},
"server_management": {
"heading": "Upravljanje strežnika",
@ -1284,6 +1294,14 @@
"updater": {
"title": "Navodila za posodabitev"
}
},
"options_flow": {
"form": {
"header": "Možnosti"
},
"success": {
"description": "Možnosti so uspešno shranjene."
}
}
},
"auth_store": {

View File

@ -129,11 +129,11 @@
"climate": {
"off": "Av",
"on": "På",
"heat": "Värmer",
"cool": "Kyler",
"heat": "Värme",
"cool": "Kyla",
"idle": "Inaktiv",
"auto": "Automatisk",
"dry": "Avfuktar",
"dry": "Avfuktning",
"fan_only": "Endast fläkt",
"eco": "Eco",
"electric": "Elektrisk",
@ -1294,6 +1294,14 @@
"updater": {
"title": "Uppdateringsanvisningar"
}
},
"options_flow": {
"form": {
"header": "Inställningar"
},
"success": {
"description": "Inställningar sparade"
}
}
},
"auth_store": {
@ -1377,9 +1385,9 @@
},
"hvac_action": {
"off": "Av",
"heating": "Uppvärmning",
"cooling": "Kyla",
"drying": "Avfuktning",
"heating": "Värmer",
"cooling": "Kyler",
"drying": "Avfuktar",
"idle": "Inaktiv",
"fan": "Fläkt"
}

View File

@ -596,7 +596,20 @@
"network_management": {
"header": "Z-Wave网络管理"
},
"network_status": {
"network_stopped": "Z-Wave网络已停止",
"network_starting": "启动Z-Wave网络......",
"network_starting_note": "这可能需要一段时间,具体取决于您的网络规模。",
"network_started": "Z-Wave网络开始",
"network_started_note_some_queried": "已查询唤醒节点。睡眠节点将在唤醒时被查询。",
"network_started_note_all_queried": "已查询所有节点。"
},
"services": {
"start_network": "启动网络",
"stop_network": "停止网络",
"heal_network": "修复网络",
"test_network": "测试网络",
"soft_reset": "软复位",
"save_config": "保存配置",
"add_node": "添加节点",
"remove_node": "删除节点",
@ -604,9 +617,15 @@
},
"common": {
"value": "值",
"instance": "实例"
"instance": "实例",
"unknown": "未知",
"wakeup_interval": "唤醒时间间隔"
},
"node_config": {
"header": "节点配置选项",
"seconds": "秒",
"set_wakeup": "设置唤醒间隔",
"config_parameter": "配置参数",
"set_config_parameter": "设置配置参数"
}
},
@ -731,6 +750,29 @@
"device_tracker_picked": "跟踪设备",
"device_tracker_pick": "选择要跟踪的设备"
}
},
"server_control": {
"section": {
"validation": {
"heading": "配置有效性",
"check_config": "配置检查",
"valid": "配置有效!",
"invalid": "配置无效"
},
"reloading": {
"heading": "配置重载中",
"core": "重载核心模块",
"group": "重载分组",
"automation": "重载自动化",
"script": "重载脚本",
"scene": "重载场景"
},
"server_management": {
"heading": "服务器管理",
"restart": "重新启动",
"stop": "停止服务"
}
}
}
},
"profile": {
@ -1238,6 +1280,14 @@
"updater": {
"title": "更新说明"
}
},
"options_flow": {
"form": {
"header": "选项"
},
"success": {
"description": "选项已成功保存。"
}
}
},
"auth_store": {

View File

@ -1294,6 +1294,14 @@
"updater": {
"title": "更新說明"
}
},
"options_flow": {
"form": {
"header": "選項"
},
"success": {
"description": "選項已儲存。"
}
}
},
"auth_store": {