Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/data/logbook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface LogbookEntry {
context_entity_id?: string;
context_entity_id_name?: string;
context_name?: string;
context_message?: string;
state?: string;
}

Expand Down
199 changes: 166 additions & 33 deletions src/panels/logbook/ha-logbook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,17 @@ import "../../components/ha-circular-progress";
import "../../components/ha-relative-time";
import { LogbookEntry } from "../../data/logbook";
import { TraceContexts } from "../../data/trace";
import { haStyle, haStyleScrollbar } from "../../resources/styles";
import {
haStyle,
haStyleScrollbar,
buttonLinkStyle,
} from "../../resources/styles";
import { HomeAssistant } from "../../types";

const EVENT_LOCALIZE_MAP = {
script_started: "from_script",
};

@customElement("ha-logbook")
class HaLogbook extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
Expand Down Expand Up @@ -119,6 +127,7 @@ class HaLogbook extends LitElement {
return html``;
}

const seenEntityIds: string[] = [];
const previous = this.entries[index - 1];
const stateObj = item.entity_id
? this.hass.states[item.entity_id]
Expand Down Expand Up @@ -167,42 +176,48 @@ class HaLogbook extends LitElement {
: ""}
<div class="message-relative_time">
<div class="message">
${!this.noName
? html`<a
href="#"
@click=${this._entityClicked}
.entityId=${item.entity_id}
><span class="name">${item.name}</span></a
>`
${!this.noName // Used for more-info panel (single entity case)
? this._renderEntity(item.entity_id, item.name)
: ""}
${item.message
? html`${this._formatMessageWithPossibleEntity(
item.message,
seenEntityIds,
item.entity_id
)}`
: item.source
? html` ${this._formatMessageWithPossibleEntity(
item.source,
seenEntityIds,
undefined,
"ui.components.logbook.by"
)}`
: ""}
${item.message}
${item_username
? ` ${this.hass.localize(
"ui.components.logbook.by"
"ui.components.logbook.by_user"
)} ${item_username}`
: !item.context_event_type
? ""
: item.context_event_type === "call_service"
? // Service Call
` ${this.hass.localize("ui.components.logbook.by_service")}
${item.context_domain}.${item.context_service}`
: item.context_entity_id === item.entity_id
? // HomeKit or something that self references
` ${this.hass.localize("ui.components.logbook.by")}
${
item.context_name
? item.context_name
: item.context_event_type
}`
: // Another entity such as an automation or script
html` ${this.hass.localize("ui.components.logbook.by")}
<a
href="#"
@click=${this._entityClicked}
.entityId=${item.context_entity_id}
class="name"
>${item.context_entity_id_name}</a
>`}
: ``}
${item.context_event_type
? this._formatEventBy(item, seenEntityIds)
: ""}
${item.context_message
? html` ${this._formatMessageWithPossibleEntity(
item.context_message,
seenEntityIds,
item.context_entity_id,
"ui.components.logbook.for"
)}`
: ""}
${item.context_entity_id &&
!seenEntityIds.includes(item.context_entity_id)
? // Another entity such as an automation or script
html` ${this.hass.localize("ui.components.logbook.for")}
${this._renderEntity(
item.context_entity_id,
item.context_entity_id_name
)}`
: ""}
</div>
<div class="secondary">
<span
Expand Down Expand Up @@ -254,6 +269,123 @@ class HaLogbook extends LitElement {
this._savedScrollPos = (e.target as HTMLDivElement).scrollTop;
}

private _formatEventBy(item: LogbookEntry, seenEntities: string[]) {
if (item.context_event_type === "call_service") {
return `${this.hass.localize("ui.components.logbook.from_service")} ${
item.context_domain
}.${item.context_service}`;
}
if (item.context_event_type === "automation_triggered") {
if (seenEntities.includes(item.context_entity_id!)) {
return "";
}
seenEntities.push(item.context_entity_id!);
return html`${this.hass.localize("ui.components.logbook.from_automation")}
${this._renderEntity(item.context_entity_id, item.context_name)}`;
}
if (item.context_name) {
return `${this.hass.localize("ui.components.logbook.from")} ${
item.context_name
}`;
}
if (item.context_event_type === "state_changed") {
return "";
}
if (item.context_event_type! in EVENT_LOCALIZE_MAP) {
return `${this.hass.localize(
`ui.components.logbook.${EVENT_LOCALIZE_MAP[item.context_event_type!]}`
)}`;
}
return `${this.hass.localize(
"ui.components.logbook.from"
)} ${this.hass.localize("ui.components.logbook.event")} ${
item.context_event_type
}`;
}

private _renderEntity(
entityId: string | undefined,
entityName: string | undefined
) {
const hasState = entityId && entityId in this.hass.states;
const displayName =
entityName ||
(hasState
? this.hass.states[entityId].attributes.friendly_name || entityId
: entityId);
if (!hasState) {
return displayName;
}
return html`<button
class="link"
@click=${this._entityClicked}
.entityId=${entityId}
>
${displayName}
</button>`;
}

private _formatMessageWithPossibleEntity(
message: string,
seenEntities: string[],
possibleEntity?: string,
localizePrefix?: string
) {
//
// As we are looking at a log(book), we are doing entity_id
// "highlighting"/"colorizing". The goal is to make it easy for
// the user to access the entity that caused the event.
//
// If there is an entity_id in the message that is also in the
// state machine, we search the message for the entity_id and
// replace it with _renderEntity
//
if (message.indexOf(".") !== -1) {
const messageParts = message.split(" ");
for (let i = 0, size = messageParts.length; i < size; i++) {
if (messageParts[i] in this.hass.states) {
const entityId = messageParts[i];
if (seenEntities.includes(entityId)) {
return "";
}
seenEntities.push(entityId);
const messageEnd = messageParts.splice(i);
messageEnd.shift(); // remove the entity
return html` ${messageParts.join(" ")}
${this._renderEntity(
entityId,
this.hass.states[entityId].attributes.friendly_name
)}
${messageEnd.join(" ")}`;
}
}
}
//
// When we have a message has a specific entity_id attached to
// it, and the entity_id is not in the message, we look
// for the friendly name of the entity and replace that with
// _renderEntity if its there so the user can quickly get to
// that entity.
//
if (possibleEntity && possibleEntity in this.hass.states) {
const possibleEntityName =
this.hass.states[possibleEntity].attributes.friendly_name;
if (possibleEntityName && message.endsWith(possibleEntityName)) {
if (seenEntities.includes(possibleEntity)) {
return "";
}
seenEntities.push(possibleEntity);
message = message.substring(
0,
message.length - possibleEntityName.length
);
return html` ${localizePrefix ? this.hass.localize(localizePrefix) : ""}
${message} ${this._renderEntity(possibleEntity, possibleEntityName)}`;
}
}
return message;
}

private _entityClicked(ev: Event) {
const entityId = (ev.currentTarget as any).entityId;
if (!entityId) {
Expand All @@ -275,6 +407,7 @@ class HaLogbook extends LitElement {
return [
haStyle,
haStyleScrollbar,
buttonLinkStyle,
css`
:host([virtualize]) {
display: block;
Expand Down
8 changes: 7 additions & 1 deletion src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -344,8 +344,14 @@
},
"logbook": {
"entries_not_found": "No logbook events found.",
"by_user": "by user",
"by": "by",
"by_service": "by service",
"from": "from",
"for": "for",
"event": "event",
"from_service": "from service",
"from_automation": "from automation",
"from_script": "from script",
"show_trace": "[%key:ui::panel::config::automation::editor::show_trace%]",
"retrieval_error": "Could not load logbook",
"messages": {
Expand Down