mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-19 07:16:39 +00:00
Add tracing to scripts (#9486)
This commit is contained in:
parent
de5a817953
commit
4b9487183b
@ -1,16 +1,16 @@
|
|||||||
import { dump } from "js-yaml";
|
import { dump } from "js-yaml";
|
||||||
import { html, LitElement, TemplateResult } from "lit";
|
import { html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import "../../../../components/ha-code-editor";
|
import "../ha-code-editor";
|
||||||
import "../../../../components/ha-icon-button";
|
import "../ha-icon-button";
|
||||||
import { AutomationTraceExtended } from "../../../../data/trace";
|
import { TraceExtended } from "../../data/trace";
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
@customElement("ha-automation-trace-blueprint-config")
|
@customElement("ha-trace-blueprint-config")
|
||||||
export class HaAutomationTraceBlueprintConfig extends LitElement {
|
export class HaTraceBlueprintConfig extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public trace!: AutomationTraceExtended;
|
@property({ attribute: false }) public trace!: TraceExtended;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
@ -24,6 +24,6 @@ export class HaAutomationTraceBlueprintConfig extends LitElement {
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"ha-automation-trace-blueprint-config": HaAutomationTraceBlueprintConfig;
|
"ha-trace-blueprint-config": HaTraceBlueprintConfig;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,16 +1,16 @@
|
|||||||
import { dump } from "js-yaml";
|
import { dump } from "js-yaml";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import "../../../../components/ha-code-editor";
|
import "../ha-code-editor";
|
||||||
import "../../../../components/ha-icon-button";
|
import "../ha-icon-button";
|
||||||
import { AutomationTraceExtended } from "../../../../data/trace";
|
import { TraceExtended } from "../../data/trace";
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
@customElement("ha-automation-trace-config")
|
@customElement("ha-trace-config")
|
||||||
export class HaAutomationTraceConfig extends LitElement {
|
export class HaTraceConfig extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public trace!: AutomationTraceExtended;
|
@property({ attribute: false }) public trace!: TraceExtended;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
@ -28,6 +28,6 @@ export class HaAutomationTraceConfig extends LitElement {
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"ha-automation-trace-config": HaAutomationTraceConfig;
|
"ha-trace-config": HaTraceConfig;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,16 +1,19 @@
|
|||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import "../../../../components/trace/hat-logbook-note";
|
import { LogbookEntry } from "../../data/logbook";
|
||||||
import type { LogbookEntry } from "../../../../data/logbook";
|
import { HomeAssistant } from "../../types";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import "./hat-logbook-note";
|
||||||
import "../../../logbook/ha-logbook";
|
import "../../panels/logbook/ha-logbook";
|
||||||
|
import { TraceExtended } from "../../data/trace";
|
||||||
|
|
||||||
@customElement("ha-automation-trace-logbook")
|
@customElement("ha-trace-logbook")
|
||||||
export class HaAutomationTraceLogbook extends LitElement {
|
export class HaTraceLogbook extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ type: Boolean, reflect: true }) public narrow!: boolean;
|
@property({ type: Boolean, reflect: true }) public narrow!: boolean;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public trace!: TraceExtended;
|
||||||
|
|
||||||
@property({ attribute: false }) public logbookEntries!: LogbookEntry[];
|
@property({ attribute: false }) public logbookEntries!: LogbookEntry[];
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
@ -22,7 +25,7 @@ export class HaAutomationTraceLogbook extends LitElement {
|
|||||||
.entries=${this.logbookEntries}
|
.entries=${this.logbookEntries}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
></ha-logbook>
|
></ha-logbook>
|
||||||
<hat-logbook-note></hat-logbook-note>
|
<hat-logbook-note .domain=${this.trace.domain}></hat-logbook-note>
|
||||||
`
|
`
|
||||||
: html`<div class="padded-box">
|
: html`<div class="padded-box">
|
||||||
No Logbook entries found for this step.
|
No Logbook entries found for this step.
|
||||||
@ -42,6 +45,6 @@ export class HaAutomationTraceLogbook extends LitElement {
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"ha-automation-trace-logbook": HaAutomationTraceLogbook;
|
"ha-trace-logbook": HaTraceLogbook;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,33 +2,33 @@ import { dump } from "js-yaml";
|
|||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { formatDateTimeWithSeconds } from "../../../../common/datetime/format_date_time";
|
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
|
||||||
import "../../../../components/ha-code-editor";
|
import "../ha-code-editor";
|
||||||
import "../../../../components/ha-icon-button";
|
import "../ha-icon-button";
|
||||||
import type { NodeInfo } from "../../../../components/trace/hat-graph";
|
import type { NodeInfo } from "./hat-graph";
|
||||||
import "../../../../components/trace/hat-logbook-note";
|
import "./hat-logbook-note";
|
||||||
import { LogbookEntry } from "../../../../data/logbook";
|
import { LogbookEntry } from "../../data/logbook";
|
||||||
import {
|
import {
|
||||||
ActionTraceStep,
|
ActionTraceStep,
|
||||||
AutomationTraceExtended,
|
|
||||||
ChooseActionTraceStep,
|
ChooseActionTraceStep,
|
||||||
getDataFromPath,
|
getDataFromPath,
|
||||||
} from "../../../../data/trace";
|
TraceExtended,
|
||||||
import { HomeAssistant } from "../../../../types";
|
} from "../../data/trace";
|
||||||
import "../../../logbook/ha-logbook";
|
import "../../panels/logbook/ha-logbook";
|
||||||
import { traceTabStyles } from "./styles";
|
import { traceTabStyles } from "./trace-tab-styles";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
@customElement("ha-automation-trace-path-details")
|
@customElement("ha-trace-path-details")
|
||||||
export class HaAutomationTracePathDetails extends LitElement {
|
export class HaTracePathDetails extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ type: Boolean, reflect: true }) public narrow!: boolean;
|
@property({ type: Boolean, reflect: true }) public narrow!: boolean;
|
||||||
|
|
||||||
@property() private selected!: NodeInfo;
|
@property({ attribute: false }) public trace!: TraceExtended;
|
||||||
|
|
||||||
@property() public trace!: AutomationTraceExtended;
|
@property({ attribute: false }) public logbookEntries!: LogbookEntry[];
|
||||||
|
|
||||||
@property() public logbookEntries!: LogbookEntry[];
|
@property({ attribute: false }) public selected!: NodeInfo;
|
||||||
|
|
||||||
@property() renderedNodes: Record<string, any> = {};
|
@property() renderedNodes: Record<string, any> = {};
|
||||||
|
|
||||||
@ -230,7 +230,7 @@ export class HaAutomationTracePathDetails extends LitElement {
|
|||||||
.entries=${entries}
|
.entries=${entries}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
></ha-logbook>
|
></ha-logbook>
|
||||||
<hat-logbook-note></hat-logbook-note>
|
<hat-logbook-note .domain=${this.trace.domain}></hat-logbook-note>
|
||||||
`
|
`
|
||||||
: html`<div class="padded-box">
|
: html`<div class="padded-box">
|
||||||
No Logbook entries found for this step.
|
No Logbook entries found for this step.
|
||||||
@ -267,6 +267,6 @@ export class HaAutomationTracePathDetails extends LitElement {
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"ha-automation-trace-path-details": HaAutomationTracePathDetails;
|
"ha-trace-path-details": HaTracePathDetails;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,17 +1,17 @@
|
|||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import type { NodeInfo } from "../../../../components/trace/hat-graph";
|
import type { NodeInfo } from "./hat-graph";
|
||||||
import "../../../../components/trace/hat-logbook-note";
|
import "./hat-logbook-note";
|
||||||
import "../../../../components/trace/hat-trace-timeline";
|
import "./hat-trace-timeline";
|
||||||
import type { LogbookEntry } from "../../../../data/logbook";
|
import type { LogbookEntry } from "../../data/logbook";
|
||||||
import type { AutomationTraceExtended } from "../../../../data/trace";
|
import type { TraceExtended } from "../../data/trace";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
@customElement("ha-automation-trace-timeline")
|
@customElement("ha-trace-timeline")
|
||||||
export class HaAutomationTraceTimeline extends LitElement {
|
export class HaTraceTimeline extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ attribute: false }) public trace!: AutomationTraceExtended;
|
@property({ attribute: false }) public trace!: TraceExtended;
|
||||||
|
|
||||||
@property({ attribute: false }) public logbookEntries!: LogbookEntry[];
|
@property({ attribute: false }) public logbookEntries!: LogbookEntry[];
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ export class HaAutomationTraceTimeline extends LitElement {
|
|||||||
allowPick
|
allowPick
|
||||||
>
|
>
|
||||||
</hat-trace-timeline>
|
</hat-trace-timeline>
|
||||||
<hat-logbook-note></hat-logbook-note>
|
<hat-logbook-note .domain=${this.trace.domain}></hat-logbook-note>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,6 +45,6 @@ export class HaAutomationTraceTimeline extends LitElement {
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"ha-automation-trace-timeline": HaAutomationTraceTimeline;
|
"ha-trace-timeline": HaTraceTimeline;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,7 +8,7 @@ export class HatGraphNode extends LitElement {
|
|||||||
|
|
||||||
@property({ reflect: true, type: Boolean }) disabled?: boolean;
|
@property({ reflect: true, type: Boolean }) disabled?: boolean;
|
||||||
|
|
||||||
@property({ reflect: true, type: Boolean }) graphstart?: boolean;
|
@property({ reflect: true, type: Boolean }) graphStart?: boolean;
|
||||||
|
|
||||||
@property({ reflect: true, type: Boolean }) nofocus?: boolean;
|
@property({ reflect: true, type: Boolean }) nofocus?: boolean;
|
||||||
|
|
||||||
@ -21,20 +21,20 @@ export class HatGraphNode extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const height = NODE_SIZE + (this.graphstart ? 2 : SPACING + 1);
|
const height = NODE_SIZE + (this.graphStart ? 2 : SPACING + 1);
|
||||||
const width = SPACING + NODE_SIZE;
|
const width = SPACING + NODE_SIZE;
|
||||||
return svg`
|
return svg`
|
||||||
<svg
|
<svg
|
||||||
width="${width}px"
|
width="${width}px"
|
||||||
height="${height}px"
|
height="${height}px"
|
||||||
viewBox="-${Math.ceil(width / 2)} -${
|
viewBox="-${Math.ceil(width / 2)} -${
|
||||||
this.graphstart
|
this.graphStart
|
||||||
? Math.ceil(height / 2)
|
? Math.ceil(height / 2)
|
||||||
: Math.ceil((NODE_SIZE + SPACING * 2) / 2)
|
: Math.ceil((NODE_SIZE + SPACING * 2) / 2)
|
||||||
} ${width} ${height}"
|
} ${width} ${height}"
|
||||||
>
|
>
|
||||||
${
|
${
|
||||||
this.graphstart
|
this.graphStart
|
||||||
? ``
|
? ``
|
||||||
: svg`
|
: svg`
|
||||||
<path
|
<path
|
||||||
|
@ -30,16 +30,16 @@ export class HatGraph extends LitElement {
|
|||||||
|
|
||||||
@property({ reflect: true, type: Boolean }) branching?: boolean;
|
@property({ reflect: true, type: Boolean }) branching?: boolean;
|
||||||
|
|
||||||
@property({ reflect: true, converter: track_converter })
|
@property({ converter: track_converter })
|
||||||
track_start?: number[];
|
track_start?: number[];
|
||||||
|
|
||||||
@property({ reflect: true, converter: track_converter }) track_end?: number[];
|
@property({ converter: track_converter }) track_end?: number[];
|
||||||
|
|
||||||
@property({ reflect: true, type: Boolean }) disabled?: boolean;
|
@property({ reflect: true, type: Boolean }) disabled?: boolean;
|
||||||
|
|
||||||
@property({ reflect: true, type: Boolean }) selected?: boolean;
|
@property({ type: Boolean }) selected?: boolean;
|
||||||
|
|
||||||
@property({ reflect: true, type: Boolean }) short = false;
|
@property({ type: Boolean }) short = false;
|
||||||
|
|
||||||
async updateChildren() {
|
async updateChildren() {
|
||||||
this._num_items = this.children.length;
|
this._num_items = this.children.length;
|
||||||
@ -68,7 +68,7 @@ export class HatGraph extends LitElement {
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
<slot name="head" @slotchange=${this.updateChildren}> </slot>
|
<slot name="head" @slotchange=${this.updateChildren}> </slot>
|
||||||
${this.branching
|
${this.branching && branches.some((branch) => !branch.start)
|
||||||
? svg`
|
? svg`
|
||||||
<svg
|
<svg
|
||||||
id="top"
|
id="top"
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import { css, html, LitElement } from "lit";
|
import { css, html, LitElement } from "lit";
|
||||||
import { customElement } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
|
|
||||||
@customElement("hat-logbook-note")
|
@customElement("hat-logbook-note")
|
||||||
class HatLogbookNote extends LitElement {
|
class HatLogbookNote extends LitElement {
|
||||||
|
@property() public domain = "automation";
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`
|
return html`
|
||||||
Not all shown logbook entries might be related to this automation.
|
Not all shown logbook entries might be related to this ${this.domain}.
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,9 +36,9 @@ import {
|
|||||||
WaitForTriggerAction,
|
WaitForTriggerAction,
|
||||||
} from "../../data/script";
|
} from "../../data/script";
|
||||||
import {
|
import {
|
||||||
AutomationTraceExtended,
|
|
||||||
ChooseActionTraceStep,
|
ChooseActionTraceStep,
|
||||||
ConditionTraceStep,
|
ConditionTraceStep,
|
||||||
|
TraceExtended,
|
||||||
} from "../../data/trace";
|
} from "../../data/trace";
|
||||||
import "../ha-svg-icon";
|
import "../ha-svg-icon";
|
||||||
import { NodeInfo, NODE_SIZE, SPACING } from "./hat-graph";
|
import { NodeInfo, NODE_SIZE, SPACING } from "./hat-graph";
|
||||||
@ -53,7 +53,7 @@ declare global {
|
|||||||
|
|
||||||
@customElement("hat-script-graph")
|
@customElement("hat-script-graph")
|
||||||
class HatScriptGraph extends LitElement {
|
class HatScriptGraph extends LitElement {
|
||||||
@property({ attribute: false }) public trace!: AutomationTraceExtended;
|
@property({ attribute: false }) public trace!: TraceExtended;
|
||||||
|
|
||||||
@property({ attribute: false }) public selected;
|
@property({ attribute: false }) public selected;
|
||||||
|
|
||||||
@ -137,7 +137,11 @@ class HatScriptGraph extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private render_choose_node(config: ChooseAction, path: string) {
|
private render_choose_node(
|
||||||
|
config: ChooseAction,
|
||||||
|
path: string,
|
||||||
|
graphStart = false
|
||||||
|
) {
|
||||||
const trace = this.trace.trace[path] as ChooseActionTraceStep[] | undefined;
|
const trace = this.trace.trace[path] as ChooseActionTraceStep[] | undefined;
|
||||||
const trace_path = trace?.[0].result
|
const trace_path = trace?.[0].result
|
||||||
? trace[0].result.choice === "default"
|
? trace[0].result.choice === "default"
|
||||||
@ -157,6 +161,7 @@ class HatScriptGraph extends LitElement {
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<hat-graph-node
|
<hat-graph-node
|
||||||
|
.graphStart=${graphStart}
|
||||||
.iconPath=${mdiCallSplit}
|
.iconPath=${mdiCallSplit}
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
track: trace !== undefined,
|
track: trace !== undefined,
|
||||||
@ -210,7 +215,11 @@ class HatScriptGraph extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private render_condition_node(node: Condition, path: string) {
|
private render_condition_node(
|
||||||
|
node: Condition,
|
||||||
|
path: string,
|
||||||
|
graphStart = false
|
||||||
|
) {
|
||||||
const trace = (this.trace.trace[path] as ConditionTraceStep[]) || undefined;
|
const trace = (this.trace.trace[path] as ConditionTraceStep[]) || undefined;
|
||||||
const track_path =
|
const track_path =
|
||||||
trace?.[0].result === undefined ? 0 : trace[0].result.result ? 1 : 2;
|
trace?.[0].result === undefined ? 0 : trace[0].result.result ? 1 : 2;
|
||||||
@ -228,23 +237,18 @@ class HatScriptGraph extends LitElement {
|
|||||||
short
|
short
|
||||||
>
|
>
|
||||||
<hat-graph-node
|
<hat-graph-node
|
||||||
|
.graphStart=${graphStart}
|
||||||
slot="head"
|
slot="head"
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
track: Boolean(trace),
|
track: Boolean(trace),
|
||||||
})}
|
})}
|
||||||
.iconPath=${mdiAbTesting}
|
.iconPath=${mdiAbTesting}
|
||||||
nofocus
|
nofocus
|
||||||
graphEnd
|
|
||||||
></hat-graph-node>
|
></hat-graph-node>
|
||||||
<div
|
<div style=${`width: ${NODE_SIZE + SPACING}px;`}></div>
|
||||||
style=${`width: ${NODE_SIZE + SPACING}px;`}
|
|
||||||
graphStart
|
|
||||||
graphEnd
|
|
||||||
></div>
|
|
||||||
<div></div>
|
<div></div>
|
||||||
<hat-graph-node
|
<hat-graph-node
|
||||||
.iconPath=${mdiClose}
|
.iconPath=${mdiClose}
|
||||||
graphEnd
|
|
||||||
nofocus
|
nofocus
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
track: track_path === 2,
|
track: track_path === 2,
|
||||||
@ -254,9 +258,14 @@ class HatScriptGraph extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private render_delay_node(node: DelayAction, path: string) {
|
private render_delay_node(
|
||||||
|
node: DelayAction,
|
||||||
|
path: string,
|
||||||
|
graphStart = false
|
||||||
|
) {
|
||||||
return html`
|
return html`
|
||||||
<hat-graph-node
|
<hat-graph-node
|
||||||
|
.graphStart=${graphStart}
|
||||||
.iconPath=${mdiTimerOutline}
|
.iconPath=${mdiTimerOutline}
|
||||||
@focus=${this.selectNode(node, path)}
|
@focus=${this.selectNode(node, path)}
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
@ -268,9 +277,14 @@ class HatScriptGraph extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private render_device_node(node: DeviceAction, path: string) {
|
private render_device_node(
|
||||||
|
node: DeviceAction,
|
||||||
|
path: string,
|
||||||
|
graphStart = false
|
||||||
|
) {
|
||||||
return html`
|
return html`
|
||||||
<hat-graph-node
|
<hat-graph-node
|
||||||
|
.graphStart=${graphStart}
|
||||||
.iconPath=${mdiDevices}
|
.iconPath=${mdiDevices}
|
||||||
@focus=${this.selectNode(node, path)}
|
@focus=${this.selectNode(node, path)}
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
@ -282,9 +296,14 @@ class HatScriptGraph extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private render_event_node(node: EventAction, path: string) {
|
private render_event_node(
|
||||||
|
node: EventAction,
|
||||||
|
path: string,
|
||||||
|
graphStart = false
|
||||||
|
) {
|
||||||
return html`
|
return html`
|
||||||
<hat-graph-node
|
<hat-graph-node
|
||||||
|
.graphStart=${graphStart}
|
||||||
.iconPath=${mdiExclamation}
|
.iconPath=${mdiExclamation}
|
||||||
@focus=${this.selectNode(node, path)}
|
@focus=${this.selectNode(node, path)}
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
@ -296,7 +315,11 @@ class HatScriptGraph extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private render_repeat_node(node: RepeatAction, path: string) {
|
private render_repeat_node(
|
||||||
|
node: RepeatAction,
|
||||||
|
path: string,
|
||||||
|
graphStart = false
|
||||||
|
) {
|
||||||
const trace: any = this.trace.trace[path];
|
const trace: any = this.trace.trace[path];
|
||||||
const track_path = trace ? [0, 1] : [];
|
const track_path = trace ? [0, 1] : [];
|
||||||
const repeats = this.trace?.trace[`${path}/repeat/sequence/0`]?.length;
|
const repeats = this.trace?.trace[`${path}/repeat/sequence/0`]?.length;
|
||||||
@ -313,6 +336,7 @@ class HatScriptGraph extends LitElement {
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<hat-graph-node
|
<hat-graph-node
|
||||||
|
.graphStart=${graphStart}
|
||||||
.iconPath=${mdiRefresh}
|
.iconPath=${mdiRefresh}
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
track: trace,
|
track: trace,
|
||||||
@ -337,9 +361,14 @@ class HatScriptGraph extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private render_scene_node(node: SceneAction, path: string) {
|
private render_scene_node(
|
||||||
|
node: SceneAction,
|
||||||
|
path: string,
|
||||||
|
graphStart = false
|
||||||
|
) {
|
||||||
return html`
|
return html`
|
||||||
<hat-graph-node
|
<hat-graph-node
|
||||||
|
.graphStart=${graphStart}
|
||||||
.iconPath=${mdiExclamation}
|
.iconPath=${mdiExclamation}
|
||||||
@focus=${this.selectNode(node, path)}
|
@focus=${this.selectNode(node, path)}
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
@ -351,9 +380,14 @@ class HatScriptGraph extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private render_service_node(node: ServiceAction, path: string) {
|
private render_service_node(
|
||||||
|
node: ServiceAction,
|
||||||
|
path: string,
|
||||||
|
graphStart = false
|
||||||
|
) {
|
||||||
return html`
|
return html`
|
||||||
<hat-graph-node
|
<hat-graph-node
|
||||||
|
.graphStart=${graphStart}
|
||||||
.iconPath=${mdiChevronRight}
|
.iconPath=${mdiChevronRight}
|
||||||
@focus=${this.selectNode(node, path)}
|
@focus=${this.selectNode(node, path)}
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
@ -367,10 +401,12 @@ class HatScriptGraph extends LitElement {
|
|||||||
|
|
||||||
private render_wait_node(
|
private render_wait_node(
|
||||||
node: WaitAction | WaitForTriggerAction,
|
node: WaitAction | WaitForTriggerAction,
|
||||||
path: string
|
path: string,
|
||||||
|
graphStart = false
|
||||||
) {
|
) {
|
||||||
return html`
|
return html`
|
||||||
<hat-graph-node
|
<hat-graph-node
|
||||||
|
.graphStart=${graphStart}
|
||||||
.iconPath=${mdiTrafficLight}
|
.iconPath=${mdiTrafficLight}
|
||||||
@focus=${this.selectNode(node, path)}
|
@focus=${this.selectNode(node, path)}
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
@ -382,9 +418,10 @@ class HatScriptGraph extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private render_other_node(node: Action, path: string) {
|
private render_other_node(node: Action, path: string, graphStart = false) {
|
||||||
return html`
|
return html`
|
||||||
<hat-graph-node
|
<hat-graph-node
|
||||||
|
.graphStart=${graphStart}
|
||||||
.iconPath=${mdiCodeBrackets}
|
.iconPath=${mdiCodeBrackets}
|
||||||
@focus=${this.selectNode(node, path)}
|
@focus=${this.selectNode(node, path)}
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
@ -395,7 +432,7 @@ class HatScriptGraph extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private render_node(node: Action, path: string) {
|
private render_node(node: Action, path: string, graphStart = false) {
|
||||||
const NODE_TYPES = {
|
const NODE_TYPES = {
|
||||||
choose: this.render_choose_node,
|
choose: this.render_choose_node,
|
||||||
condition: this.render_condition_node,
|
condition: this.render_condition_node,
|
||||||
@ -411,7 +448,7 @@ class HatScriptGraph extends LitElement {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const type = Object.keys(NODE_TYPES).find((key) => key in node) || "other";
|
const type = Object.keys(NODE_TYPES).find((key) => key in node) || "other";
|
||||||
const nodeEl = NODE_TYPES[type].bind(this)(node, path);
|
const nodeEl = NODE_TYPES[type].bind(this)(node, path, graphStart);
|
||||||
this.renderedNodes[path] = { config: node, path };
|
this.renderedNodes[path] = { config: node, path };
|
||||||
if (this.trace && path in this.trace.trace) {
|
if (this.trace && path in this.trace.trace) {
|
||||||
this.trackedNodes[path] = this.renderedNodes[path];
|
this.trackedNodes[path] = this.renderedNodes[path];
|
||||||
@ -423,35 +460,47 @@ class HatScriptGraph extends LitElement {
|
|||||||
const paths = Object.keys(this.trackedNodes);
|
const paths = Object.keys(this.trackedNodes);
|
||||||
const manual_triggered = this.trace && "trigger" in this.trace.trace;
|
const manual_triggered = this.trace && "trigger" in this.trace.trace;
|
||||||
let track_path = manual_triggered ? undefined : [0];
|
let track_path = manual_triggered ? undefined : [0];
|
||||||
const trigger_nodes = ensureArray(this.trace.config.trigger).map(
|
const trigger_nodes =
|
||||||
(trigger, i) => {
|
"trigger" in this.trace.config
|
||||||
if (this.trace && `trigger/${i}` in this.trace.trace) {
|
? ensureArray(this.trace.config.trigger).map((trigger, i) => {
|
||||||
track_path = [i];
|
if (this.trace && `trigger/${i}` in this.trace.trace) {
|
||||||
}
|
track_path = [i];
|
||||||
return this.render_trigger(trigger, i);
|
}
|
||||||
}
|
return this.render_trigger(trigger, i);
|
||||||
);
|
})
|
||||||
|
: undefined;
|
||||||
try {
|
try {
|
||||||
return html`
|
return html`
|
||||||
<hat-graph class="parent">
|
<hat-graph class="parent">
|
||||||
<div></div>
|
<div></div>
|
||||||
<hat-graph
|
${trigger_nodes
|
||||||
branching
|
? html`<hat-graph
|
||||||
id="trigger"
|
branching
|
||||||
.short=${trigger_nodes.length < 2}
|
id="trigger"
|
||||||
.track_start=${track_path}
|
.short=${trigger_nodes.length < 2}
|
||||||
.track_end=${track_path}
|
.track_start=${track_path}
|
||||||
>
|
.track_end=${track_path}
|
||||||
${trigger_nodes}
|
>
|
||||||
</hat-graph>
|
${trigger_nodes}
|
||||||
<hat-graph id="condition">
|
</hat-graph>`
|
||||||
${ensureArray(this.trace.config.condition)?.map((condition, i) =>
|
: ""}
|
||||||
this.render_condition(condition!, i)
|
${"condition" in this.trace.config
|
||||||
)}
|
? html`<hat-graph id="condition">
|
||||||
</hat-graph>
|
${ensureArray(
|
||||||
${ensureArray(this.trace.config.action).map((action, i) =>
|
this.trace.config.condition
|
||||||
this.render_node(action, `action/${i}`)
|
)?.map((condition, i) => this.render_condition(condition!, i))}
|
||||||
)}
|
</hat-graph>`
|
||||||
|
: ""}
|
||||||
|
${"action" in this.trace.config
|
||||||
|
? html`${ensureArray(this.trace.config.action).map((action, i) =>
|
||||||
|
this.render_node(action, `action/${i}`)
|
||||||
|
)}`
|
||||||
|
: ""}
|
||||||
|
${"sequence" in this.trace.config
|
||||||
|
? html`${ensureArray(this.trace.config.sequence).map((action, i) =>
|
||||||
|
this.render_node(action, `sequence/${i}`, i === 0)
|
||||||
|
)}`
|
||||||
|
: ""}
|
||||||
</hat-graph>
|
</hat-graph>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<mwc-icon-button
|
<mwc-icon-button
|
||||||
@ -564,6 +613,7 @@ class HatScriptGraph extends LitElement {
|
|||||||
}
|
}
|
||||||
.parent {
|
.parent {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
.error {
|
.error {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
@ -7,6 +7,7 @@ import { computeObjectId } from "../common/entity/compute_object_id";
|
|||||||
import { navigate } from "../common/navigate";
|
import { navigate } from "../common/navigate";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import { Condition, Trigger } from "./automation";
|
import { Condition, Trigger } from "./automation";
|
||||||
|
import { BlueprintInput } from "./blueprint";
|
||||||
|
|
||||||
export const MODES = ["single", "restart", "queued", "parallel"] as const;
|
export const MODES = ["single", "restart", "queued", "parallel"] as const;
|
||||||
export const MODES_MAX = ["queued", "parallel"];
|
export const MODES_MAX = ["queued", "parallel"];
|
||||||
@ -28,6 +29,10 @@ export interface ScriptConfig {
|
|||||||
max?: number;
|
max?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BlueprintScriptConfig extends ScriptConfig {
|
||||||
|
use_blueprint: { path: string; input?: BlueprintInput };
|
||||||
|
}
|
||||||
|
|
||||||
export interface EventAction {
|
export interface EventAction {
|
||||||
alias?: string;
|
alias?: string;
|
||||||
event: string;
|
event: string;
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
BlueprintAutomationConfig,
|
BlueprintAutomationConfig,
|
||||||
ManualAutomationConfig,
|
ManualAutomationConfig,
|
||||||
} from "./automation";
|
} from "./automation";
|
||||||
|
import { BlueprintScriptConfig, ScriptConfig } from "./script";
|
||||||
|
|
||||||
interface BaseTraceStep {
|
interface BaseTraceStep {
|
||||||
path: string;
|
path: string;
|
||||||
@ -54,7 +55,7 @@ export type ActionTraceStep =
|
|||||||
| ChooseActionTraceStep
|
| ChooseActionTraceStep
|
||||||
| ChooseChoiceActionTraceStep;
|
| ChooseChoiceActionTraceStep;
|
||||||
|
|
||||||
export interface AutomationTrace {
|
interface BaseTrace {
|
||||||
domain: string;
|
domain: string;
|
||||||
item_id: string;
|
item_id: string;
|
||||||
last_step: string | null;
|
last_step: string | null;
|
||||||
@ -81,23 +82,46 @@ export interface AutomationTrace {
|
|||||||
// The exception is in the trace itself or in the last element of the trace
|
// The exception is in the trace itself or in the last element of the trace
|
||||||
// Script execution stopped by async_stop called on the script run because home assistant is shutting down, script mode is SCRIPT_MODE_RESTART etc:
|
// Script execution stopped by async_stop called on the script run because home assistant is shutting down, script mode is SCRIPT_MODE_RESTART etc:
|
||||||
| "cancelled";
|
| "cancelled";
|
||||||
// Automation only, should become it's own type when we support script in frontend
|
}
|
||||||
|
|
||||||
|
interface BaseTraceExtended {
|
||||||
|
trace: Record<string, ActionTraceStep[]>;
|
||||||
|
context: Context;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AutomationTrace extends BaseTrace {
|
||||||
|
domain: "automation";
|
||||||
trigger: string;
|
trigger: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AutomationTraceExtended extends AutomationTrace {
|
export interface AutomationTraceExtended
|
||||||
trace: Record<string, ActionTraceStep[]>;
|
extends AutomationTrace,
|
||||||
context: Context;
|
BaseTraceExtended {
|
||||||
config: ManualAutomationConfig;
|
config: ManualAutomationConfig;
|
||||||
blueprint_inputs?: BlueprintAutomationConfig;
|
blueprint_inputs?: BlueprintAutomationConfig;
|
||||||
error?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ScriptTrace extends BaseTrace {
|
||||||
|
domain: "script";
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScriptTraceExtended extends ScriptTrace, BaseTraceExtended {
|
||||||
|
config: ScriptConfig;
|
||||||
|
blueprint_inputs?: BlueprintScriptConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TraceExtended = AutomationTraceExtended | ScriptTraceExtended;
|
||||||
|
|
||||||
interface TraceTypes {
|
interface TraceTypes {
|
||||||
automation: {
|
automation: {
|
||||||
short: AutomationTrace;
|
short: AutomationTrace;
|
||||||
extended: AutomationTraceExtended;
|
extended: AutomationTraceExtended;
|
||||||
};
|
};
|
||||||
|
script: {
|
||||||
|
short: ScriptTrace;
|
||||||
|
extended: ScriptTraceExtended;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const loadTrace = <T extends keyof TraceTypes>(
|
export const loadTrace = <T extends keyof TraceTypes>(
|
||||||
@ -141,7 +165,7 @@ export const loadTraceContexts = (
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const getDataFromPath = (
|
export const getDataFromPath = (
|
||||||
config: ManualAutomationConfig,
|
config: TraceExtended["config"],
|
||||||
path: string
|
path: string
|
||||||
): any => {
|
): any => {
|
||||||
const parts = path.split("/").reverse();
|
const parts = path.split("/").reverse();
|
||||||
|
@ -9,31 +9,28 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
|||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { repeat } from "lit/directives/repeat";
|
import { repeat } from "lit/directives/repeat";
|
||||||
import { isComponentLoaded } from "../../../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||||
import { formatDateTimeWithSeconds } from "../../../../common/datetime/format_date_time";
|
import { formatDateTimeWithSeconds } from "../../../common/datetime/format_date_time";
|
||||||
import type { NodeInfo } from "../../../../components/trace/hat-graph";
|
import type { NodeInfo } from "../../../components/trace/hat-graph";
|
||||||
import "../../../../components/trace/hat-script-graph";
|
import "../../../components/trace/hat-script-graph";
|
||||||
import { AutomationEntity } from "../../../../data/automation";
|
import { AutomationEntity } from "../../../data/automation";
|
||||||
import {
|
import { getLogbookDataForContext, LogbookEntry } from "../../../data/logbook";
|
||||||
getLogbookDataForContext,
|
|
||||||
LogbookEntry,
|
|
||||||
} from "../../../../data/logbook";
|
|
||||||
import {
|
import {
|
||||||
AutomationTrace,
|
AutomationTrace,
|
||||||
AutomationTraceExtended,
|
AutomationTraceExtended,
|
||||||
loadTrace,
|
loadTrace,
|
||||||
loadTraces,
|
loadTraces,
|
||||||
} from "../../../../data/trace";
|
} from "../../../data/trace";
|
||||||
import { showAlertDialog } from "../../../../dialogs/generic/show-dialog-box";
|
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||||
import { haStyle } from "../../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import { HomeAssistant, Route } from "../../../../types";
|
import { HomeAssistant, Route } from "../../../types";
|
||||||
import { configSections } from "../../ha-panel-config";
|
import { configSections } from "../ha-panel-config";
|
||||||
import "./ha-automation-trace-blueprint-config";
|
import "../../../components/trace/ha-trace-blueprint-config";
|
||||||
import "./ha-automation-trace-config";
|
import "../../../components/trace/ha-trace-config";
|
||||||
import "./ha-automation-trace-logbook";
|
import "../../../components/trace/ha-trace-logbook";
|
||||||
import "./ha-automation-trace-path-details";
|
import "../../../components/trace/ha-trace-path-details";
|
||||||
import "./ha-automation-trace-timeline";
|
import "../../../components/trace/ha-trace-timeline";
|
||||||
import { traceTabStyles } from "./styles";
|
import { traceTabStyles } from "../../../components/trace/trace-tab-styles";
|
||||||
|
|
||||||
@customElement("ha-automation-trace")
|
@customElement("ha-automation-trace")
|
||||||
export class HaAutomationTrace extends LitElement {
|
export class HaAutomationTrace extends LitElement {
|
||||||
@ -209,7 +206,7 @@ export class HaAutomationTrace extends LitElement {
|
|||||||
@click=${this._showTab}
|
@click=${this._showTab}
|
||||||
>
|
>
|
||||||
Blueprint Config
|
Blueprint Config
|
||||||
</div>
|
</button>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
@ -219,46 +216,47 @@ export class HaAutomationTrace extends LitElement {
|
|||||||
? ""
|
? ""
|
||||||
: this._view === "details"
|
: this._view === "details"
|
||||||
? html`
|
? html`
|
||||||
<ha-automation-trace-path-details
|
<ha-trace-path-details
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
.trace=${this._trace}
|
.trace=${this._trace}
|
||||||
.selected=${this._selected}
|
.selected=${this._selected}
|
||||||
.logbookEntries=${this._logbookEntries}
|
.logbookEntries=${this._logbookEntries}
|
||||||
.trackedNodes=${trackedNodes}
|
.trackedNodes=${trackedNodes}
|
||||||
.renderedNodes=${renderedNodes}
|
.renderedNodes=${renderedNodes!}
|
||||||
></ha-automation-trace-path-details>
|
></ha-trace-path-details>
|
||||||
`
|
`
|
||||||
: this._view === "config"
|
: this._view === "config"
|
||||||
? html`
|
? html`
|
||||||
<ha-automation-trace-config
|
<ha-trace-config
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.trace=${this._trace}
|
.trace=${this._trace}
|
||||||
></ha-automation-trace-config>
|
></ha-trace-config>
|
||||||
`
|
`
|
||||||
: this._view === "logbook"
|
: this._view === "logbook"
|
||||||
? html`
|
? html`
|
||||||
<ha-automation-trace-logbook
|
<ha-trace-logbook
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
|
.trace=${this._trace}
|
||||||
.logbookEntries=${this._logbookEntries}
|
.logbookEntries=${this._logbookEntries}
|
||||||
></ha-automation-trace-logbook>
|
></ha-trace-logbook>
|
||||||
`
|
`
|
||||||
: this._view === "blueprint"
|
: this._view === "blueprint"
|
||||||
? html`
|
? html`
|
||||||
<ha-automation-trace-blueprint-config
|
<ha-trace-blueprint-config
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.trace=${this._trace}
|
.trace=${this._trace}
|
||||||
></ha-automation-trace-blueprint-config>
|
></ha-trace-blueprint-config>
|
||||||
`
|
`
|
||||||
: html`
|
: html`
|
||||||
<ha-automation-trace-timeline
|
<ha-trace-timeline
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.trace=${this._trace}
|
.trace=${this._trace}
|
||||||
.logbookEntries=${this._logbookEntries}
|
.logbookEntries=${this._logbookEntries}
|
||||||
.selected=${this._selected}
|
.selected=${this._selected}
|
||||||
@value-changed=${this._timelinePathPicked}
|
@value-changed=${this._timelinePathPicked}
|
||||||
></ha-automation-trace-timeline>
|
></ha-trace-timeline>
|
||||||
`}
|
`}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@ -51,7 +51,7 @@ class HaConfigAutomation extends HassRouterPage {
|
|||||||
},
|
},
|
||||||
trace: {
|
trace: {
|
||||||
tag: "ha-automation-trace",
|
tag: "ha-automation-trace",
|
||||||
load: () => import("./trace/ha-automation-trace"),
|
load: () => import("./ha-automation-trace"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -42,6 +42,10 @@ class HaConfigScript extends HassRouterPage {
|
|||||||
edit: {
|
edit: {
|
||||||
tag: "ha-script-editor",
|
tag: "ha-script-editor",
|
||||||
},
|
},
|
||||||
|
trace: {
|
||||||
|
tag: "ha-script-trace",
|
||||||
|
load: () => import("./ha-script-trace"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -81,7 +85,7 @@ class HaConfigScript extends HassRouterPage {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
(!changedProps || changedProps.has("route")) &&
|
(!changedProps || changedProps.has("route")) &&
|
||||||
this._currentPage === "edit"
|
this._currentPage !== "dashboard"
|
||||||
) {
|
) {
|
||||||
pageEl.creatingNew = undefined;
|
pageEl.creatingNew = undefined;
|
||||||
const scriptEntityId = this.routeTail.path.substr(1);
|
const scriptEntityId = this.routeTail.path.substr(1);
|
||||||
|
@ -297,7 +297,16 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
<div
|
<div
|
||||||
class="card-actions layout horizontal justified center"
|
class="card-actions layout horizontal justified center"
|
||||||
>
|
>
|
||||||
<span></span>
|
<a
|
||||||
|
href="/config/script/trace/${this
|
||||||
|
.scriptEntityId}"
|
||||||
|
>
|
||||||
|
<mwc-button>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.script.editor.show_trace"
|
||||||
|
)}
|
||||||
|
</mwc-button>
|
||||||
|
</a>
|
||||||
<mwc-button
|
<mwc-button
|
||||||
@click=${this._runScript}
|
@click=${this._runScript}
|
||||||
title="${this.hass.localize(
|
title="${this.hass.localize(
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import "@material/mwc-icon-button";
|
import "@material/mwc-icon-button";
|
||||||
import {
|
import {
|
||||||
mdiHelpCircle,
|
mdiHelpCircle,
|
||||||
|
mdiHistory,
|
||||||
mdiInformationOutline,
|
mdiInformationOutline,
|
||||||
mdiPencil,
|
mdiPencil,
|
||||||
mdiPlay,
|
mdiPlay,
|
||||||
@ -140,6 +141,21 @@ class HaScriptPicker extends LitElement {
|
|||||||
</mwc-icon-button>
|
</mwc-icon-button>
|
||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
|
columns.trace = {
|
||||||
|
title: "",
|
||||||
|
type: "icon-button",
|
||||||
|
template: (_info, script: any) => html`
|
||||||
|
<a href="/config/script/trace/${script.entity_id}">
|
||||||
|
<mwc-icon-button
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.script.picker.dev_script"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ha-svg-icon .path=${mdiHistory}></ha-svg-icon>
|
||||||
|
</mwc-icon-button>
|
||||||
|
</a>
|
||||||
|
`,
|
||||||
|
};
|
||||||
columns.edit = {
|
columns.edit = {
|
||||||
title: "",
|
title: "",
|
||||||
type: "icon-button",
|
type: "icon-button",
|
||||||
|
502
src/panels/config/script/ha-script-trace.ts
Normal file
502
src/panels/config/script/ha-script-trace.ts
Normal file
@ -0,0 +1,502 @@
|
|||||||
|
import {
|
||||||
|
mdiDownload,
|
||||||
|
mdiPencil,
|
||||||
|
mdiRayEndArrow,
|
||||||
|
mdiRayStartArrow,
|
||||||
|
mdiRefresh,
|
||||||
|
} from "@mdi/js";
|
||||||
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { classMap } from "lit/directives/class-map";
|
||||||
|
import { repeat } from "lit/directives/repeat";
|
||||||
|
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||||
|
import { formatDateTimeWithSeconds } from "../../../common/datetime/format_date_time";
|
||||||
|
import type { NodeInfo } from "../../../components/trace/hat-graph";
|
||||||
|
import "../../../components/trace/hat-script-graph";
|
||||||
|
import { getLogbookDataForContext, LogbookEntry } from "../../../data/logbook";
|
||||||
|
import { ScriptEntity } from "../../../data/script";
|
||||||
|
import {
|
||||||
|
loadTrace,
|
||||||
|
loadTraces,
|
||||||
|
ScriptTrace,
|
||||||
|
ScriptTraceExtended,
|
||||||
|
} from "../../../data/trace";
|
||||||
|
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||||
|
import { haStyle } from "../../../resources/styles";
|
||||||
|
import { HomeAssistant, Route } from "../../../types";
|
||||||
|
import { traceTabStyles } from "../../../components/trace/trace-tab-styles";
|
||||||
|
import { configSections } from "../ha-panel-config";
|
||||||
|
import "../../../components/trace/ha-trace-blueprint-config";
|
||||||
|
import "../../../components/trace/ha-trace-config";
|
||||||
|
import "../../../components/trace/ha-trace-logbook";
|
||||||
|
import "../../../components/trace/ha-trace-path-details";
|
||||||
|
import "../../../components/trace/ha-trace-timeline";
|
||||||
|
|
||||||
|
@customElement("ha-script-trace")
|
||||||
|
export class HaScriptTrace extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() public scriptEntityId!: string;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public scripts!: ScriptEntity[];
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public isWide?: boolean;
|
||||||
|
|
||||||
|
@property({ type: Boolean, reflect: true }) public narrow!: boolean;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public route!: Route;
|
||||||
|
|
||||||
|
@state() private _traces?: ScriptTrace[];
|
||||||
|
|
||||||
|
@state() private _runId?: string;
|
||||||
|
|
||||||
|
@state() private _selected?: NodeInfo;
|
||||||
|
|
||||||
|
@state() private _trace?: ScriptTraceExtended;
|
||||||
|
|
||||||
|
@state() private _logbookEntries?: LogbookEntry[];
|
||||||
|
|
||||||
|
@state() private _view:
|
||||||
|
| "details"
|
||||||
|
| "config"
|
||||||
|
| "timeline"
|
||||||
|
| "logbook"
|
||||||
|
| "blueprint" = "details";
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
const stateObj = this.scriptEntityId
|
||||||
|
? this.hass.states[this.scriptEntityId]
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const graph = this.shadowRoot!.querySelector("hat-script-graph");
|
||||||
|
const trackedNodes = graph?.trackedNodes;
|
||||||
|
const renderedNodes = graph?.renderedNodes;
|
||||||
|
|
||||||
|
const title = stateObj?.attributes.friendly_name || this.scriptEntityId;
|
||||||
|
|
||||||
|
let devButtons: TemplateResult | string = "";
|
||||||
|
if (__DEV__) {
|
||||||
|
devButtons = html`<div style="position: absolute; right: 0;">
|
||||||
|
<button @click=${this._importTrace}>Import trace</button>
|
||||||
|
<button @click=${this._loadLocalStorageTrace}>Load stored trace</button>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionButtons = html`
|
||||||
|
<mwc-icon-button label="Refresh" @click=${() => this._loadTraces()}>
|
||||||
|
<ha-svg-icon .path=${mdiRefresh}></ha-svg-icon>
|
||||||
|
</mwc-icon-button>
|
||||||
|
<mwc-icon-button
|
||||||
|
.disabled=${!this._trace}
|
||||||
|
label="Download Trace"
|
||||||
|
@click=${this._downloadTrace}
|
||||||
|
>
|
||||||
|
<ha-svg-icon .path=${mdiDownload}></ha-svg-icon>
|
||||||
|
</mwc-icon-button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
${devButtons}
|
||||||
|
<hass-tabs-subpage
|
||||||
|
.hass=${this.hass}
|
||||||
|
.narrow=${this.narrow}
|
||||||
|
.route=${this.route}
|
||||||
|
.tabs=${configSections.automation}
|
||||||
|
>
|
||||||
|
${this.narrow
|
||||||
|
? html`<span slot="header"> ${title} </span>
|
||||||
|
<div slot="toolbar-icon">${actionButtons}</div>`
|
||||||
|
: ""}
|
||||||
|
<div class="toolbar">
|
||||||
|
${!this.narrow
|
||||||
|
? html`<div>
|
||||||
|
${title}
|
||||||
|
<a
|
||||||
|
class="linkButton"
|
||||||
|
href="/config/script/edit/${this.scriptEntityId}"
|
||||||
|
>
|
||||||
|
<mwc-icon-button label="Edit Script" tabindex="-1">
|
||||||
|
<ha-svg-icon .path=${mdiPencil}></ha-svg-icon>
|
||||||
|
</mwc-icon-button>
|
||||||
|
</a>
|
||||||
|
</div>`
|
||||||
|
: ""}
|
||||||
|
${this._traces && this._traces.length > 0
|
||||||
|
? html`
|
||||||
|
<div>
|
||||||
|
<mwc-icon-button
|
||||||
|
.disabled=${this._traces[this._traces.length - 1].run_id ===
|
||||||
|
this._runId}
|
||||||
|
label="Older trace"
|
||||||
|
@click=${this._pickOlderTrace}
|
||||||
|
>
|
||||||
|
<ha-svg-icon .path=${mdiRayEndArrow}></ha-svg-icon>
|
||||||
|
</mwc-icon-button>
|
||||||
|
<select .value=${this._runId} @change=${this._pickTrace}>
|
||||||
|
${repeat(
|
||||||
|
this._traces,
|
||||||
|
(trace) => trace.run_id,
|
||||||
|
(trace) =>
|
||||||
|
html`<option value=${trace.run_id}>
|
||||||
|
${formatDateTimeWithSeconds(
|
||||||
|
new Date(trace.timestamp.start),
|
||||||
|
this.hass.locale
|
||||||
|
)}
|
||||||
|
</option>`
|
||||||
|
)}
|
||||||
|
</select>
|
||||||
|
<mwc-icon-button
|
||||||
|
.disabled=${this._traces[0].run_id === this._runId}
|
||||||
|
label="Newer trace"
|
||||||
|
@click=${this._pickNewerTrace}
|
||||||
|
>
|
||||||
|
<ha-svg-icon .path=${mdiRayStartArrow}></ha-svg-icon>
|
||||||
|
</mwc-icon-button>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${!this.narrow ? html`<div>${actionButtons}</div>` : ""}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${this._traces === undefined
|
||||||
|
? html`<div class="container">Loading…</div>`
|
||||||
|
: this._traces.length === 0
|
||||||
|
? html`<div class="container">No traces found</div>`
|
||||||
|
: this._trace === undefined
|
||||||
|
? ""
|
||||||
|
: html`
|
||||||
|
<div class="main">
|
||||||
|
<div class="graph">
|
||||||
|
<hat-script-graph
|
||||||
|
.trace=${this._trace}
|
||||||
|
.selected=${this._selected?.path}
|
||||||
|
@graph-node-selected=${this._pickNode}
|
||||||
|
></hat-script-graph>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info">
|
||||||
|
<div class="tabs top">
|
||||||
|
${[
|
||||||
|
["details", "Step Details"],
|
||||||
|
["timeline", "Trace Timeline"],
|
||||||
|
["logbook", "Related logbook entries"],
|
||||||
|
["config", "Script Config"],
|
||||||
|
].map(
|
||||||
|
([view, label]) => html`
|
||||||
|
<button
|
||||||
|
tabindex="0"
|
||||||
|
.view=${view}
|
||||||
|
class=${classMap({ active: this._view === view })}
|
||||||
|
@click=${this._showTab}
|
||||||
|
>
|
||||||
|
${label}
|
||||||
|
</button>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
${this._trace.blueprint_inputs
|
||||||
|
? html`
|
||||||
|
<button
|
||||||
|
tabindex="0"
|
||||||
|
.view=${"blueprint"}
|
||||||
|
class=${classMap({
|
||||||
|
active: this._view === "blueprint",
|
||||||
|
})}
|
||||||
|
@click=${this._showTab}
|
||||||
|
>
|
||||||
|
Blueprint Config
|
||||||
|
</button>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
${this._selected === undefined ||
|
||||||
|
this._logbookEntries === undefined ||
|
||||||
|
trackedNodes === undefined
|
||||||
|
? ""
|
||||||
|
: this._view === "details"
|
||||||
|
? html`
|
||||||
|
<ha-trace-path-details
|
||||||
|
.hass=${this.hass}
|
||||||
|
.narrow=${this.narrow}
|
||||||
|
.trace=${this._trace}
|
||||||
|
.selected=${this._selected}
|
||||||
|
.logbookEntries=${this._logbookEntries}
|
||||||
|
.trackedNodes=${trackedNodes}
|
||||||
|
.renderedNodes=${renderedNodes!}
|
||||||
|
></ha-trace-path-details>
|
||||||
|
`
|
||||||
|
: this._view === "config"
|
||||||
|
? html`
|
||||||
|
<ha-trace-config
|
||||||
|
.hass=${this.hass}
|
||||||
|
.trace=${this._trace}
|
||||||
|
></ha-trace-config>
|
||||||
|
`
|
||||||
|
: this._view === "logbook"
|
||||||
|
? html`
|
||||||
|
<ha-trace-logbook
|
||||||
|
.hass=${this.hass}
|
||||||
|
.narrow=${this.narrow}
|
||||||
|
.trace=${this._trace}
|
||||||
|
.logbookEntries=${this._logbookEntries}
|
||||||
|
></ha-trace-logbook>
|
||||||
|
`
|
||||||
|
: this._view === "blueprint"
|
||||||
|
? html`
|
||||||
|
<ha-trace-blueprint-config
|
||||||
|
.hass=${this.hass}
|
||||||
|
.trace=${this._trace}
|
||||||
|
></ha-trace-blueprint-config>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<ha-trace-timeline
|
||||||
|
.hass=${this.hass}
|
||||||
|
.trace=${this._trace}
|
||||||
|
.logbookEntries=${this._logbookEntries}
|
||||||
|
.selected=${this._selected}
|
||||||
|
@value-changed=${this._timelinePathPicked}
|
||||||
|
></ha-trace-timeline>
|
||||||
|
`}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`}
|
||||||
|
</hass-tabs-subpage>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected firstUpdated(changedProps) {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
|
||||||
|
if (!this.scriptEntityId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = new URLSearchParams(location.search);
|
||||||
|
this._loadTraces(params.get("run_id") || undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated(changedProps) {
|
||||||
|
super.updated(changedProps);
|
||||||
|
|
||||||
|
// Only reset if automationId has changed and we had one before.
|
||||||
|
if (changedProps.get("scriptEntityId")) {
|
||||||
|
this._traces = undefined;
|
||||||
|
this._runId = undefined;
|
||||||
|
this._trace = undefined;
|
||||||
|
this._logbookEntries = undefined;
|
||||||
|
if (this.scriptEntityId) {
|
||||||
|
this._loadTraces();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changedProps.has("_runId") && this._runId) {
|
||||||
|
this._trace = undefined;
|
||||||
|
this._logbookEntries = undefined;
|
||||||
|
this.shadowRoot!.querySelector("select")!.value = this._runId;
|
||||||
|
this._loadTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _pickOlderTrace() {
|
||||||
|
const curIndex = this._traces!.findIndex((tr) => tr.run_id === this._runId);
|
||||||
|
this._runId = this._traces![curIndex + 1].run_id;
|
||||||
|
this._selected = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _pickNewerTrace() {
|
||||||
|
const curIndex = this._traces!.findIndex((tr) => tr.run_id === this._runId);
|
||||||
|
this._runId = this._traces![curIndex - 1].run_id;
|
||||||
|
this._selected = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _pickTrace(ev) {
|
||||||
|
this._runId = ev.target.value;
|
||||||
|
this._selected = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _pickNode(ev) {
|
||||||
|
this._selected = ev.detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _loadTraces(runId?: string) {
|
||||||
|
this._traces = await loadTraces(
|
||||||
|
this.hass,
|
||||||
|
"script",
|
||||||
|
this.scriptEntityId.split(".")[1]
|
||||||
|
);
|
||||||
|
// Newest will be on top.
|
||||||
|
this._traces.reverse();
|
||||||
|
|
||||||
|
if (runId) {
|
||||||
|
this._runId = runId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if current run ID still exists
|
||||||
|
if (
|
||||||
|
this._runId &&
|
||||||
|
!this._traces.some((trace) => trace.run_id === this._runId)
|
||||||
|
) {
|
||||||
|
this._runId = undefined;
|
||||||
|
this._selected = undefined;
|
||||||
|
|
||||||
|
// If we came here from a trace passed into the url, clear it.
|
||||||
|
if (runId) {
|
||||||
|
const params = new URLSearchParams(location.search);
|
||||||
|
params.delete("run_id");
|
||||||
|
history.replaceState(
|
||||||
|
null,
|
||||||
|
"",
|
||||||
|
`${location.pathname}?${params.toString()}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await showAlertDialog(this, {
|
||||||
|
text: "Chosen trace is no longer available",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// See if we can set a default runID
|
||||||
|
if (!this._runId && this._traces.length > 0) {
|
||||||
|
this._runId = this._traces[0].run_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _loadTrace() {
|
||||||
|
const trace = await loadTrace(
|
||||||
|
this.hass,
|
||||||
|
"script",
|
||||||
|
this.scriptEntityId.split(".")[1],
|
||||||
|
this._runId!
|
||||||
|
);
|
||||||
|
this._logbookEntries = isComponentLoaded(this.hass, "logbook")
|
||||||
|
? await getLogbookDataForContext(
|
||||||
|
this.hass,
|
||||||
|
trace.timestamp.start,
|
||||||
|
trace.context.id
|
||||||
|
)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
this._trace = trace;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _downloadTrace() {
|
||||||
|
const aEl = document.createElement("a");
|
||||||
|
aEl.download = `trace ${this.scriptEntityId} ${
|
||||||
|
this._trace!.timestamp.start
|
||||||
|
}.json`;
|
||||||
|
aEl.href = `data:application/json;charset=utf-8,${encodeURI(
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
trace: this._trace,
|
||||||
|
logbookEntries: this._logbookEntries,
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
2
|
||||||
|
)
|
||||||
|
)}`;
|
||||||
|
aEl.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _importTrace() {
|
||||||
|
const traceText = prompt("Enter downloaded trace");
|
||||||
|
if (!traceText) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
localStorage.devTrace = traceText;
|
||||||
|
this._loadLocalTrace(traceText);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _loadLocalStorageTrace() {
|
||||||
|
if (localStorage.devTrace) {
|
||||||
|
this._loadLocalTrace(localStorage.devTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _loadLocalTrace(traceText: string) {
|
||||||
|
const traceInfo = JSON.parse(traceText);
|
||||||
|
this._trace = traceInfo.trace;
|
||||||
|
this._logbookEntries = traceInfo.logbookEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _showTab(ev) {
|
||||||
|
this._view = (ev.target as any).view;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _timelinePathPicked(ev) {
|
||||||
|
const path = ev.detail.value;
|
||||||
|
const nodes = this.shadowRoot!.querySelector("hat-script-graph")!
|
||||||
|
.trackedNodes;
|
||||||
|
if (nodes[path]) {
|
||||||
|
this._selected = nodes[path];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return [
|
||||||
|
haStyle,
|
||||||
|
traceTabStyles,
|
||||||
|
css`
|
||||||
|
.toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 20px;
|
||||||
|
height: var(--header-height);
|
||||||
|
padding: 0 16px;
|
||||||
|
background-color: var(--primary-background-color);
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--app-header-text-color, white);
|
||||||
|
border-bottom: var(--app-header-border-bottom, none);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar > * {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([narrow]) .toolbar > * {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
height: calc(100% - 56px);
|
||||||
|
display: flex;
|
||||||
|
background-color: var(--card-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([narrow]) .main {
|
||||||
|
height: auto;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.graph {
|
||||||
|
border-right: 1px solid var(--divider-color);
|
||||||
|
overflow-x: auto;
|
||||||
|
max-width: 50%;
|
||||||
|
}
|
||||||
|
:host([narrow]) .graph {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
flex: 1;
|
||||||
|
background-color: var(--card-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkButton {
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-script-trace": HaScriptTrace;
|
||||||
|
}
|
||||||
|
}
|
@ -303,7 +303,7 @@
|
|||||||
"entries_not_found": "No logbook entries found.",
|
"entries_not_found": "No logbook entries found.",
|
||||||
"by": "by",
|
"by": "by",
|
||||||
"by_service": "by service",
|
"by_service": "by service",
|
||||||
"show_trace": "Show trace",
|
"show_trace": "[%key:ui::panel::config::automation::editor::show_trace%]",
|
||||||
"retrieval_error": "Error during logbook entry retrieval",
|
"retrieval_error": "Error during logbook entry retrieval",
|
||||||
"messages": {
|
"messages": {
|
||||||
"was_away": "was detected away",
|
"was_away": "was detected away",
|
||||||
@ -1640,6 +1640,7 @@
|
|||||||
"show_info": "Show info about script",
|
"show_info": "Show info about script",
|
||||||
"run_script": "Run script",
|
"run_script": "Run script",
|
||||||
"edit_script": "Edit script",
|
"edit_script": "Edit script",
|
||||||
|
"dev_script": "Debug script",
|
||||||
"headers": {
|
"headers": {
|
||||||
"name": "Name"
|
"name": "Name"
|
||||||
},
|
},
|
||||||
@ -1653,6 +1654,7 @@
|
|||||||
"id_already_exists_save_error": "You can't save this script because the ID is not unique, pick another ID or leave it blank to automatically generate one.",
|
"id_already_exists_save_error": "You can't save this script because the ID is not unique, pick another ID or leave it blank to automatically generate one.",
|
||||||
"id_already_exists": "This ID already exists",
|
"id_already_exists": "This ID already exists",
|
||||||
"introduction": "Use scripts to run a sequence of actions.",
|
"introduction": "Use scripts to run a sequence of actions.",
|
||||||
|
"show_trace": "[%key:ui::panel::config::automation::editor::show_trace%]",
|
||||||
"header": "Script: {name}",
|
"header": "Script: {name}",
|
||||||
"default_name": "New Script",
|
"default_name": "New Script",
|
||||||
"modes": {
|
"modes": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user