304 lines
8.4 KiB
HTML
304 lines
8.4 KiB
HTML
<link rel="import" href="../../bower_components/polymer/polymer-element.html">
|
|
|
|
<link rel="import" href="../../bower_components/paper-button/paper-button.html">
|
|
<link rel="import" href="../../bower_components/paper-input/paper-input.html">
|
|
<link rel="import" href="../../bower_components/paper-input/paper-textarea.html">
|
|
<link rel="import" href="../../bower_components/paper-checkbox/paper-checkbox.html">
|
|
|
|
<link rel="import" href="../../bower_components/app-layout/app-header-layout/app-header-layout.html">
|
|
<link rel="import" href="../../bower_components/app-layout/app-header/app-header.html">
|
|
<link rel="import" href="../../bower_components/app-layout/app-toolbar/app-toolbar.html">
|
|
|
|
<link rel="import" href="../../src/components/ha-menu-button.html">
|
|
<link rel="import" href="../../src/components/entity/ha-entity-picker.html">
|
|
<link rel="import" href="../../src/resources/ha-style.html">
|
|
|
|
<dom-module id="ha-panel-dev-state">
|
|
<template>
|
|
<style include="ha-style">
|
|
:host {
|
|
-ms-user-select: initial;
|
|
-webkit-user-select: initial;
|
|
-moz-user-select: initial;
|
|
}
|
|
|
|
.content {
|
|
padding: 16px;
|
|
}
|
|
|
|
ha-entity-picker, .state-input, paper-textarea {
|
|
display: block;
|
|
max-width: 400px;
|
|
}
|
|
|
|
.entities th {
|
|
text-align: left;
|
|
}
|
|
|
|
.entities tr {
|
|
vertical-align: top;
|
|
}
|
|
|
|
.entities tr:nth-child(odd) {
|
|
background-color: var(--table-row-background-color, #fff)
|
|
}
|
|
|
|
.entities tr:nth-child(even) {
|
|
background-color: var(--table-row-alternative-background-color, #eee)
|
|
}
|
|
|
|
.entities td:nth-child(3) {
|
|
white-space: pre-wrap;
|
|
word-break: break-word;
|
|
}
|
|
|
|
.entities a {
|
|
color: var(--primary-color);
|
|
}
|
|
</style>
|
|
|
|
<app-header-layout has-scrolling-region>
|
|
<app-header slot="header" fixed>
|
|
<app-toolbar>
|
|
<ha-menu-button narrow='[[narrow]]' show-menu='[[showMenu]]'></ha-menu-button>
|
|
<div main-title>States</div>
|
|
</app-toolbar>
|
|
</app-header>
|
|
|
|
<div class='content'>
|
|
<div>
|
|
<p>
|
|
Set the representation of a device within Home Assistant.<br />
|
|
This will not communicate with the actual device.
|
|
</p>
|
|
|
|
<ha-entity-picker
|
|
autofocus
|
|
hass="[[hass]]"
|
|
value="{{_entityId}}"
|
|
allow-custom-entity
|
|
></ha-entity-picker>
|
|
<paper-input
|
|
label="State"
|
|
required
|
|
value='{{_state}}'
|
|
class='state-input'
|
|
></paper-input>
|
|
<paper-textarea label="State attributes (JSON, optional)" value='{{_stateAttributes}}'></paper-textarea>
|
|
<paper-button on-click='handleSetState' raised>Set State</paper-button>
|
|
</div>
|
|
|
|
<h1>Current entities</h1>
|
|
<table class='entities'>
|
|
<tr>
|
|
<th>Entity</th>
|
|
<th>State</th>
|
|
<th hidden$='[[narrow]]'>
|
|
Attributes
|
|
<paper-checkbox checked='{{_showAttributes}}'></paper-checkbox>
|
|
</th>
|
|
</tr>
|
|
<tr>
|
|
<th><paper-input label="Filter entities" type="search" value='{{_entityFilter}}'></paper-input></th>
|
|
<th><paper-input label="Filter states" type="search" value='{{_stateFilter}}'></paper-input></th>
|
|
<th hidden$='[[!computeShowAttributes(narrow, _showAttributes)]]'><paper-input label="Filter attributes" type="search" value='{{_attributeFilter}}'></paper-input></th>
|
|
</tr>
|
|
<tr hidden$='[[!computeShowEntitiesPlaceholder(_entities)]]'>
|
|
<td colspan="3">No entities</td>
|
|
</tr>
|
|
<template is='dom-repeat' items='[[_entities]]' as='entity'>
|
|
<tr>
|
|
<td><a href='#' on-click='entitySelected'>[[entity.entity_id]]</a></td>
|
|
<td>[[entity.state]]</td>
|
|
<template is='dom-if' if='[[computeShowAttributes(narrow, _showAttributes)]]'>
|
|
<td>[[attributeString(entity)]]</td>
|
|
</template>
|
|
</tr>
|
|
</template>
|
|
</table>
|
|
</div>
|
|
</app-header-layout>
|
|
</template>
|
|
</dom-module>
|
|
|
|
<script>
|
|
class HaPanelDevState extends Polymer.Element {
|
|
static get is() { return 'ha-panel-dev-state'; }
|
|
|
|
static get properties() {
|
|
return {
|
|
hass: {
|
|
type: Object,
|
|
},
|
|
|
|
narrow: {
|
|
type: Boolean,
|
|
value: false,
|
|
},
|
|
|
|
showMenu: {
|
|
type: Boolean,
|
|
value: false,
|
|
},
|
|
|
|
_entityId: {
|
|
type: String,
|
|
value: '',
|
|
},
|
|
|
|
_entityFilter: {
|
|
type: String,
|
|
value: '',
|
|
},
|
|
|
|
_stateFilter: {
|
|
type: String,
|
|
value: '',
|
|
},
|
|
|
|
_attributeFilter: {
|
|
type: String,
|
|
value: '',
|
|
},
|
|
|
|
_state: {
|
|
type: String,
|
|
value: '',
|
|
},
|
|
|
|
_stateAttributes: {
|
|
type: String,
|
|
value: '',
|
|
},
|
|
|
|
_showAttributes: {
|
|
type: Boolean,
|
|
value: true,
|
|
},
|
|
|
|
_entities: {
|
|
type: Array,
|
|
computed: 'computeEntities(hass, _entityFilter, _stateFilter, _attributeFilter)',
|
|
},
|
|
};
|
|
}
|
|
|
|
entitySelected(ev) {
|
|
var state = ev.model.entity;
|
|
this._entityId = state.entity_id;
|
|
this._state = state.state;
|
|
this._stateAttributes = JSON.stringify(state.attributes, null, ' ');
|
|
ev.preventDefault();
|
|
}
|
|
|
|
handleSetState() {
|
|
var attr;
|
|
var attrRaw = this._stateAttributes.replace(/^\s+|\s+$/g, '');
|
|
try {
|
|
attr = attrRaw ?
|
|
JSON.parse(attrRaw) : {};
|
|
} catch (err) {
|
|
/* eslint-disable no-alert */
|
|
alert('Error parsing JSON: ' + err);
|
|
/* eslint-enable no-alert */
|
|
return;
|
|
}
|
|
|
|
this.hass.callApi('POST', 'states/' + this._entityId, {
|
|
state: this._state,
|
|
attributes: attr,
|
|
});
|
|
}
|
|
|
|
computeEntities(hass, _entityFilter, _stateFilter, _attributeFilter) {
|
|
return Object.keys(hass.states).map(function (key) { return hass.states[key]; })
|
|
.filter(function (value) {
|
|
if (!value.entity_id.includes(_entityFilter.toLowerCase())) {
|
|
return false;
|
|
}
|
|
|
|
if (!value.state.includes(_stateFilter.toLowerCase())) {
|
|
return false;
|
|
}
|
|
|
|
if (_attributeFilter !== '') {
|
|
var attributeFilter = _attributeFilter.toLowerCase();
|
|
var colonIndex = attributeFilter.indexOf(':');
|
|
var multiMode = colonIndex !== -1;
|
|
|
|
var keyFilter = attributeFilter;
|
|
var valueFilter = attributeFilter;
|
|
|
|
if (multiMode) {
|
|
// we need to filter keys and values separately
|
|
keyFilter = attributeFilter.substring(0, colonIndex).trim();
|
|
valueFilter = attributeFilter.substring(colonIndex + 1).trim();
|
|
}
|
|
|
|
var attributeKeys = Object.keys(value.attributes);
|
|
|
|
for (var i = 0; i < attributeKeys.length; i++) {
|
|
var key = attributeKeys[i];
|
|
|
|
if (key.includes(keyFilter) && !multiMode) {
|
|
return true; // in single mode we're already satisfied with this match
|
|
} else if (!key.includes(keyFilter) && multiMode) {
|
|
continue;
|
|
}
|
|
|
|
var attributeValue = value.attributes[key];
|
|
|
|
if (attributeValue !== null
|
|
&& JSON.stringify(attributeValue).toLowerCase().includes(valueFilter)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// there are no attributes where the key and/or value can be matched
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
})
|
|
.sort(function (entityA, entityB) {
|
|
if (entityA.entity_id < entityB.entity_id) {
|
|
return -1;
|
|
}
|
|
if (entityA.entity_id > entityB.entity_id) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
});
|
|
}
|
|
|
|
computeShowEntitiesPlaceholder(_entities) {
|
|
return _entities.length === 0;
|
|
}
|
|
|
|
computeShowAttributes(narrow, _showAttributes) {
|
|
return !narrow && _showAttributes;
|
|
}
|
|
|
|
attributeString(entity) {
|
|
var output = '';
|
|
var i;
|
|
var keys;
|
|
var key;
|
|
var value;
|
|
|
|
for (i = 0, keys = Object.keys(entity.attributes); i < keys.length; i++) {
|
|
key = keys[i];
|
|
value = entity.attributes[key];
|
|
if (!Array.isArray(value) && (value instanceof Object)) {
|
|
value = JSON.stringify(value, null, ' ');
|
|
}
|
|
output += key + ': ' + value + '\n';
|
|
}
|
|
|
|
return output;
|
|
}
|
|
}
|
|
|
|
customElements.define(HaPanelDevState.is, HaPanelDevState);
|
|
</script>
|