diff --git a/src/data/logbook.ts b/src/data/logbook.ts index c1ff44b6fe..00fd8e937d 100644 --- a/src/data/logbook.ts +++ b/src/data/logbook.ts @@ -23,7 +23,8 @@ export const getLogbookData = ( hass: HomeAssistant, startDate: string, endDate: string, - entityId?: string + entityId?: string, + entity_matches_only?: boolean ) => { const ALL_ENTITIES = "*"; @@ -51,7 +52,8 @@ export const getLogbookData = ( hass, startDate, endDate, - entityId !== ALL_ENTITIES ? entityId : undefined + entityId !== ALL_ENTITIES ? entityId : undefined, + entity_matches_only ).then((entries) => entries.reverse()); return DATA_CACHE[cacheKey][entityId]; }; @@ -60,11 +62,13 @@ const getLogbookDataFromServer = async ( hass: HomeAssistant, startDate: string, endDate: string, - entityId?: string + entityId?: string, + entity_matches_only?: boolean ) => { const url = `logbook/${startDate}?end_time=${endDate}${ entityId ? `&entity=${entityId}` : "" - }`; + }${entity_matches_only ? `&entity_matches_only` : ""}`; + return hass.callApi("GET", url); }; diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index b1953f678d..56d429836d 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -1,33 +1,34 @@ import "@material/mwc-button"; import "@material/mwc-icon-button"; -import "../../components/ha-header-bar"; -import "../../components/ha-dialog"; -import "../../components/ha-svg-icon"; +import "@material/mwc-tab"; +import "@material/mwc-tab-bar"; +import { mdiClose, mdiCog, mdiPencil } from "@mdi/js"; +import { + css, + customElement, + html, + internalProperty, + LitElement, + property, +} from "lit-element"; import { isComponentLoaded } from "../../common/config/is_component_loaded"; import { DOMAINS_MORE_INFO_NO_HISTORY } from "../../common/const"; +import { fireEvent } from "../../common/dom/fire_event"; +import { computeDomain } from "../../common/entity/compute_domain"; import { computeStateName } from "../../common/entity/compute_state_name"; import { navigate } from "../../common/navigate"; -import { fireEvent } from "../../common/dom/fire_event"; +import "../../components/ha-dialog"; +import "../../components/ha-header-bar"; +import "../../components/ha-svg-icon"; import "../../components/state-history-charts"; import { removeEntityRegistryEntry } from "../../data/entity_registry"; import { showEntityEditorDialog } from "../../panels/config/entities/show-dialog-entity-editor"; +import "../../panels/logbook/ha-logbook"; +import { haStyleDialog } from "../../resources/styles"; import "../../state-summary/state-card-content"; +import { HomeAssistant } from "../../types"; import { showConfirmationDialog } from "../generic/show-dialog-box"; import "./more-info-content"; -import { - customElement, - LitElement, - property, - internalProperty, - css, - html, -} from "lit-element"; -import { haStyleDialog } from "../../resources/styles"; -import { HomeAssistant } from "../../types"; -import { getRecentWithCache } from "../../data/cached-history"; -import { computeDomain } from "../../common/entity/compute_domain"; -import { mdiClose, mdiCog, mdiPencil } from "@mdi/js"; -import { HistoryResult } from "../../data/history"; const DOMAINS_NO_INFO = ["camera", "configurator"]; const EDITABLE_DOMAINS_WITH_ID = ["scene", "automation"]; @@ -43,11 +44,9 @@ export class MoreInfoDialog extends LitElement { @property({ type: Boolean, reflect: true }) public large = false; - @internalProperty() private _stateHistory?: HistoryResult; - @internalProperty() private _entityId?: string | null; - private _historyRefreshInterval?: number; + @internalProperty() private _currTabIndex = 0; public showDialog(params: MoreInfoDialogParams) { this._entityId = params.entityId; @@ -55,21 +54,11 @@ export class MoreInfoDialog extends LitElement { this.closeDialog(); } this.large = false; - this._stateHistory = undefined; - if (this._computeShowHistoryComponent(this._entityId)) { - this._getStateHistory(); - clearInterval(this._historyRefreshInterval); - this._historyRefreshInterval = window.setInterval(() => { - this._getStateHistory(); - }, 60 * 1000); - } } public closeDialog() { this._entityId = undefined; - this._stateHistory = undefined; - clearInterval(this._historyRefreshInterval); - this._historyRefreshInterval = undefined; + this._currTabIndex = 0; fireEvent(this, "dialog-closed", { dialog: this.localName }); } @@ -93,109 +82,123 @@ export class MoreInfoDialog extends LitElement { hideActions data-domain=${domain} > - - - - -
- ${computeStateName(stateObj)} -
- ${this.hass.user!.is_admin - ? html` - - ` - : ""} - ${this.hass.user!.is_admin && - ((EDITABLE_DOMAINS_WITH_ID.includes(domain) && - stateObj.attributes.id) || - EDITABLE_DOMAINS.includes(domain)) - ? html` - - ` - : ""} -
-
- ${DOMAINS_NO_INFO.includes(domain) - ? "" - : html` - - `} +
+ + + + +
+ ${computeStateName(stateObj)} +
+ ${this.hass.user!.is_admin + ? html` + + + + ` + : ""} + ${this.hass.user!.is_admin && + ((EDITABLE_DOMAINS_WITH_ID.includes(domain) && + stateObj.attributes.id) || + EDITABLE_DOMAINS.includes(domain)) + ? html` + + + + ` + : ""} +
${this._computeShowHistoryComponent(entityId) ? html` - + + + + ` : ""} - - - ${stateObj.attributes.restored - ? html`

- ${this.hass.localize( - "ui.dialogs.more_info_control.restored.not_provided" - )} -

-

- ${this.hass.localize( - "ui.dialogs.more_info_control.restored.remove_intro" - )} -

- - ${this.hass.localize( - "ui.dialogs.more_info_control.restored.remove_action" - )} - ` - : ""} +
+
+ ${this._currTabIndex === 0 + ? html` + ${DOMAINS_NO_INFO.includes(domain) + ? "" + : html` + + `} + + ${stateObj.attributes.restored + ? html` +

+ ${this.hass.localize( + "ui.dialogs.more_info_control.restored.not_provided" + )} +

+

+ ${this.hass.localize( + "ui.dialogs.more_info_control.restored.remove_intro" + )} +

+ + ${this.hass.localize( + "ui.dialogs.more_info_control.restored.remove_action" + )} + + ` + : ""} + ` + : html` + + `}
`; } - private _enlarge() { - this.large = !this.large; + protected firstUpdated(): void { + import("./ha-more-info-tab-history"); } - private async _getStateHistory(): Promise { - if (!this._entityId) { - return; - } - this._stateHistory = await getRecentWithCache( - this.hass!, - this._entityId, - { - refresh: 60, - cacheKey: `more_info.${this._entityId}`, - hoursToShow: 24, - }, - this.hass!.localize, - this.hass!.language - ); + private _enlarge() { + this.large = !this.large; } private _computeShowHistoryComponent(entityId) { @@ -243,6 +246,15 @@ export class MoreInfoDialog extends LitElement { this.closeDialog(); } + private _handleTabChanged(ev: CustomEvent): void { + const newTab = ev.detail.index; + if (newTab === this._currTabIndex) { + return; + } + + this._currTabIndex = ev.detail.index; + } + static get styles() { return [ haStyleDialog, @@ -256,8 +268,6 @@ export class MoreInfoDialog extends LitElement { --mdc-theme-on-primary: var(--primary-text-color); --mdc-theme-primary: var(--mdc-theme-surface); flex-shrink: 0; - border-bottom: 1px solid - var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12)); } @media all and (max-width: 450px), all and (max-height: 500px) { @@ -268,6 +278,11 @@ export class MoreInfoDialog extends LitElement { } } + .heading { + border-bottom: 1px solid + var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12)); + } + @media all and (min-width: 451px) and (min-height: 501px) { ha-dialog { --mdc-dialog-max-width: 90vw; @@ -306,8 +321,7 @@ export class MoreInfoDialog extends LitElement { --dialog-content-padding: 0; } - state-card-content, - state-history-charts { + state-card-content { display: block; margin-bottom: 16px; } @@ -315,3 +329,9 @@ export class MoreInfoDialog extends LitElement { ]; } } + +declare global { + interface HTMLElementTagNameMap { + "ha-more-info-dialog": MoreInfoDialog; + } +} diff --git a/src/dialogs/more-info/ha-more-info-tab-history.ts b/src/dialogs/more-info/ha-more-info-tab-history.ts new file mode 100644 index 0000000000..036d66bdb6 --- /dev/null +++ b/src/dialogs/more-info/ha-more-info-tab-history.ts @@ -0,0 +1,164 @@ +import { + css, + customElement, + html, + internalProperty, + LitElement, + property, + PropertyValues, + TemplateResult, +} from "lit-element"; +import { classMap } from "lit-html/directives/class-map"; +import { computeStateDomain } from "../../common/entity/compute_state_domain"; +import "../../components/ha-circular-progress"; +import "../../components/state-history-charts"; +import { getRecentWithCache } from "../../data/cached-history"; +import { HistoryResult } from "../../data/history"; +import { getLogbookData, LogbookEntry } from "../../data/logbook"; +import "../../panels/logbook/ha-logbook"; +import { haStyleDialog } from "../../resources/styles"; +import { HomeAssistant } from "../../types"; + +@customElement("ha-more-info-tab-history") +export class MoreInfoTabHistoryDialog extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property() public entityId!: string; + + @internalProperty() private _stateHistory?: HistoryResult; + + @internalProperty() private _entries?: LogbookEntry[]; + + @internalProperty() private _persons = {}; + + private _historyRefreshInterval?: number; + + protected render(): TemplateResult { + if (!this.entityId) { + return html``; + } + const stateObj = this.hass.states[this.entityId]; + + if (!stateObj) { + return html``; + } + + return html` + + ${!this._entries + ? html` + + ` + : html` + + `} + `; + } + + protected firstUpdated(): void { + this._fetchPersonNames(); + } + + protected updated(changedProps: PropertyValues): void { + super.updated(changedProps); + if (!this.entityId) { + clearInterval(this._historyRefreshInterval); + } + + if (changedProps.has("entityId")) { + this._stateHistory = undefined; + this._entries = undefined; + + this._getStateHistory(); + this._getLogBookData(); + + clearInterval(this._historyRefreshInterval); + this._historyRefreshInterval = window.setInterval(() => { + this._getStateHistory(); + }, 60 * 1000); + } + } + + private async _getStateHistory(): Promise { + this._stateHistory = await getRecentWithCache( + this.hass!, + this.entityId, + { + refresh: 60, + cacheKey: `more_info.${this.entityId}`, + hoursToShow: 24, + }, + this.hass!.localize, + this.hass!.language + ); + } + + private async _getLogBookData() { + const yesterday = new Date(new Date().getTime() - 24 * 60 * 60 * 1000); + const now = new Date(); + this._entries = await getLogbookData( + this.hass, + yesterday.toISOString(), + now.toISOString(), + this.entityId, + true + ); + } + + private _fetchPersonNames() { + Object.values(this.hass.states).forEach((entity) => { + if ( + entity.attributes.user_id && + computeStateDomain(entity) === "person" + ) { + this._persons[entity.attributes.user_id] = + entity.attributes.friendly_name; + } + }); + } + + static get styles() { + return [ + haStyleDialog, + css` + state-history-charts { + display: block; + margin-bottom: 16px; + } + + ha-logbook.has-entries { + height: 360px; + } + + ha-circular-progress { + display: flex; + justify-content: center; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-more-info-tab-history": MoreInfoTabHistoryDialog; + } +} diff --git a/src/panels/logbook/ha-logbook.ts b/src/panels/logbook/ha-logbook.ts index af886126d3..173dc59231 100644 --- a/src/panels/logbook/ha-logbook.ts +++ b/src/panels/logbook/ha-logbook.ts @@ -1,35 +1,48 @@ import { css, CSSResult, + customElement, + eventOptions, html, LitElement, property, PropertyValues, TemplateResult, - eventOptions, } from "lit-element"; +import { classMap } from "lit-html/directives/class-map"; import { scroll } from "lit-virtualizer"; import { formatDate } from "../../common/datetime/format_date"; import { formatTimeWithSeconds } from "../../common/datetime/format_time"; +import { restoreScroll } from "../../common/decorators/restore-scroll"; import { fireEvent } from "../../common/dom/fire_event"; import { domainIcon } from "../../common/entity/domain_icon"; import { stateIcon } from "../../common/entity/state_icon"; import { computeRTL, emitRTLDirection } from "../../common/util/compute_rtl"; +import "../../components/ha-circular-progress"; import "../../components/ha-icon"; import { LogbookEntry } from "../../data/logbook"; import { HomeAssistant } from "../../types"; -import { restoreScroll } from "../../common/decorators/restore-scroll"; +@customElement("ha-logbook") class HaLogbook extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public userIdToName = {}; + @property({ attribute: false }) public userIdToName = {}; - @property() public entries: LogbookEntry[] = []; + @property({ attribute: false }) public entries: LogbookEntry[] = []; - @property({ attribute: "rtl", type: Boolean, reflect: true }) + @property({ type: Boolean, attribute: "narrow" }) + public narrow = false; + + @property({ attribute: "rtl", type: Boolean }) private _rtl = false; + @property({ type: Boolean, attribute: "no-icon" }) + public noIcon = false; + + @property({ type: Boolean, attribute: "no-name" }) + public noName = false; + // @ts-ignore @restoreScroll(".container") private _savedScrollPos?: number; @@ -52,14 +65,22 @@ class HaLogbook extends LitElement { protected render(): TemplateResult { if (!this.entries?.length) { return html` -
+
${this.hass.localize("ui.panel.logbook.entries_not_found")}
`; } return html` -
+
${scroll({ items: this.entries, renderItem: (item: LogbookEntry, index?: number) => @@ -76,6 +97,7 @@ class HaLogbook extends LitElement { if (index === undefined) { return html``; } + const previous = this.entries[index - 1]; const state = item.entity_id ? this.hass.states[item.entity_id] : undefined; const item_username = @@ -98,46 +120,52 @@ class HaLogbook extends LitElement {
${formatTimeWithSeconds(new Date(item.when), this.hass.language)}
- -
- ${!item.entity_id - ? html` ${item.name} ` - : html` - ${item.name} - `} - ${item.message}${item_username - ? ` (${item_username})` - : ``} - ${!item.context_event_type - ? "" - : item.context_event_type === "call_service" - ? // Service Call - html` by service ${item.context_domain}.${item.context_service}` - : item.context_entity_id === item.entity_id - ? // HomeKit or something that self references - html` by - ${item.context_name - ? item.context_name - : item.context_event_type}` - : // Another entity such as an automation or script - html` by - ${item.context_entity_id_name}`} +
+ ${!this.noIcon + ? html` + + ` + : ""} +
+ ${!this.noName + ? !item.entity_id + ? html`${item.name}` + : html` + ${item.name} + ` + : ""} + ${item.message} + ${item_username ? ` (${item_username})` : ``} + ${!item.context_event_type + ? "" + : item.context_event_type === "call_service" + ? // Service Call + html` by service + ${item.context_domain}.${item.context_service}` + : item.context_entity_id === item.entity_id + ? // HomeKit or something that self references + html` by + ${item.context_name + ? item.context_name + : item.context_event_type}` + : // Another entity such as an automation or script + html` by + ${item.context_entity_id_name}`} +
@@ -163,26 +191,36 @@ class HaLogbook extends LitElement { height: 100%; } - :host([rtl]) { + .rtl { direction: ltr; } .entry { display: flex; line-height: 2em; + padding-bottom: 8px; } .time { width: 65px; flex-shrink: 0; - font-size: 0.8em; + font-size: 12px; color: var(--secondary-text-color); } - :host([rtl]) .date { + .rtl .date { direction: rtl; } + .icon-message { + display: flex; + align-items: center; + } + + .no-entries { + text-align: center; + } + ha-icon { margin: 0 8px 0 16px; flex-shrink: 0; @@ -193,6 +231,10 @@ class HaLogbook extends LitElement { color: var(--primary-text-color); } + .no-name .item-message { + text-transform: capitalize; + } + a { color: var(--primary-color); } @@ -212,8 +254,21 @@ class HaLogbook extends LitElement { .uni-virtualizer-host > * { box-sizing: border-box; } + + .narrow .entry { + flex-direction: column-reverse; + line-height: 1.5; + } + + .narrow .icon-message ha-icon { + margin-left: 0; + } `; } } -customElements.define("ha-logbook", HaLogbook); +declare global { + interface HTMLElementTagNameMap { + "ha-logbook": HaLogbook; + } +} diff --git a/src/translations/en.json b/src/translations/en.json index df63a21322..0c0a64fd09 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -389,6 +389,8 @@ "dismiss": "Dismiss dialog", "settings": "Entity settings", "edit": "Edit entity", + "controls": "Controls", + "history": "History", "script": { "last_action": "Last Action", "last_triggered": "Last Triggered"