mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-18 14:56:37 +00:00
Migrate developer state tools to LitElement (#18134)
Co-authored-by: Steve Repsher <steverep@users.noreply.github.com>
This commit is contained in:
parent
7ee91ca8fc
commit
a08185a1a5
@ -1,699 +0,0 @@
|
|||||||
import { addHours } from "date-fns/esm";
|
|
||||||
import "@material/mwc-button";
|
|
||||||
import {
|
|
||||||
mdiClipboardTextMultipleOutline,
|
|
||||||
mdiInformationOutline,
|
|
||||||
mdiRefresh,
|
|
||||||
} from "@mdi/js";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
/* eslint-plugin-disable lit */
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
import { dump, load } from "js-yaml";
|
|
||||||
import { formatDateTimeWithSeconds } from "../../../common/datetime/format_date_time";
|
|
||||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
|
||||||
import { escapeRegExp } from "../../../common/string/escape_regexp";
|
|
||||||
import { copyToClipboard } from "../../../common/util/copy-clipboard";
|
|
||||||
import "../../../components/entity/ha-entity-picker";
|
|
||||||
import "../../../components/ha-code-editor";
|
|
||||||
import "../../../components/ha-icon-button";
|
|
||||||
import "../../../components/ha-svg-icon";
|
|
||||||
import "../../../components/ha-checkbox";
|
|
||||||
import "../../../components/ha-tip";
|
|
||||||
import "../../../components/ha-alert";
|
|
||||||
import "../../../components/search-input";
|
|
||||||
import "../../../components/ha-expansion-panel";
|
|
||||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
|
||||||
import { EventsMixin } from "../../../mixins/events-mixin";
|
|
||||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
|
||||||
import "../../../styles/polymer-ha-style";
|
|
||||||
|
|
||||||
const ERROR_SENTINEL = {};
|
|
||||||
/*
|
|
||||||
* @appliesMixin EventsMixin
|
|
||||||
* @appliesMixin LocalizeMixin
|
|
||||||
*/
|
|
||||||
class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style include="ha-style">
|
|
||||||
:host {
|
|
||||||
-ms-user-select: initial;
|
|
||||||
-webkit-user-select: initial;
|
|
||||||
-moz-user-select: initial;
|
|
||||||
display: block;
|
|
||||||
padding: 16px;
|
|
||||||
padding: max(16px, env(safe-area-inset-top))
|
|
||||||
max(16px, env(safe-area-inset-right))
|
|
||||||
max(16px, env(safe-area-inset-bottom))
|
|
||||||
max(16px, env(safe-area-inset-left));
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-textfield {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.state-input {
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-expansion-panel {
|
|
||||||
margin: 0 8px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputs {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 800px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info {
|
|
||||||
padding: 0 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-row {
|
|
||||||
display: flex;
|
|
||||||
margin-top: 8px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-wrapper {
|
|
||||||
width: 100%;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.entities th {
|
|
||||||
padding: 0 8px;
|
|
||||||
text-align: left;
|
|
||||||
font-size: var(
|
|
||||||
--paper-input-container-shared-input-style_-_font-size
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
.filters th {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filters search-input {
|
|
||||||
display: block;
|
|
||||||
--mdc-text-field-fill-color: transparent;
|
|
||||||
}
|
|
||||||
ha-tip {
|
|
||||||
display: flex;
|
|
||||||
padding: 8px 0;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
th.attributes {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
th.attributes ha-checkbox {
|
|
||||||
position: absolute;
|
|
||||||
bottom: -8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host([rtl]) .entities th {
|
|
||||||
text-align: right;
|
|
||||||
direction: rtl;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host([rtl]) .filters {
|
|
||||||
direction: rtl;
|
|
||||||
}
|
|
||||||
|
|
||||||
.entities tr {
|
|
||||||
vertical-align: top;
|
|
||||||
direction: ltr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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 {
|
|
||||||
padding: 4px;
|
|
||||||
min-width: 200px;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
.entities ha-svg-icon {
|
|
||||||
--mdc-icon-size: 20px;
|
|
||||||
padding: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
.entities td:nth-child(1) {
|
|
||||||
min-width: 300px;
|
|
||||||
width: 30%;
|
|
||||||
}
|
|
||||||
.entities td:nth-child(3) {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.entities a {
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.entities .id-name-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.entities .id-name-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host([narrow]) .state-wrapper {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host([narrow]) .info {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<h1>
|
|
||||||
[[localize('ui.panel.developer-tools.tabs.states.current_entities')]]
|
|
||||||
</h1>
|
|
||||||
<ha-expansion-panel
|
|
||||||
header="[[localize('ui.panel.developer-tools.tabs.states.set_state')]]"
|
|
||||||
outlined
|
|
||||||
expanded="[[_expanded]]"
|
|
||||||
on-expanded-changed="expandedChanged"
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
[[localize('ui.panel.developer-tools.tabs.states.description1')]]<br />
|
|
||||||
[[localize('ui.panel.developer-tools.tabs.states.description2')]]
|
|
||||||
</p>
|
|
||||||
<template is="dom-if" if="[[_error]]">
|
|
||||||
<ha-alert alert-type="error">[[_error]]</ha-alert>
|
|
||||||
</template>
|
|
||||||
<div class="state-wrapper flex layout horizontal">
|
|
||||||
<div class="inputs">
|
|
||||||
<ha-entity-picker
|
|
||||||
autofocus
|
|
||||||
hass="[[hass]]"
|
|
||||||
value="{{_entityId}}"
|
|
||||||
on-change="entityIdChanged"
|
|
||||||
allow-custom-entity
|
|
||||||
item-label-path="entity_id"
|
|
||||||
></ha-entity-picker>
|
|
||||||
<ha-tip hass="[[hass]]">[[localize('ui.tips.key_e_hint')]]</ha-tip>
|
|
||||||
<ha-textfield
|
|
||||||
label="[[localize('ui.panel.developer-tools.tabs.states.state')]]"
|
|
||||||
required
|
|
||||||
autocapitalize="none"
|
|
||||||
autocomplete="off"
|
|
||||||
autocorrect="off"
|
|
||||||
input-spellcheck="false"
|
|
||||||
value="[[_state]]"
|
|
||||||
on-change="stateChanged"
|
|
||||||
class="state-input"
|
|
||||||
></ha-textfield>
|
|
||||||
<p>
|
|
||||||
[[localize('ui.panel.developer-tools.tabs.states.state_attributes')]]
|
|
||||||
</p>
|
|
||||||
<ha-code-editor
|
|
||||||
mode="yaml"
|
|
||||||
value="[[_stateAttributes]]"
|
|
||||||
error="[[!validJSON]]"
|
|
||||||
on-value-changed="_yamlChanged"
|
|
||||||
dir="ltr"
|
|
||||||
></ha-code-editor>
|
|
||||||
<div class="button-row">
|
|
||||||
<mwc-button
|
|
||||||
on-click="handleSetState"
|
|
||||||
disabled="[[!validJSON]]"
|
|
||||||
raised
|
|
||||||
>[[localize('ui.panel.developer-tools.tabs.states.set_state')]]</mwc-button
|
|
||||||
>
|
|
||||||
<ha-icon-button
|
|
||||||
on-click="entityIdChanged"
|
|
||||||
label="[[localize('ui.common.refresh')]]"
|
|
||||||
path="[[refreshIcon()]]"
|
|
||||||
></ha-icon-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="info">
|
|
||||||
<template is="dom-if" if="[[_entity]]">
|
|
||||||
<p>
|
|
||||||
<b
|
|
||||||
>[[localize('ui.panel.developer-tools.tabs.states.last_changed')]]:</b
|
|
||||||
><br />
|
|
||||||
<a href="[[historyFromLastChanged(_entity)]]"
|
|
||||||
>[[lastChangedString(_entity)]]</a
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<b
|
|
||||||
>[[localize('ui.panel.developer-tools.tabs.states.last_updated')]]:</b
|
|
||||||
><br />
|
|
||||||
<a href="[[historyFromLastUpdated(_entity)]]"
|
|
||||||
>[[lastUpdatedString(_entity)]]</a
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ha-expansion-panel>
|
|
||||||
<div class="table-wrapper">
|
|
||||||
<table class="entities">
|
|
||||||
<tr>
|
|
||||||
<th>[[localize('ui.panel.developer-tools.tabs.states.entity')]]</th>
|
|
||||||
<th>[[localize('ui.panel.developer-tools.tabs.states.state')]]</th>
|
|
||||||
<th hidden$="[[narrow]]" class="attributes">
|
|
||||||
[[localize('ui.panel.developer-tools.tabs.states.attributes')]]
|
|
||||||
<ha-checkbox
|
|
||||||
checked="[[_showAttributes]]"
|
|
||||||
on-change="saveAttributeCheckboxState"
|
|
||||||
reducedTouchTarget
|
|
||||||
></ha-checkbox>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
<tr class="filters">
|
|
||||||
<th>
|
|
||||||
<search-input
|
|
||||||
label="[[localize('ui.panel.developer-tools.tabs.states.filter_entities')]]"
|
|
||||||
value="[[_entityFilter]]"
|
|
||||||
on-value-changed="_entityFilterChanged"
|
|
||||||
></search-input>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
<search-input
|
|
||||||
label="[[localize('ui.panel.developer-tools.tabs.states.filter_states')]]"
|
|
||||||
type="search"
|
|
||||||
value="[[_stateFilter]]"
|
|
||||||
on-value-changed="_stateFilterChanged"
|
|
||||||
></search-input>
|
|
||||||
</th>
|
|
||||||
<th hidden$="[[!computeShowAttributes(narrow, _showAttributes)]]">
|
|
||||||
<search-input
|
|
||||||
label="[[localize('ui.panel.developer-tools.tabs.states.filter_attributes')]]"
|
|
||||||
type="search"
|
|
||||||
value="[[_attributeFilter]]"
|
|
||||||
on-value-changed="_attributeFilterChanged"
|
|
||||||
></search-input>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
<tr hidden$="[[!computeShowEntitiesPlaceholder(_entities)]]">
|
|
||||||
<td colspan="3">
|
|
||||||
[[localize('ui.panel.developer-tools.tabs.states.no_entities')]]
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<template is="dom-repeat" items="[[_entities]]" as="entity">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<div class="id-name-container">
|
|
||||||
<div class="id-name-row">
|
|
||||||
<ha-svg-icon
|
|
||||||
on-click="copyEntity"
|
|
||||||
alt="[[localize('ui.panel.developer-tools.tabs.states.copy_id')]]"
|
|
||||||
title="[[localize('ui.panel.developer-tools.tabs.states.copy_id')]]"
|
|
||||||
path="[[clipboardOutlineIcon()]]"
|
|
||||||
></ha-svg-icon>
|
|
||||||
<a href="#" on-click="entitySelected"
|
|
||||||
>[[entity.entity_id]]</a
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="id-name-row">
|
|
||||||
<ha-svg-icon
|
|
||||||
on-click="entityMoreInfo"
|
|
||||||
alt="[[localize('ui.panel.developer-tools.tabs.states.more_info')]]"
|
|
||||||
title="[[localize('ui.panel.developer-tools.tabs.states.more_info')]]"
|
|
||||||
path="[[informationOutlineIcon()]]"
|
|
||||||
></ha-svg-icon>
|
|
||||||
<span class="secondary">
|
|
||||||
[[entity.attributes.friendly_name]]
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>[[entity.state]]</td>
|
|
||||||
<template
|
|
||||||
is="dom-if"
|
|
||||||
if="[[computeShowAttributes(narrow, _showAttributes)]]"
|
|
||||||
>
|
|
||||||
<td>[[attributeString(entity)]]</td>
|
|
||||||
</template>
|
|
||||||
</tr>
|
|
||||||
</template>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: {
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
|
|
||||||
parsedJSON: {
|
|
||||||
type: Object,
|
|
||||||
computed: "_computeParsedStateAttributes(_stateAttributes)",
|
|
||||||
},
|
|
||||||
|
|
||||||
validJSON: {
|
|
||||||
type: Boolean,
|
|
||||||
computed: "_computeValidJSON(parsedJSON)",
|
|
||||||
},
|
|
||||||
|
|
||||||
_error: {
|
|
||||||
type: String,
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
|
|
||||||
_entityId: {
|
|
||||||
type: String,
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
|
|
||||||
_entityFilter: {
|
|
||||||
type: String,
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
|
|
||||||
_stateFilter: {
|
|
||||||
type: String,
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
|
|
||||||
_attributeFilter: {
|
|
||||||
type: String,
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
|
|
||||||
_entity: {
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
|
|
||||||
_state: {
|
|
||||||
type: String,
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
|
|
||||||
_stateAttributes: {
|
|
||||||
type: String,
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
|
|
||||||
_showAttributes: {
|
|
||||||
type: Boolean,
|
|
||||||
value: JSON.parse(
|
|
||||||
localStorage.getItem("devToolsShowAttributes") || true
|
|
||||||
),
|
|
||||||
},
|
|
||||||
|
|
||||||
_entities: {
|
|
||||||
type: Array,
|
|
||||||
computed:
|
|
||||||
"computeEntities(hass, _entityFilter, _stateFilter, _attributeFilter)",
|
|
||||||
},
|
|
||||||
|
|
||||||
_expanded: {
|
|
||||||
type: Boolean,
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
narrow: {
|
|
||||||
type: Boolean,
|
|
||||||
reflectToAttribute: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
rtl: {
|
|
||||||
reflectToAttribute: true,
|
|
||||||
computed: "_computeRTL(hass)",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
copyEntity(ev) {
|
|
||||||
ev.preventDefault();
|
|
||||||
copyToClipboard(ev.model.entity.entity_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
entitySelected(ev) {
|
|
||||||
const state = ev.model.entity;
|
|
||||||
this._entityId = state.entity_id;
|
|
||||||
this._entity = state;
|
|
||||||
this._state = state.state;
|
|
||||||
this._stateAttributes = dump(state.attributes);
|
|
||||||
this._expanded = true;
|
|
||||||
ev.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
entityIdChanged() {
|
|
||||||
if (!this._entityId) {
|
|
||||||
this._entity = undefined;
|
|
||||||
this._state = "";
|
|
||||||
this._stateAttributes = "";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const state = this.hass.states[this._entityId];
|
|
||||||
if (!state) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._entity = state;
|
|
||||||
this._state = state.state;
|
|
||||||
this._stateAttributes = dump(state.attributes);
|
|
||||||
this._expanded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
stateChanged(ev) {
|
|
||||||
this._state = ev.target.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
_entityFilterChanged(ev) {
|
|
||||||
this._entityFilter = ev.detail.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
_stateFilterChanged(ev) {
|
|
||||||
this._stateFilter = ev.detail.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
_attributeFilterChanged(ev) {
|
|
||||||
this._attributeFilter = ev.detail.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
_getHistoryURL(entityId, inputDate) {
|
|
||||||
const date = new Date(inputDate);
|
|
||||||
const hourBefore = addHours(date, -1).toISOString();
|
|
||||||
return `/history?entity_id=${entityId}&start_date=${hourBefore}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
historyFromLastChanged(entity) {
|
|
||||||
return this._getHistoryURL(entity.entity_id, entity.last_changed);
|
|
||||||
}
|
|
||||||
|
|
||||||
historyFromLastUpdated(entity) {
|
|
||||||
return this._getHistoryURL(entity.entity_id, entity.last_updated);
|
|
||||||
}
|
|
||||||
|
|
||||||
expandedChanged(ev) {
|
|
||||||
this._expanded = ev.detail.expanded;
|
|
||||||
}
|
|
||||||
|
|
||||||
entityMoreInfo(ev) {
|
|
||||||
ev.preventDefault();
|
|
||||||
this.fire("hass-more-info", { entityId: ev.model.entity.entity_id });
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleSetState() {
|
|
||||||
this._error = "";
|
|
||||||
if (!this._entityId) {
|
|
||||||
showAlertDialog(this, {
|
|
||||||
text: this.hass.localize(
|
|
||||||
"ui.panel.developer-tools.tabs.states.alert_entity_field"
|
|
||||||
),
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await this.hass.callApi("POST", "states/" + this._entityId, {
|
|
||||||
state: this._state,
|
|
||||||
attributes: this.parsedJSON,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
this._error = e.body?.message || "Unknown error";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
informationOutlineIcon() {
|
|
||||||
return mdiInformationOutline;
|
|
||||||
}
|
|
||||||
|
|
||||||
clipboardOutlineIcon() {
|
|
||||||
return mdiClipboardTextMultipleOutline;
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshIcon() {
|
|
||||||
return mdiRefresh;
|
|
||||||
}
|
|
||||||
|
|
||||||
computeEntities(hass, _entityFilter, _stateFilter, _attributeFilter) {
|
|
||||||
const entityFilterRegExp =
|
|
||||||
_entityFilter &&
|
|
||||||
RegExp(escapeRegExp(_entityFilter).replace(/\\\*/g, ".*"), "i");
|
|
||||||
|
|
||||||
const stateFilterRegExp =
|
|
||||||
_stateFilter &&
|
|
||||||
RegExp(escapeRegExp(_stateFilter).replace(/\\\*/g, ".*"), "i");
|
|
||||||
|
|
||||||
let keyFilterRegExp;
|
|
||||||
let valueFilterRegExp;
|
|
||||||
let multiMode = false;
|
|
||||||
|
|
||||||
if (_attributeFilter) {
|
|
||||||
const colonIndex = _attributeFilter.indexOf(":");
|
|
||||||
multiMode = colonIndex !== -1;
|
|
||||||
|
|
||||||
const keyFilter = multiMode
|
|
||||||
? _attributeFilter.substring(0, colonIndex).trim()
|
|
||||||
: _attributeFilter;
|
|
||||||
const valueFilter = multiMode
|
|
||||||
? _attributeFilter.substring(colonIndex + 1).trim()
|
|
||||||
: _attributeFilter;
|
|
||||||
|
|
||||||
keyFilterRegExp = RegExp(
|
|
||||||
escapeRegExp(keyFilter).replace(/\\\*/g, ".*"),
|
|
||||||
"i"
|
|
||||||
);
|
|
||||||
valueFilterRegExp = multiMode
|
|
||||||
? RegExp(escapeRegExp(valueFilter).replace(/\\\*/g, ".*"), "i")
|
|
||||||
: keyFilterRegExp;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.values(hass.states)
|
|
||||||
.filter((value) => {
|
|
||||||
if (
|
|
||||||
entityFilterRegExp &&
|
|
||||||
!entityFilterRegExp.test(value.entity_id) &&
|
|
||||||
(value.attributes.friendly_name === undefined ||
|
|
||||||
!entityFilterRegExp.test(value.attributes.friendly_name))
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stateFilterRegExp && !stateFilterRegExp.test(value.state)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keyFilterRegExp && valueFilterRegExp) {
|
|
||||||
for (const [key, attributeValue] of Object.entries(
|
|
||||||
value.attributes
|
|
||||||
)) {
|
|
||||||
const match = keyFilterRegExp.test(key);
|
|
||||||
if (match && !multiMode) {
|
|
||||||
return true; // in single mode we're already satisfied with this match
|
|
||||||
}
|
|
||||||
if (!match && multiMode) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
attributeValue !== undefined &&
|
|
||||||
valueFilterRegExp.test(JSON.stringify(attributeValue))
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// there are no attributes where the key and/or value can be matched
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
.sort((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) {
|
|
||||||
let output = "";
|
|
||||||
let i;
|
|
||||||
let keys;
|
|
||||||
let key;
|
|
||||||
let value;
|
|
||||||
|
|
||||||
for (i = 0, keys = Object.keys(entity.attributes); i < keys.length; i++) {
|
|
||||||
key = keys[i];
|
|
||||||
value = this.formatAttributeValue(entity.attributes[key]);
|
|
||||||
output += `${key}: ${value}\n`;
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastChangedString(entity) {
|
|
||||||
return formatDateTimeWithSeconds(
|
|
||||||
new Date(entity.last_changed),
|
|
||||||
this.hass.locale,
|
|
||||||
this.hass.config
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
lastUpdatedString(entity) {
|
|
||||||
return formatDateTimeWithSeconds(
|
|
||||||
new Date(entity.last_updated),
|
|
||||||
this.hass.locale,
|
|
||||||
this.hass.config
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
formatAttributeValue(value) {
|
|
||||||
if (
|
|
||||||
(Array.isArray(value) && value.some((val) => val instanceof Object)) ||
|
|
||||||
(!Array.isArray(value) && value instanceof Object)
|
|
||||||
) {
|
|
||||||
return `\n${dump(value)}`;
|
|
||||||
}
|
|
||||||
return Array.isArray(value) ? value.join(", ") : value;
|
|
||||||
}
|
|
||||||
|
|
||||||
saveAttributeCheckboxState(ev) {
|
|
||||||
this._showAttributes = ev.target.checked;
|
|
||||||
try {
|
|
||||||
localStorage.setItem("devToolsShowAttributes", ev.target.checked);
|
|
||||||
} catch (e) {
|
|
||||||
// Catch for Safari private mode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeParsedStateAttributes(stateAttributes) {
|
|
||||||
try {
|
|
||||||
return stateAttributes.trim() ? load(stateAttributes) : {};
|
|
||||||
} catch (err) {
|
|
||||||
return ERROR_SENTINEL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeValidJSON(parsedJSON) {
|
|
||||||
return parsedJSON !== ERROR_SENTINEL;
|
|
||||||
}
|
|
||||||
|
|
||||||
_yamlChanged(ev) {
|
|
||||||
this._stateAttributes = ev.detail.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeRTL(hass) {
|
|
||||||
return computeRTL(hass);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("developer-tools-state", HaPanelDevState);
|
|
708
src/panels/developer-tools/state/developer-tools-state.ts
Normal file
708
src/panels/developer-tools/state/developer-tools-state.ts
Normal file
@ -0,0 +1,708 @@
|
|||||||
|
import { addHours } from "date-fns/esm";
|
||||||
|
import "@material/mwc-button";
|
||||||
|
import {
|
||||||
|
mdiClipboardTextMultipleOutline,
|
||||||
|
mdiInformationOutline,
|
||||||
|
mdiRefresh,
|
||||||
|
} from "@mdi/js";
|
||||||
|
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import {
|
||||||
|
HassEntities,
|
||||||
|
HassEntity,
|
||||||
|
HassEntityAttributeBase,
|
||||||
|
} from "home-assistant-js-websocket";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import { formatDateTimeWithSeconds } from "../../../common/datetime/format_date_time";
|
||||||
|
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||||
|
import { escapeRegExp } from "../../../common/string/escape_regexp";
|
||||||
|
import { copyToClipboard } from "../../../common/util/copy-clipboard";
|
||||||
|
import "../../../components/entity/ha-entity-picker";
|
||||||
|
import "../../../components/ha-yaml-editor";
|
||||||
|
import "../../../components/ha-icon-button";
|
||||||
|
import "../../../components/ha-svg-icon";
|
||||||
|
import "../../../components/ha-checkbox";
|
||||||
|
import "../../../components/ha-tip";
|
||||||
|
import "../../../components/ha-alert";
|
||||||
|
import "../../../components/search-input";
|
||||||
|
import "../../../components/ha-expansion-panel";
|
||||||
|
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||||
|
import { haStyle } from "../../../resources/styles";
|
||||||
|
import { HomeAssistant } from "../../../types";
|
||||||
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
|
import { toggleAttribute } from "../../../common/dom/toggle_attribute";
|
||||||
|
import { storage } from "../../../common/decorators/storage";
|
||||||
|
|
||||||
|
@customElement("developer-tools-state")
|
||||||
|
class HaPanelDevState extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@state() private _error: string = "";
|
||||||
|
|
||||||
|
@state() private _entityId: string = "";
|
||||||
|
|
||||||
|
@state() private _entityFilter: string = "";
|
||||||
|
|
||||||
|
@state() private _stateFilter: string = "";
|
||||||
|
|
||||||
|
@state() private _attributeFilter: string = "";
|
||||||
|
|
||||||
|
@state() private _entity?: HassEntity;
|
||||||
|
|
||||||
|
@state() private _state: string = "";
|
||||||
|
|
||||||
|
@state() private _stateAttributes: HassEntityAttributeBase & {
|
||||||
|
[key: string]: any;
|
||||||
|
} = {};
|
||||||
|
|
||||||
|
@state() private _expanded = false;
|
||||||
|
|
||||||
|
@state() private _validJSON: boolean = true;
|
||||||
|
|
||||||
|
@storage({
|
||||||
|
key: "devToolsShowAttributes",
|
||||||
|
state: true,
|
||||||
|
})
|
||||||
|
private _showAttributes = true;
|
||||||
|
|
||||||
|
@property({ type: Boolean, reflect: true }) public narrow = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean, reflect: true }) public rtl = false;
|
||||||
|
|
||||||
|
private _filteredEntities = memoizeOne(
|
||||||
|
(
|
||||||
|
entityFilter: string,
|
||||||
|
stateFilter: string,
|
||||||
|
attributeFilter: string,
|
||||||
|
states: HassEntities
|
||||||
|
): HassEntity[] =>
|
||||||
|
this._applyFiltersOnEntities(
|
||||||
|
entityFilter,
|
||||||
|
stateFilter,
|
||||||
|
attributeFilter,
|
||||||
|
states
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const entities = this._filteredEntities(
|
||||||
|
this._entityFilter,
|
||||||
|
this._stateFilter,
|
||||||
|
this._attributeFilter,
|
||||||
|
this.hass.states
|
||||||
|
);
|
||||||
|
const showAttributes = !this.narrow && this._showAttributes;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<h1>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.states.current_entities"
|
||||||
|
)}
|
||||||
|
</h1>
|
||||||
|
<ha-expansion-panel
|
||||||
|
.header=${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.states.set_state"
|
||||||
|
)}
|
||||||
|
outlined
|
||||||
|
.expanded=${this._expanded}
|
||||||
|
@expanded-changed=${this._expandedChanged}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.states.description1"
|
||||||
|
)}<br />
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.states.description2"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
${this._error
|
||||||
|
? html`<ha-alert alert-type="error">${this._error}}</ha-alert>`
|
||||||
|
: nothing}
|
||||||
|
<div class="state-wrapper flex-horizontal">
|
||||||
|
<div class="inputs">
|
||||||
|
<ha-entity-picker
|
||||||
|
autofocus
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${this._entityId}
|
||||||
|
@change=${this._entityIdChanged}
|
||||||
|
allow-custom-entity
|
||||||
|
item-label-path="entity_id"
|
||||||
|
></ha-entity-picker>
|
||||||
|
<ha-tip .hass=${this.hass}
|
||||||
|
>${this.hass.localize("ui.tips.key_e_hint")}</ha-tip
|
||||||
|
>
|
||||||
|
<ha-textfield
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.states.state"
|
||||||
|
)}
|
||||||
|
required
|
||||||
|
autocapitalize="none"
|
||||||
|
autocomplete="off"
|
||||||
|
autocorrect="off"
|
||||||
|
input-spellcheck="false"
|
||||||
|
.value=${this._state}
|
||||||
|
@change=${this._stateChanged}
|
||||||
|
class="state-input"
|
||||||
|
></ha-textfield>
|
||||||
|
<p>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.states.state_attributes"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<ha-yaml-editor
|
||||||
|
autoUpdate
|
||||||
|
.value=${this._stateAttributes}
|
||||||
|
.error=${!this._validJSON}
|
||||||
|
@value-changed=${this._yamlChanged}
|
||||||
|
dir="ltr"
|
||||||
|
></ha-yaml-editor>
|
||||||
|
<div class="button-row">
|
||||||
|
<mwc-button
|
||||||
|
@click=${this._handleSetState}
|
||||||
|
.disabled=${!this._validJSON}
|
||||||
|
raised
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.states.set_state"
|
||||||
|
)}</mwc-button
|
||||||
|
>
|
||||||
|
<ha-icon-button
|
||||||
|
@click=${this._entityIdChanged}
|
||||||
|
.label=${this.hass.localize("ui.common.refresh")}
|
||||||
|
.path=${mdiRefresh}
|
||||||
|
></ha-icon-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
${this._entity
|
||||||
|
? html`<p>
|
||||||
|
<b
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.states.last_changed"
|
||||||
|
)}:</b
|
||||||
|
><br />
|
||||||
|
<a href=${this._historyFromLastChanged(this._entity)}
|
||||||
|
>${this._lastChangedString(this._entity)}</a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.states.last_updated"
|
||||||
|
)}:</b
|
||||||
|
><br />
|
||||||
|
<a href=${this._historyFromLastUpdated(this._entity)}
|
||||||
|
>${this._lastUpdatedString(this._entity)}</a
|
||||||
|
>
|
||||||
|
</p>`
|
||||||
|
: nothing}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ha-expansion-panel>
|
||||||
|
<div class="table-wrapper">
|
||||||
|
<table class="entities">
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.states.entity"
|
||||||
|
)}
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.states.state"
|
||||||
|
)}
|
||||||
|
</th>
|
||||||
|
${!this.narrow
|
||||||
|
? html`<th class="attributes">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.states.attributes"
|
||||||
|
)}
|
||||||
|
<ha-checkbox
|
||||||
|
.checked=${this._showAttributes}
|
||||||
|
@change=${this._saveAttributeCheckboxState}
|
||||||
|
reducedTouchTarget
|
||||||
|
></ha-checkbox>
|
||||||
|
</th>`
|
||||||
|
: nothing}
|
||||||
|
</tr>
|
||||||
|
<tr class="filters">
|
||||||
|
<th>
|
||||||
|
<search-input
|
||||||
|
.hass=${this.hass}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.states.filter_entities"
|
||||||
|
)}
|
||||||
|
.value=${this._entityFilter}
|
||||||
|
@value-changed=${this._entityFilterChanged}
|
||||||
|
></search-input>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<search-input
|
||||||
|
.hass=${this.hass}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.states.filter_states"
|
||||||
|
)}
|
||||||
|
type="search"
|
||||||
|
.value=${this._stateFilter}
|
||||||
|
@value-changed=${this._stateFilterChanged}
|
||||||
|
></search-input>
|
||||||
|
</th>
|
||||||
|
${showAttributes
|
||||||
|
? html`<th>
|
||||||
|
<search-input
|
||||||
|
.hass=${this.hass}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.states.filter_attributes"
|
||||||
|
)}
|
||||||
|
type="search"
|
||||||
|
.value=${this._attributeFilter}
|
||||||
|
@value-changed=${this._attributeFilterChanged}
|
||||||
|
></search-input>
|
||||||
|
</th>`
|
||||||
|
: nothing}
|
||||||
|
</tr>
|
||||||
|
${entities.length === 0
|
||||||
|
? html`<tr>
|
||||||
|
<td colspan="3">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.states.no_entities"
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
</tr>`
|
||||||
|
: nothing}
|
||||||
|
${entities.map(
|
||||||
|
(entity) =>
|
||||||
|
html`<tr>
|
||||||
|
<td>
|
||||||
|
<div class="id-name-container">
|
||||||
|
<div class="id-name-row">
|
||||||
|
<ha-svg-icon
|
||||||
|
@click=${this._copyEntity}
|
||||||
|
.entity=${entity}
|
||||||
|
alt=${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.states.copy_id"
|
||||||
|
)}
|
||||||
|
title=${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.states.copy_id"
|
||||||
|
)}
|
||||||
|
.path=${mdiClipboardTextMultipleOutline}
|
||||||
|
></ha-svg-icon>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
.entity=${entity}
|
||||||
|
@click=${this._entitySelected}
|
||||||
|
>${entity.entity_id}</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="id-name-row">
|
||||||
|
<ha-svg-icon
|
||||||
|
@click=${this._entityMoreInfo}
|
||||||
|
.entity=${entity}
|
||||||
|
alt=${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.states.more_info"
|
||||||
|
)}
|
||||||
|
title=${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.states.more_info"
|
||||||
|
)}
|
||||||
|
.path=${mdiInformationOutline}
|
||||||
|
></ha-svg-icon>
|
||||||
|
<span class="secondary">
|
||||||
|
${entity.attributes.friendly_name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>${entity.state}</td>
|
||||||
|
${showAttributes
|
||||||
|
? html`<td>${this._attributeString(entity)}</td>`
|
||||||
|
: nothing}
|
||||||
|
</tr>`
|
||||||
|
)}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated(changedProps) {
|
||||||
|
super.updated(changedProps);
|
||||||
|
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||||
|
if (!oldHass || oldHass.locale !== this.hass.locale) {
|
||||||
|
toggleAttribute(this, "rtl", computeRTL(this.hass));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _copyEntity(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
const entity = (ev.currentTarget! as any).entity;
|
||||||
|
copyToClipboard(entity.entity_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _entitySelected(ev) {
|
||||||
|
const entityState: HassEntity = (ev.currentTarget! as any).entity;
|
||||||
|
this._entityId = entityState.entity_id;
|
||||||
|
this._entity = entityState;
|
||||||
|
this._state = entityState.state;
|
||||||
|
this._stateAttributes = entityState.attributes;
|
||||||
|
this._expanded = true;
|
||||||
|
ev.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _entityIdChanged() {
|
||||||
|
if (!this._entityId) {
|
||||||
|
this._entity = undefined;
|
||||||
|
this._state = "";
|
||||||
|
this._stateAttributes = {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const entityState = this.hass.states[this._entityId];
|
||||||
|
if (!entityState) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._entity = entityState;
|
||||||
|
this._state = entityState.state;
|
||||||
|
this._stateAttributes = entityState.attributes;
|
||||||
|
this._expanded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _stateChanged(ev) {
|
||||||
|
this._state = ev.target.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _entityFilterChanged(ev) {
|
||||||
|
this._entityFilter = ev.detail.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _stateFilterChanged(ev) {
|
||||||
|
this._stateFilter = ev.detail.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _attributeFilterChanged(ev) {
|
||||||
|
this._attributeFilter = ev.detail.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getHistoryURL(entityId, inputDate) {
|
||||||
|
const date = new Date(inputDate);
|
||||||
|
const hourBefore = addHours(date, -1).toISOString();
|
||||||
|
return `/history?entity_id=${entityId}&start_date=${hourBefore}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _historyFromLastChanged(entity) {
|
||||||
|
return this._getHistoryURL(entity.entity_id, entity.last_changed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _historyFromLastUpdated(entity) {
|
||||||
|
return this._getHistoryURL(entity.entity_id, entity.last_updated);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _expandedChanged(ev) {
|
||||||
|
this._expanded = ev.detail.expanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _entityMoreInfo(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
const entity = (ev.currentTarget! as any).entity;
|
||||||
|
fireEvent(this, "hass-more-info", { entityId: entity.entity_id });
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _handleSetState() {
|
||||||
|
this._error = "";
|
||||||
|
if (!this._entityId) {
|
||||||
|
showAlertDialog(this, {
|
||||||
|
text: this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.states.alert_entity_field"
|
||||||
|
),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await this.hass.callApi("POST", "states/" + this._entityId, {
|
||||||
|
state: this._state,
|
||||||
|
attributes: this._stateAttributes,
|
||||||
|
});
|
||||||
|
} catch (e: any) {
|
||||||
|
this._error = e.body?.message || "Unknown error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _applyFiltersOnEntities(
|
||||||
|
entityFilter: string,
|
||||||
|
stateFilter: string,
|
||||||
|
attributeFilter: string,
|
||||||
|
states: HassEntities
|
||||||
|
) {
|
||||||
|
const entityFilterRegExp =
|
||||||
|
entityFilter &&
|
||||||
|
RegExp(escapeRegExp(entityFilter).replace(/\\\*/g, ".*"), "i");
|
||||||
|
|
||||||
|
const stateFilterRegExp =
|
||||||
|
stateFilter &&
|
||||||
|
RegExp(escapeRegExp(stateFilter).replace(/\\\*/g, ".*"), "i");
|
||||||
|
|
||||||
|
let keyFilterRegExp;
|
||||||
|
let valueFilterRegExp;
|
||||||
|
let multiMode = false;
|
||||||
|
|
||||||
|
if (attributeFilter) {
|
||||||
|
const colonIndex = attributeFilter.indexOf(":");
|
||||||
|
multiMode = colonIndex !== -1;
|
||||||
|
|
||||||
|
const keyFilter = multiMode
|
||||||
|
? attributeFilter.substring(0, colonIndex).trim()
|
||||||
|
: attributeFilter;
|
||||||
|
const valueFilter = multiMode
|
||||||
|
? attributeFilter.substring(colonIndex + 1).trim()
|
||||||
|
: attributeFilter;
|
||||||
|
|
||||||
|
keyFilterRegExp = RegExp(
|
||||||
|
escapeRegExp(keyFilter).replace(/\\\*/g, ".*"),
|
||||||
|
"i"
|
||||||
|
);
|
||||||
|
valueFilterRegExp = multiMode
|
||||||
|
? RegExp(escapeRegExp(valueFilter).replace(/\\\*/g, ".*"), "i")
|
||||||
|
: keyFilterRegExp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.values(states)
|
||||||
|
.filter((value) => {
|
||||||
|
if (
|
||||||
|
entityFilterRegExp &&
|
||||||
|
!entityFilterRegExp.test(value.entity_id) &&
|
||||||
|
(value.attributes.friendly_name === undefined ||
|
||||||
|
!entityFilterRegExp.test(value.attributes.friendly_name))
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stateFilterRegExp && !stateFilterRegExp.test(value.state)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyFilterRegExp && valueFilterRegExp) {
|
||||||
|
for (const [key, attributeValue] of Object.entries(
|
||||||
|
value.attributes
|
||||||
|
)) {
|
||||||
|
const match = keyFilterRegExp.test(key);
|
||||||
|
if (match && !multiMode) {
|
||||||
|
return true; // in single mode we're already satisfied with this match
|
||||||
|
}
|
||||||
|
if (!match && multiMode) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
attributeValue !== undefined &&
|
||||||
|
valueFilterRegExp.test(JSON.stringify(attributeValue))
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// there are no attributes where the key and/or value can be matched
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.sort((entityA, entityB) => {
|
||||||
|
if (entityA.entity_id < entityB.entity_id) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (entityA.entity_id > entityB.entity_id) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _attributeString(entity) {
|
||||||
|
const output = "";
|
||||||
|
|
||||||
|
if (entity && entity.attributes) {
|
||||||
|
return Object.keys(entity.attributes).map(
|
||||||
|
(key) => `${key}: ${entity.attributes[key]}\n`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _lastChangedString(entity) {
|
||||||
|
return formatDateTimeWithSeconds(
|
||||||
|
new Date(entity.last_changed),
|
||||||
|
this.hass.locale,
|
||||||
|
this.hass.config
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _lastUpdatedString(entity) {
|
||||||
|
return formatDateTimeWithSeconds(
|
||||||
|
new Date(entity.last_updated),
|
||||||
|
this.hass.locale,
|
||||||
|
this.hass.config
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _saveAttributeCheckboxState(ev) {
|
||||||
|
this._showAttributes = ev.target.checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _yamlChanged(ev) {
|
||||||
|
this._stateAttributes = ev.detail.value;
|
||||||
|
this._validJSON = ev.detail.isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return [
|
||||||
|
haStyle,
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
-ms-user-select: initial;
|
||||||
|
-webkit-user-select: initial;
|
||||||
|
-moz-user-select: initial;
|
||||||
|
display: block;
|
||||||
|
padding: 16px;
|
||||||
|
padding: max(16px, env(safe-area-inset-top))
|
||||||
|
max(16px, env(safe-area-inset-right))
|
||||||
|
max(16px, env(safe-area-inset-bottom))
|
||||||
|
max(16px, env(safe-area-inset-left));
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-textfield {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.state-input {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-expansion-panel {
|
||||||
|
margin: 0 8px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputs {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-row {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entities th {
|
||||||
|
padding: 0 8px;
|
||||||
|
text-align: left;
|
||||||
|
font-size: var(
|
||||||
|
--paper-input-container-shared-input-style_-_font-size
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters th {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters search-input {
|
||||||
|
display: block;
|
||||||
|
--mdc-text-field-fill-color: transparent;
|
||||||
|
}
|
||||||
|
ha-tip {
|
||||||
|
display: flex;
|
||||||
|
padding: 8px 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
th.attributes {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
th.attributes ha-checkbox {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([rtl]) .entities th {
|
||||||
|
text-align: right;
|
||||||
|
direction: rtl;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([rtl]) .filters {
|
||||||
|
direction: rtl;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entities tr {
|
||||||
|
vertical-align: top;
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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 {
|
||||||
|
padding: 4px;
|
||||||
|
min-width: 200px;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
.entities ha-svg-icon {
|
||||||
|
--mdc-icon-size: 20px;
|
||||||
|
padding: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
.entities td:nth-child(1) {
|
||||||
|
min-width: 300px;
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
.entities td:nth-child(3) {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entities a {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.entities .id-name-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.entities .id-name-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([narrow]) .state-wrapper {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([narrow]) .info {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-horizontal {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"developer-tools-state": HaPanelDevState;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user