diff --git a/setup.py b/setup.py index c12595c8f9..19507c138e 100644 --- a/setup.py +++ b/setup.py @@ -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", diff --git a/src/data/config_entries.ts b/src/data/config_entries.ts index c8119f1983..9d5388c5bf 100644 --- a/src/data/config_entries.ts +++ b/src/data/config_entries.ts @@ -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("POST", "config/config_entries/flow", { - handler, - }); - -export const fetchConfigFlow = (hass: HomeAssistant, flowId: string) => - hass.callApi( - "GET", - `config/config_entries/flow/${flowId}` - ); - -export const handleConfigFlowStep = ( - hass: HomeAssistant, - flowId: string, - data: { [key: string]: any } -) => - hass.callApi( - "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("GET", "config/config_entries/flow"); - -export const getConfigFlowHandlers = (hass: HomeAssistant) => - hass.callApi("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( - "_configFlowProgress", - fetchConfigFlowInProgress, - subscribeConfigFlowInProgressUpdates, - hass.connection, - onChange - ); +export interface ConfigEntrySystemOptions { + disable_new_entities: boolean; +} export const getConfigEntries = (hass: HomeAssistant) => hass.callApi("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( - "POST", - "config/config_entries/options/flow", - { - handler, - } - ); - -export const fetchOptionsFlow = (hass: HomeAssistant, flowId: string) => - hass.callApi( - "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( - "POST", - `config/config_entries/options/flow/${flowId}`, - data - ); + hass.callWS({ + 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 +) => + hass.callWS({ + type: "config_entries/system_options/update", + entry_id: configEntryId, + ...params, + }); diff --git a/src/data/config_flow.ts b/src/data/config_flow.ts new file mode 100644 index 0000000000..45f401a91c --- /dev/null +++ b/src/data/config_flow.ts @@ -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("POST", "config/config_entries/flow", { + handler, + }); + +export const fetchConfigFlow = (hass: HomeAssistant, flowId: string) => + hass.callApi( + "GET", + `config/config_entries/flow/${flowId}` + ); + +export const handleConfigFlowStep = ( + hass: HomeAssistant, + flowId: string, + data: { [key: string]: any } +) => + hass.callApi( + "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("GET", "config/config_entries/flow"); + +export const getConfigFlowHandlers = (hass: HomeAssistant) => + hass.callApi("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( + "_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); +}; diff --git a/src/data/entity_registry.ts b/src/data/entity_registry.ts index f6549ee533..a3037e45cb 100644 --- a/src/data/entity_registry.ts +++ b/src/data/entity_registry.ts @@ -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 = ( diff --git a/src/data/options_flow.ts b/src/data/options_flow.ts new file mode 100644 index 0000000000..52bb95e9b5 --- /dev/null +++ b/src/data/options_flow.ts @@ -0,0 +1,31 @@ +import { HomeAssistant } from "../types"; +import { DataEntryFlowStep } from "./data_entry_flow"; + +export const createOptionsFlow = (hass: HomeAssistant, handler: string) => + hass.callApi( + "POST", + "config/config_entries/options/flow", + { + handler, + } + ); + +export const fetchOptionsFlow = (hass: HomeAssistant, flowId: string) => + hass.callApi( + "GET", + `config/config_entries/options/flow/${flowId}` + ); + +export const handleOptionsFlowStep = ( + hass: HomeAssistant, + flowId: string, + data: { [key: string]: any } +) => + hass.callApi( + "POST", + `config/config_entries/options/flow/${flowId}`, + data + ); + +export const deleteOptionsFlow = (hass: HomeAssistant, flowId: string) => + hass.callApi("DELETE", `config/config_entries/options/flow/${flowId}`); diff --git a/src/dialogs/config-entry-system-options/dialog-config-entry-system-options.ts b/src/dialogs/config-entry-system-options/dialog-config-entry-system-options.ts new file mode 100644 index 0000000000..5c5ac07b87 --- /dev/null +++ b/src/dialogs/config-entry-system-options/dialog-config-entry-system-options.ts @@ -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 { + 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` + +

+ ${this.hass.localize("ui.dialogs.config_entry_system_options.title")} +

+ + ${this._loading + ? html` +
+ +
+ ` + : html` + ${this._error + ? html` +
${this._error}
+ ` + : ""} +
+ +
+ ${this.hass.localize( + "ui.dialogs.config_entry_system_options.enable_new_entities_label" + )} +
+
+ ${this.hass.localize( + "ui.dialogs.config_entry_system_options.enable_new_entities_description" + )} +
+
+
+ `} +
+ ${!this._loading + ? html` +
+ + ${this.hass.localize( + "ui.panel.config.entity_registry.editor.update" + )} + +
+ ` + : ""} +
+ `; + } + + private _disableNewEntitiesChanged(ev: PolymerChangedEvent): void { + this._error = undefined; + this._disableNewEntities = !ev.detail.value; + } + + private async _updateEntry(): Promise { + 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): 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; + } +} diff --git a/src/dialogs/config-entry-system-options/show-dialog-config-entry-system-options.ts b/src/dialogs/config-entry-system-options/show-dialog-config-entry-system-options.ts new file mode 100644 index 0000000000..f21b46644c --- /dev/null +++ b/src/dialogs/config-entry-system-options/show-dialog-config-entry-system-options.ts @@ -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 + // ) => Promise; + // removeEntry: () => Promise; +} + +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, + }); +}; diff --git a/src/dialogs/config-flow/show-dialog-config-flow.ts b/src/dialogs/config-flow/show-dialog-config-flow.ts index f8718393f4..601f522d05 100644 --- a/src/dialogs/config-flow/show-dialog-config-flow.ts +++ b/src/dialogs/config-flow/show-dialog-config-flow.ts @@ -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 { diff --git a/src/dialogs/config-flow/show-dialog-options-flow.ts b/src/dialogs/config-flow/show-dialog-options-flow.ts index a4f8f5126c..740b364fbf 100644 --- a/src/dialogs/config-flow/show-dialog-options-flow.ts +++ b/src/dialogs/config-flow/show-dialog-options-flow.ts @@ -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; diff --git a/src/panels/config/integrations/dialog-device-registry-detail.ts b/src/dialogs/device-registry-detail/dialog-device-registry-detail.ts similarity index 94% rename from src/panels/config/integrations/dialog-device-registry-detail.ts rename to src/dialogs/device-registry-detail/dialog-device-registry-detail.ts index 06da57b870..64039847c4 100644 --- a/src/panels/config/integrations/dialog-device-registry-detail.ts +++ b/src/dialogs/device-registry-detail/dialog-device-registry-detail.ts @@ -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}" > -

${device.name}

+

${device.name || "Unnamed device"}

${this._error ? html` diff --git a/src/panels/config/integrations/show-dialog-device-registry-detail.ts b/src/dialogs/device-registry-detail/show-dialog-device-registry-detail.ts similarity index 88% rename from src/panels/config/integrations/show-dialog-device-registry-detail.ts rename to src/dialogs/device-registry-detail/show-dialog-device-registry-detail.ts index f7291b3d75..564a32aa60 100644 --- a/src/panels/config/integrations/show-dialog-device-registry-detail.ts +++ b/src/dialogs/device-registry-detail/show-dialog-device-registry-detail.ts @@ -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; diff --git a/src/onboarding/onboarding-integrations.ts b/src/onboarding/onboarding-integrations.ts index b8de3d44bb..1919b98d37 100644 --- a/src/onboarding/onboarding-integrations.ts +++ b/src/onboarding/onboarding-integrations.ts @@ -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 { diff --git a/src/panels/config/entity_registry/dialog-entity-registry-detail.ts b/src/panels/config/entity_registry/dialog-entity-registry-detail.ts index 391e72d16c..2a6ac0553f 100644 --- a/src/panels/config/entity_registry/dialog-entity-registry-detail.ts +++ b/src/panels/config/entity_registry/dialog-entity-registry-detail.ts @@ -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}" > -

${entry.entity_id}

+

+ ${stateObj + ? computeStateName(stateObj) + : entry.name || entry.entity_id} +

${!stateObj ? html` @@ -96,6 +94,35 @@ class DialogEntityRegistryDetail extends LitElement { .invalid=${invalidDomainUpdate} .disabled=${this._submitting} > +
+ +
+
+ ${this.hass.localize( + "ui.panel.config.entity_registry.editor.enabled_label" + )} +
+
+ ${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" + )} +
Note: this might not work yet with all integrations. +
+
+
+
@@ -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): 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); + } `, ]; } diff --git a/src/panels/config/entity_registry/ha-config-entity-registry.ts b/src/panels/config/entity_registry/ha-config-entity-registry.ts index 776e41e1a0..8e5abfc3b8 100644 --- a/src/panels/config/entity_registry/ha-config-entity-registry.ts +++ b/src/panels/config/entity_registry/ha-config-entity-registry.ts @@ -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` - +
${computeEntityRegistryName(this.hass!, entry) || - this.hass!.localize( - "ui.panel.config.entity_registry.picker.unavailable" - )} + `(${this.hass!.localize("state.default.unavailable")})`}
${entry.entity_id}
-
${entry.platform}
+
+ ${entry.platform} + ${entry.disabled_by + ? html` +
(disabled) + ` + : ""} +
`; })} @@ -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); + } `; } } diff --git a/src/panels/config/integrations/ha-ce-entities-card.js b/src/panels/config/integrations/config-entry/ha-ce-entities-card.js similarity index 84% rename from src/panels/config/integrations/ha-ce-entities-card.js rename to src/panels/config/integrations/config-entry/ha-ce-entities-card.js index 49b9ea3454..10d9e7f2bc 100644 --- a/src/panels/config/integrations/ha-ce-entities-card.js +++ b/src/panels/config/integrations/config-entry/ha-ce-entities-card.js @@ -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 diff --git a/src/panels/config/integrations/config-entry/ha-config-entry-page.ts b/src/panels/config/integrations/config-entry/ha-config-entry-page.ts new file mode 100644 index 0000000000..979a10887d --- /dev/null +++ b/src/panels/config/integrations/config-entry/ha-config-entry-page.ts @@ -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` + + `; + } + + const configEntryDevices = this._computeConfigEntryDevices( + configEntry, + this.deviceRegistryEntries + ); + + const noDeviceEntities = this._computeNoDeviceEntities( + configEntry, + this.entityRegistryEntries + ); + + return html` + + ${configEntry.supports_options + ? html` + + ` + : ""} + + +
+ ${configEntryDevices.length === 0 && noDeviceEntities.length === 0 + ? html` +

+ ${this.hass.localize( + "ui.panel.config.integrations.config_entry.no_devices" + )} +

+ ` + : ""} + ${configEntryDevices.map( + (device) => html` + + ` + )} + ${noDeviceEntities.length > 0 + ? html` + + ` + : ""} +
+
+ `; + } + + 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); diff --git a/src/panels/config/integrations/ha-device-card.js b/src/panels/config/integrations/config-entry/ha-device-card.js similarity index 92% rename from src/panels/config/integrations/ha-device-card.js rename to src/panels/config/integrations/config-entry/ha-device-card.js index bddc5936ad..40d9b49cee 100644 --- a/src/panels/config/integrations/ha-device-card.js +++ b/src/panels/config/integrations/config-entry/ha-device-card.js @@ -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; diff --git a/src/panels/config/integrations/ha-config-entries-dashboard.js b/src/panels/config/integrations/ha-config-entries-dashboard.js index 937fcc21f8..d7c6113afb 100644 --- a/src/panels/config/integrations/ha-config-entries-dashboard.js +++ b/src/panels/config/integrations/ha-config-entries-dashboard.js @@ -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(