mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-01 13:37:47 +00:00
Add entity registry functions to device page (#3843)
* Add entity registry functions to device page * Move update/remove logic to dialog * Hide disabled entities by default
This commit is contained in:
parent
76d3218130
commit
b927a3ef29
@ -123,24 +123,26 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="device-entities">
|
||||
<template
|
||||
is="dom-repeat"
|
||||
items="[[_computeDeviceEntities(hass, device, entities)]]"
|
||||
as="entity"
|
||||
>
|
||||
<paper-icon-item on-click="_openMoreInfo">
|
||||
<state-badge
|
||||
state-obj="[[_computeStateObj(entity, hass)]]"
|
||||
slot="item-icon"
|
||||
></state-badge>
|
||||
<paper-item-body>
|
||||
<div class="name">[[_computeEntityName(entity, hass)]]</div>
|
||||
<div class="secondary entity-id">[[entity.entity_id]]</div>
|
||||
</paper-item-body>
|
||||
</paper-icon-item>
|
||||
</template>
|
||||
</div>
|
||||
<template is="dom-if" if="[[!hideEntities]]">
|
||||
<div class="device-entities">
|
||||
<template
|
||||
is="dom-repeat"
|
||||
items="[[_computeDeviceEntities(hass, device, entities)]]"
|
||||
as="entity"
|
||||
>
|
||||
<paper-icon-item on-click="_openMoreInfo">
|
||||
<state-badge
|
||||
state-obj="[[_computeStateObj(entity, hass)]]"
|
||||
slot="item-icon"
|
||||
></state-badge>
|
||||
<paper-item-body>
|
||||
<div class="name">[[_computeEntityName(entity, hass)]]</div>
|
||||
<div class="secondary entity-id">[[entity.entity_id]]</div>
|
||||
</paper-item-body>
|
||||
</paper-icon-item>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
@ -157,6 +159,7 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
reflectToAttribute: true,
|
||||
},
|
||||
hideSettings: { type: Boolean, value: false },
|
||||
hideEntities: { type: Boolean, value: false },
|
||||
_childDevices: {
|
||||
type: Array,
|
||||
computed: "_computeChildDevices(device, devices)",
|
||||
|
@ -0,0 +1,171 @@
|
||||
import {
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
html,
|
||||
property,
|
||||
customElement,
|
||||
css,
|
||||
CSSResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import memoizeOne from "memoize-one";
|
||||
|
||||
import { compare } from "../../../../common/string/compare";
|
||||
import "../../../../components/entity/state-badge";
|
||||
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-icon";
|
||||
import "../../../../components/ha-switch";
|
||||
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
||||
import { EntityRegistryEntry } from "../../../../data/entity_registry";
|
||||
import { showEntityRegistryDetailDialog } from "../../entity_registry/show-dialog-entity-registry-detail";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../../../common/entity/compute_domain";
|
||||
import { domainIcon } from "../../../../common/entity/domain_icon";
|
||||
// tslint:disable-next-line
|
||||
import { HaSwitch } from "../../../../components/ha-switch";
|
||||
|
||||
@customElement("ha-device-entities-card")
|
||||
export class HaDeviceEntitiesCard extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public deviceId!: string;
|
||||
@property() public entities!: EntityRegistryEntry[];
|
||||
@property() public narrow!: boolean;
|
||||
@property() private _showDisabled = false;
|
||||
|
||||
private _entities = memoizeOne(
|
||||
(
|
||||
deviceId: string,
|
||||
entities: EntityRegistryEntry[]
|
||||
): EntityRegistryEntry[] =>
|
||||
entities
|
||||
.filter((entity) => entity.device_id === deviceId)
|
||||
.sort((ent1, ent2) =>
|
||||
compare(
|
||||
this._computeEntityName(ent1) || `zzz${ent1.entity_id}`,
|
||||
this._computeEntityName(ent2) || `zzz${ent2.entity_id}`
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const entities = this._entities(this.deviceId, this.entities);
|
||||
return html`
|
||||
<ha-card>
|
||||
<paper-item>
|
||||
<ha-switch
|
||||
?checked=${this._showDisabled}
|
||||
@change=${this._showDisabledChanged}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.entity_registry.picker.show_disabled"
|
||||
)}
|
||||
</ha-switch>
|
||||
</paper-item>
|
||||
${entities.length
|
||||
? entities.map((entry: EntityRegistryEntry) => {
|
||||
if (!this._showDisabled && entry.disabled_by) {
|
||||
return "";
|
||||
}
|
||||
const stateObj = this.hass.states[entry.entity_id];
|
||||
return html`
|
||||
<paper-icon-item
|
||||
.entry=${entry}
|
||||
class=${classMap({ "disabled-entry": !!entry.disabled_by })}
|
||||
>
|
||||
${stateObj
|
||||
? html`
|
||||
<state-badge
|
||||
.stateObj=${stateObj}
|
||||
slot="item-icon"
|
||||
></state-badge>
|
||||
`
|
||||
: html`
|
||||
<ha-icon
|
||||
slot="item-icon"
|
||||
.icon=${domainIcon(computeDomain(entry.entity_id))}
|
||||
></ha-icon>
|
||||
`}
|
||||
<paper-item-body two-line>
|
||||
<div class="name">${this._computeEntityName(entry)}</div>
|
||||
<div class="secondary entity-id">${entry.entity_id}</div>
|
||||
</paper-item-body>
|
||||
<div class="buttons">
|
||||
${stateObj
|
||||
? html`
|
||||
<paper-icon-button
|
||||
@click=${this._openMoreInfo}
|
||||
icon="hass:open-in-new"
|
||||
></paper-icon-button>
|
||||
`
|
||||
: ""}
|
||||
<paper-icon-button
|
||||
@click=${this._openEditEntry}
|
||||
icon="hass:settings"
|
||||
></paper-icon-button>
|
||||
</div>
|
||||
</paper-icon-item>
|
||||
`;
|
||||
})
|
||||
: html`
|
||||
<div class="config-entry-row">
|
||||
<paper-item-body two-line>
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.entities.none"
|
||||
)}
|
||||
</div>
|
||||
</paper-item-body>
|
||||
</div>
|
||||
`}
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private _showDisabledChanged(ev: Event) {
|
||||
this._showDisabled = (ev.target as HaSwitch).checked;
|
||||
}
|
||||
|
||||
private _openEditEntry(ev: MouseEvent): void {
|
||||
const entry = (ev.currentTarget! as any).closest("paper-icon-item").entry;
|
||||
showEntityRegistryDetailDialog(this, {
|
||||
entry,
|
||||
});
|
||||
}
|
||||
|
||||
private _openMoreInfo(ev: MouseEvent) {
|
||||
const entry = (ev.currentTarget! as any).closest("paper-icon-item").entry;
|
||||
fireEvent(this, "hass-more-info", { entityId: entry.entity_id });
|
||||
}
|
||||
|
||||
private _computeEntityName(entity) {
|
||||
if (entity.name) {
|
||||
return entity.name;
|
||||
}
|
||||
const state = this.hass.states[entity.entity_id];
|
||||
return state ? computeStateName(state) : null;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-icon {
|
||||
width: 40px;
|
||||
}
|
||||
.entity-id {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.buttons {
|
||||
text-align: right;
|
||||
margin: 0 0 0 8px;
|
||||
}
|
||||
.disabled-entry {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
@ -11,11 +11,12 @@ import memoizeOne from "memoize-one";
|
||||
|
||||
import "../../../layouts/hass-subpage";
|
||||
import "../../../layouts/hass-error-screen";
|
||||
import "../ha-config-section";
|
||||
|
||||
import "./device-detail/ha-device-card";
|
||||
import "./device-detail/ha-device-triggers-card";
|
||||
import "./device-detail/ha-device-conditions-card";
|
||||
import "./device-detail/ha-device-actions-card";
|
||||
import "./device-detail/ha-device-entities-card";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { ConfigEntry } from "../../../data/config_entries";
|
||||
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||
@ -37,6 +38,7 @@ export class HaConfigDevicePage extends LitElement {
|
||||
@property() public entities!: EntityRegistryEntry[];
|
||||
@property() public areas!: AreaRegistryEntry[];
|
||||
@property() public deviceId!: string;
|
||||
@property() public narrow!: boolean;
|
||||
|
||||
private _device = memoizeOne(
|
||||
(
|
||||
@ -67,7 +69,11 @@ export class HaConfigDevicePage extends LitElement {
|
||||
icon="hass:settings"
|
||||
@click=${this._showSettings}
|
||||
></paper-icon-button>
|
||||
<div class="content">
|
||||
<ha-config-section .isWide=${!this.narrow}>
|
||||
<span slot="header">Device info</span>
|
||||
<span slot="introduction">
|
||||
Here are all the details of your device.
|
||||
</span>
|
||||
<ha-device-card
|
||||
.hass=${this.hass}
|
||||
.areas=${this.areas}
|
||||
@ -75,7 +81,18 @@ export class HaConfigDevicePage extends LitElement {
|
||||
.device=${device}
|
||||
.entities=${this.entities}
|
||||
hide-settings
|
||||
hide-entities
|
||||
></ha-device-card>
|
||||
|
||||
<div class="header">Entities</div>
|
||||
<ha-device-entities-card
|
||||
.hass=${this.hass}
|
||||
.deviceId=${this.deviceId}
|
||||
.entities=${this.entities}
|
||||
>
|
||||
</ha-device-entities-card>
|
||||
|
||||
<div class="header">Automations</div>
|
||||
<ha-device-triggers-card
|
||||
.hass=${this.hass}
|
||||
.deviceId=${this.deviceId}
|
||||
@ -88,7 +105,7 @@ export class HaConfigDevicePage extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.deviceId=${this.deviceId}
|
||||
></ha-device-actions-card>
|
||||
</div>
|
||||
</ha-config-section>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
@ -104,12 +121,20 @@ export class HaConfigDevicePage extends LitElement {
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
.content {
|
||||
padding: 16px;
|
||||
.header {
|
||||
font-family: var(--paper-font-display1_-_font-family);
|
||||
-webkit-font-smoothing: var(
|
||||
--paper-font-display1_-_-webkit-font-smoothing
|
||||
);
|
||||
font-size: var(--paper-font-display1_-_font-size);
|
||||
font-weight: var(--paper-font-display1_-_font-weight);
|
||||
letter-spacing: var(--paper-font-display1_-_letter-spacing);
|
||||
line-height: var(--paper-font-display1_-_line-height);
|
||||
opacity: var(--dark-primary-opacity);
|
||||
}
|
||||
.content > * {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
|
||||
ha-config-section *:last-child {
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@ -22,10 +22,15 @@ import { HaSwitch } from "../../../components/ha-switch";
|
||||
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import {
|
||||
updateEntityRegistryEntry,
|
||||
removeEntityRegistryEntry,
|
||||
} from "../../../data/entity_registry";
|
||||
|
||||
class DialogEntityRegistryDetail extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() private _name!: string;
|
||||
@property() private _platform!: string;
|
||||
@property() private _entityId!: string;
|
||||
@property() private _disabledBy!: string | null;
|
||||
@property() private _error?: string;
|
||||
@ -38,6 +43,7 @@ class DialogEntityRegistryDetail extends LitElement {
|
||||
this._params = params;
|
||||
this._error = undefined;
|
||||
this._name = this._params.entry.name || "";
|
||||
this._platform = this._params.entry.platform;
|
||||
this._entityId = this._params.entry.entity_id;
|
||||
this._disabledBy = this._params.entry.disabled_by;
|
||||
await this.updateComplete;
|
||||
@ -164,7 +170,7 @@ class DialogEntityRegistryDetail extends LitElement {
|
||||
private async _updateEntry(): Promise<void> {
|
||||
this._submitting = true;
|
||||
try {
|
||||
await this._params!.updateEntry({
|
||||
await updateEntityRegistryEntry(this.hass!, this._entityId, {
|
||||
name: this._name.trim() || null,
|
||||
disabled_by: this._disabledBy,
|
||||
new_entity_id: this._entityId.trim(),
|
||||
@ -178,11 +184,27 @@ class DialogEntityRegistryDetail extends LitElement {
|
||||
}
|
||||
|
||||
private async _deleteEntry(): Promise<void> {
|
||||
if (
|
||||
!confirm(
|
||||
`${this.hass.localize(
|
||||
"ui.panel.config.entity_registry.editor.confirm_delete"
|
||||
)}
|
||||
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.entity_registry.editor.confirm_delete2",
|
||||
"platform",
|
||||
this._platform
|
||||
)}`
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._submitting = true;
|
||||
|
||||
try {
|
||||
if (await this._params!.removeEntry()) {
|
||||
this._params = undefined;
|
||||
}
|
||||
await removeEntityRegistryEntry(this.hass!, this._entityId);
|
||||
this._params = undefined;
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
|
@ -13,8 +13,6 @@ import { HomeAssistant } from "../../../types";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
computeEntityRegistryName,
|
||||
updateEntityRegistryEntry,
|
||||
removeEntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
} from "../../../data/entity_registry";
|
||||
import "../../../layouts/hass-subpage";
|
||||
@ -143,35 +141,6 @@ class HaConfigEntityRegistry extends LitElement {
|
||||
const entry = (ev.currentTarget! as any).entry;
|
||||
showEntityRegistryDetailDialog(this, {
|
||||
entry,
|
||||
updateEntry: async (updates) => {
|
||||
const updated = await updateEntityRegistryEntry(
|
||||
this.hass!,
|
||||
entry.entity_id,
|
||||
updates
|
||||
);
|
||||
this._entities = this._entities!.map((ent) =>
|
||||
ent === entry ? updated : ent
|
||||
);
|
||||
},
|
||||
removeEntry: async () => {
|
||||
if (
|
||||
!confirm(`Are you sure you want to delete this entry?
|
||||
|
||||
Deleting an entry will not remove the entity from Home Assistant. To do this, you will need to remove the integration "${
|
||||
entry.platform
|
||||
}" from Home Assistant.`)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await removeEntityRegistryEntry(this.hass!, entry.entity_id);
|
||||
this._entities = this._entities!.filter((ent) => ent !== entry);
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,15 +1,8 @@
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
EntityRegistryEntryUpdateParams,
|
||||
} from "../../../data/entity_registry";
|
||||
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||
|
||||
export interface EntityRegistryDetailDialogParams {
|
||||
entry: EntityRegistryEntry;
|
||||
updateEntry: (
|
||||
updates: Partial<EntityRegistryEntryUpdateParams>
|
||||
) => Promise<unknown>;
|
||||
removeEntry: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
export const loadEntityRegistryDetailDialog = () =>
|
||||
|
@ -930,6 +930,8 @@
|
||||
"enabled_cause": "Disabled by {cause}.",
|
||||
"enabled_description": "Disabled entities will not be added to Home Assistant.",
|
||||
"delete": "DELETE",
|
||||
"confirm_delete": "Are you sure you want to delete this entry?",
|
||||
"confirm_delete2": "Deleting an entry will not remove the entity from Home Assistant. To do this, you will need to remove the integration '{platform}' from Home Assistant.",
|
||||
"update": "UPDATE"
|
||||
}
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user