diff --git a/src/panels/lovelace/common/compute-unused-entities.js b/src/panels/lovelace/common/compute-unused-entities.js
new file mode 100644
index 0000000000..ceb0b0782c
--- /dev/null
+++ b/src/panels/lovelace/common/compute-unused-entities.js
@@ -0,0 +1,29 @@
+const EXCLUDED_DOMAINS = [
+ 'group',
+ 'zone'
+];
+
+function computeUsedEntities(config) {
+ const entities = new Set();
+
+ function addEntityId(entity) {
+ entities.add(typeof entity === 'string' ? entity : entity.entity);
+ }
+
+ function addEntities(obj) {
+ if (obj.entity) addEntityId(obj.entity);
+ if (obj.entities) obj.entities.forEach(entity => addEntityId(entity));
+ if (obj.card) addEntities(obj.card);
+ if (obj.cards) obj.cards.forEach(card => addEntities(card));
+ }
+
+ config.views.forEach(view => addEntities(view));
+ return entities;
+}
+
+export default function computeUnusedEntities(hass, config) {
+ const usedEntities = computeUsedEntities(config);
+ return Object.keys(hass.states).filter(entity => !usedEntities.has(entity) &&
+ !(config.excluded_entities && config.excluded_entities.includes(entity)) &&
+ !EXCLUDED_DOMAINS.includes(entity.split('.', 1)[0])).sort();
+}
diff --git a/src/panels/lovelace/hui-root.js b/src/panels/lovelace/hui-root.js
index 946db854eb..27b8b11c6e 100644
--- a/src/panels/lovelace/hui-root.js
+++ b/src/panels/lovelace/hui-root.js
@@ -3,6 +3,9 @@ import '@polymer/app-layout/app-header/app-header.js';
import '@polymer/app-layout/app-toolbar/app-toolbar.js';
import '@polymer/app-route/app-route.js';
import '@polymer/paper-icon-button/paper-icon-button.js';
+import '@polymer/paper-item/paper-item.js';
+import '@polymer/paper-listbox/paper-listbox.js';
+import '@polymer/paper-menu-button/paper-menu-button.js';
import '@polymer/paper-tabs/paper-tab.js';
import '@polymer/paper-tabs/paper-tabs.js';
@@ -18,6 +21,7 @@ import '../../layouts/ha-app-layout.js';
import '../../components/ha-start-voice-button.js';
import '../../components/ha-icon.js';
import { loadModule, loadJS } from '../../common/dom/load_resource.js';
+import './hui-unused-entities.js';
import './hui-view.js';
import createCardElement from './common/create-card-element.js';
@@ -53,11 +57,19 @@ class HUIRoot extends NavigateMixin(EventsMixin(PolymerElement)) {
[[_computeTitle(config)]]
-
-
-
-
+
+
+
+ Refresh
+ Unused entities
+ Help
+
+
@@ -148,6 +160,18 @@ class HUIRoot extends NavigateMixin(EventsMixin(PolymerElement)) {
this.fire('config-refresh');
}
+ _handleUnusedEntities() {
+ this._selectView('unused');
+ }
+
+ _deselect(ev) {
+ ev.target.selected = null;
+ }
+
+ _handleHelp() {
+ window.open('https://developers.home-assistant.io/docs/en/lovelace_index.html', '_blank');
+ }
+
_handleViewSelected(ev) {
const index = ev.detail.selected;
if (index !== this._curView) {
@@ -166,16 +190,20 @@ class HUIRoot extends NavigateMixin(EventsMixin(PolymerElement)) {
root.removeChild(root.lastChild);
}
- const viewConfig = this.config.views[this._curView];
-
let view;
- if (viewConfig.panel) {
- view = createCardElement(viewConfig.cards[0]);
+ if (viewIndex === 'unused') {
+ view = document.createElement('hui-unused-entities');
+ view.config = this.config;
} else {
- view = document.createElement('hui-view');
- view.config = viewConfig;
- view.columns = this.columns;
+ const viewConfig = this.config.views[this._curView];
+ if (viewConfig.panel) {
+ view = createCardElement(viewConfig.cards[0]);
+ } else {
+ view = document.createElement('hui-view');
+ view.config = viewConfig;
+ view.columns = this.columns;
+ }
}
view.hass = this.hass;
diff --git a/src/panels/lovelace/hui-unused-entities.js b/src/panels/lovelace/hui-unused-entities.js
new file mode 100644
index 0000000000..27abdb7320
--- /dev/null
+++ b/src/panels/lovelace/hui-unused-entities.js
@@ -0,0 +1,57 @@
+import { html } from '@polymer/polymer/lib/utils/html-tag.js';
+import { PolymerElement } from '@polymer/polymer/polymer-element.js';
+
+import computeUnusedEntities from './common/compute-unused-entities.js';
+import createCardElement from './common/create-card-element.js';
+
+import './cards/hui-entities-card.js';
+
+class HuiUnusedEntities extends PolymerElement {
+ static get template() {
+ return html`
+
+
+ `;
+ }
+
+ static get properties() {
+ return {
+ hass: {
+ type: Object,
+ observer: '_hassChanged'
+ },
+ config: {
+ type: Object,
+ observer: '_configChanged'
+ },
+ };
+ }
+
+ _configChanged(config) {
+ const root = this.$.root;
+ if (root.lastChild) root.removeChild(root.lastChild);
+
+ const entities = computeUnusedEntities(this.hass, config);
+ const cardConfig = {
+ type: 'entities',
+ title: 'Unused entities',
+ entities,
+ show_header_toggle: false
+ };
+ const element = createCardElement(cardConfig);
+ element.hass = this.hass;
+ root.appendChild(element);
+ }
+
+ _hassChanged(hass) {
+ const root = this.$.root;
+ if (!root.lastChild) return;
+ root.lastChild.hass = hass;
+ }
+}
+customElements.define('hui-unused-entities', HuiUnusedEntities);