mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 17:56:46 +00:00
Merge pull request #8795 from home-assistant/dev
This commit is contained in:
commit
c810e541ea
@ -2,8 +2,7 @@ import { DemoTrace } from "./types";
|
||||
|
||||
export const basicTrace: DemoTrace = {
|
||||
trace: {
|
||||
last_action: "action/2",
|
||||
last_condition: "condition/0",
|
||||
last_step: "action/2",
|
||||
run_id: "0",
|
||||
state: "stopped",
|
||||
timestamp: {
|
||||
@ -14,6 +13,12 @@ export const basicTrace: DemoTrace = {
|
||||
domain: "automation",
|
||||
item_id: "1615419646544",
|
||||
trace: {
|
||||
"trigger/0": [
|
||||
{
|
||||
path: "trigger/0",
|
||||
timestamp: "2021-03-25T04:36:51.223693+00:00",
|
||||
},
|
||||
],
|
||||
"condition/0": [
|
||||
{
|
||||
path: "condition/0",
|
||||
@ -284,45 +289,7 @@ export const basicTrace: DemoTrace = {
|
||||
parent_id: "664d6d261450a9ecea6738e97269a149",
|
||||
user_id: null,
|
||||
},
|
||||
variables: {
|
||||
trigger: {
|
||||
platform: "state",
|
||||
entity_id: "input_boolean.toggle_1",
|
||||
from_state: {
|
||||
entity_id: "input_boolean.toggle_1",
|
||||
state: "on",
|
||||
attributes: {
|
||||
editable: true,
|
||||
friendly_name: "Toggle 1",
|
||||
},
|
||||
last_changed: "2021-03-24T19:03:59.141440+00:00",
|
||||
last_updated: "2021-03-24T19:03:59.141440+00:00",
|
||||
context: {
|
||||
id: "5d0918eb379214d07554bdab6a08bcff",
|
||||
parent_id: null,
|
||||
user_id: null,
|
||||
},
|
||||
},
|
||||
to_state: {
|
||||
entity_id: "input_boolean.toggle_1",
|
||||
state: "off",
|
||||
attributes: {
|
||||
editable: true,
|
||||
friendly_name: "Toggle 1",
|
||||
},
|
||||
last_changed: "2021-03-25T04:36:51.220696+00:00",
|
||||
last_updated: "2021-03-25T04:36:51.220696+00:00",
|
||||
context: {
|
||||
id: "664d6d261450a9ecea6738e97269a149",
|
||||
parent_id: null,
|
||||
user_id: "d1b4e89da01445fa8bc98e39fac477ca",
|
||||
},
|
||||
},
|
||||
for: null,
|
||||
attribute: null,
|
||||
description: "state of input_boolean.toggle_1",
|
||||
},
|
||||
},
|
||||
script_execution: "finished",
|
||||
},
|
||||
logbookEntries: [
|
||||
{
|
||||
|
44
gallery/src/data/traces/mock-demo-trace.ts
Normal file
44
gallery/src/data/traces/mock-demo-trace.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { LogbookEntry } from "../../../../src/data/logbook";
|
||||
import { AutomationTraceExtended } from "../../../../src/data/trace";
|
||||
import { DemoTrace } from "./types";
|
||||
|
||||
export const mockDemoTrace = (
|
||||
tracePartial: Partial<AutomationTraceExtended>,
|
||||
logbookEntries?: LogbookEntry[]
|
||||
): DemoTrace => ({
|
||||
trace: {
|
||||
last_step: "",
|
||||
run_id: "0",
|
||||
state: "stopped",
|
||||
timestamp: {
|
||||
start: "2021-03-25T04:36:51.223693+00:00",
|
||||
finish: "2021-03-25T04:36:51.266132+00:00",
|
||||
},
|
||||
trigger: "mocked trigger",
|
||||
domain: "automation",
|
||||
item_id: "1615419646544",
|
||||
trace: {
|
||||
"trigger/0": [
|
||||
{
|
||||
path: "trigger/0",
|
||||
changed_variables: {
|
||||
trigger: {
|
||||
description: "mocked trigger",
|
||||
},
|
||||
},
|
||||
timestamp: "2021-03-25T04:36:51.223693+00:00",
|
||||
},
|
||||
],
|
||||
},
|
||||
config: {
|
||||
trigger: [],
|
||||
action: [],
|
||||
},
|
||||
context: {
|
||||
id: "abcd",
|
||||
},
|
||||
script_execution: "finished",
|
||||
...tracePartial,
|
||||
},
|
||||
logbookEntries: logbookEntries || [],
|
||||
});
|
@ -2,8 +2,7 @@ import { DemoTrace } from "./types";
|
||||
|
||||
export const motionLightTrace: DemoTrace = {
|
||||
trace: {
|
||||
last_action: "action/3",
|
||||
last_condition: null,
|
||||
last_step: "action/3",
|
||||
run_id: "1",
|
||||
state: "stopped",
|
||||
timestamp: {
|
||||
@ -14,6 +13,12 @@ export const motionLightTrace: DemoTrace = {
|
||||
domain: "automation",
|
||||
item_id: "1614732497392",
|
||||
trace: {
|
||||
"trigger/0": [
|
||||
{
|
||||
path: "trigger/0",
|
||||
timestamp: "2021-03-25T04:36:51.223693+00:00",
|
||||
},
|
||||
],
|
||||
"action/0": [
|
||||
{
|
||||
path: "action/0",
|
||||
@ -171,45 +176,7 @@ export const motionLightTrace: DemoTrace = {
|
||||
parent_id: "e22ddfd5f11dc4aad9a52fc10dab613b",
|
||||
user_id: null,
|
||||
},
|
||||
variables: {
|
||||
trigger: {
|
||||
platform: "state",
|
||||
entity_id: "binary_sensor.pauluss_macbook_pro_camera_in_use",
|
||||
from_state: {
|
||||
entity_id: "binary_sensor.pauluss_macbook_pro_camera_in_use",
|
||||
state: "off",
|
||||
attributes: {
|
||||
friendly_name: "Paulus’s MacBook Pro Camera In Use",
|
||||
icon: "mdi:camera-off",
|
||||
},
|
||||
last_changed: "2021-03-14T06:06:29.235325+00:00",
|
||||
last_updated: "2021-03-14T06:06:29.235325+00:00",
|
||||
context: {
|
||||
id: "ad4864c5ce957c38a07b50378eeb245d",
|
||||
parent_id: null,
|
||||
user_id: null,
|
||||
},
|
||||
},
|
||||
to_state: {
|
||||
entity_id: "binary_sensor.pauluss_macbook_pro_camera_in_use",
|
||||
state: "on",
|
||||
attributes: {
|
||||
friendly_name: "Paulus’s MacBook Pro Camera In Use",
|
||||
icon: "mdi:camera",
|
||||
},
|
||||
last_changed: "2021-03-14T06:07:01.762009+00:00",
|
||||
last_updated: "2021-03-14T06:07:01.762009+00:00",
|
||||
context: {
|
||||
id: "e22ddfd5f11dc4aad9a52fc10dab613b",
|
||||
parent_id: null,
|
||||
user_id: null,
|
||||
},
|
||||
},
|
||||
for: null,
|
||||
attribute: null,
|
||||
description: "state of binary_sensor.pauluss_macbook_pro_camera_in_use",
|
||||
},
|
||||
},
|
||||
script_execution: "finished",
|
||||
},
|
||||
logbookEntries: [
|
||||
{
|
||||
|
102
gallery/src/demos/demo-automation-describe-action.ts
Normal file
102
gallery/src/demos/demo-automation-describe-action.ts
Normal file
@ -0,0 +1,102 @@
|
||||
import { safeDump } from "js-yaml";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
css,
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
property,
|
||||
} from "lit-element";
|
||||
import "../../../src/components/ha-card";
|
||||
import { describeAction } from "../../../src/data/script_i18n";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
|
||||
const actions = [
|
||||
{ wait_template: "{{ true }}", alias: "Something with an alias" },
|
||||
{ delay: "0:05" },
|
||||
{ wait_template: "{{ true }}" },
|
||||
{
|
||||
condition: "template",
|
||||
value_template: "{{ true }}",
|
||||
},
|
||||
{ event: "happy_event" },
|
||||
{
|
||||
device_id: "abcdefgh",
|
||||
domain: "plex",
|
||||
entity_id: "media_player.kitchen",
|
||||
},
|
||||
{ scene: "scene.kitchen_morning" },
|
||||
{
|
||||
wait_for_trigger: [
|
||||
{
|
||||
platform: "state",
|
||||
entity_id: "input_boolean.toggle_1",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
variables: {
|
||||
hello: "world",
|
||||
},
|
||||
},
|
||||
{
|
||||
service: "input_boolean.toggle",
|
||||
target: {
|
||||
entity_id: "input_boolean.toggle_4",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("demo-automation-describe-action")
|
||||
export class DemoAutomationDescribeAction extends LitElement {
|
||||
@property({ attribute: false }) hass!: HomeAssistant;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<ha-card header="Actions">
|
||||
${actions.map(
|
||||
(conf) => html`
|
||||
<div class="action">
|
||||
<span>${describeAction(this.hass, conf as any)}</span>
|
||||
<pre>${safeDump(conf)}</pre>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
const hass = provideHass(this);
|
||||
hass.updateTranslations(null, "en");
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
ha-card {
|
||||
max-width: 600px;
|
||||
margin: 24px auto;
|
||||
}
|
||||
.action {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
span {
|
||||
margin-right: 16px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-automation-describe-action": DemoAutomationDescribeAction;
|
||||
}
|
||||
}
|
65
gallery/src/demos/demo-automation-describe-condition.ts
Normal file
65
gallery/src/demos/demo-automation-describe-condition.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { safeDump } from "js-yaml";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
css,
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../src/components/ha-card";
|
||||
import { describeCondition } from "../../../src/data/automation_i18n";
|
||||
|
||||
const conditions = [
|
||||
{ condition: "and" },
|
||||
{ condition: "not" },
|
||||
{ condition: "or" },
|
||||
{ condition: "state" },
|
||||
{ condition: "numeric_state" },
|
||||
{ condition: "sun", after: "sunset" },
|
||||
{ condition: "sun", after: "sunrise" },
|
||||
{ condition: "zone" },
|
||||
{ condition: "time" },
|
||||
{ condition: "template" },
|
||||
];
|
||||
|
||||
@customElement("demo-automation-describe-condition")
|
||||
export class DemoAutomationDescribeCondition extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-card header="Conditions">
|
||||
${conditions.map(
|
||||
(conf) => html`
|
||||
<div class="condition">
|
||||
<span>${describeCondition(conf as any)}</span>
|
||||
<pre>${safeDump(conf)}</pre>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
ha-card {
|
||||
max-width: 600px;
|
||||
margin: 24px auto;
|
||||
}
|
||||
.condition {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
span {
|
||||
margin-right: 16px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-automation-describe-condition": DemoAutomationDescribeCondition;
|
||||
}
|
||||
}
|
68
gallery/src/demos/demo-automation-describe-trigger.ts
Normal file
68
gallery/src/demos/demo-automation-describe-trigger.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import { safeDump } from "js-yaml";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
css,
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../src/components/ha-card";
|
||||
import { describeTrigger } from "../../../src/data/automation_i18n";
|
||||
|
||||
const triggers = [
|
||||
{ platform: "state" },
|
||||
{ platform: "mqtt" },
|
||||
{ platform: "geo_location" },
|
||||
{ platform: "homeassistant" },
|
||||
{ platform: "numeric_state" },
|
||||
{ platform: "sun" },
|
||||
{ platform: "time_pattern" },
|
||||
{ platform: "webhook" },
|
||||
{ platform: "zone" },
|
||||
{ platform: "tag" },
|
||||
{ platform: "time" },
|
||||
{ platform: "template" },
|
||||
{ platform: "event" },
|
||||
];
|
||||
|
||||
@customElement("demo-automation-describe-trigger")
|
||||
export class DemoAutomationDescribeTrigger extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-card header="Triggers">
|
||||
${triggers.map(
|
||||
(conf) => html`
|
||||
<div class="trigger">
|
||||
<span>${describeTrigger(conf as any)}</span>
|
||||
<pre>${safeDump(conf)}</pre>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
ha-card {
|
||||
max-width: 600px;
|
||||
margin: 24px auto;
|
||||
}
|
||||
.trigger {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
span {
|
||||
margin-right: 16px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-automation-describe-trigger": DemoAutomationDescribeTrigger;
|
||||
}
|
||||
}
|
87
gallery/src/demos/demo-automation-trace-timeline.ts
Normal file
87
gallery/src/demos/demo-automation-trace-timeline.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
css,
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
property,
|
||||
} from "lit-element";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/trace/hat-script-graph";
|
||||
import "../../../src/components/trace/hat-trace-timeline";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import { mockDemoTrace } from "../data/traces/mock-demo-trace";
|
||||
import { DemoTrace } from "../data/traces/types";
|
||||
|
||||
const traces: DemoTrace[] = [
|
||||
mockDemoTrace({ state: "running" }),
|
||||
mockDemoTrace({ state: "debugged" }),
|
||||
mockDemoTrace({ state: "stopped", script_execution: "failed_condition" }),
|
||||
mockDemoTrace({ state: "stopped", script_execution: "failed_single" }),
|
||||
mockDemoTrace({ state: "stopped", script_execution: "failed_max_runs" }),
|
||||
mockDemoTrace({ state: "stopped", script_execution: "finished" }),
|
||||
mockDemoTrace({ state: "stopped", script_execution: "aborted" }),
|
||||
mockDemoTrace({
|
||||
state: "stopped",
|
||||
script_execution: "error",
|
||||
error: 'Variable "beer" cannot be None',
|
||||
}),
|
||||
mockDemoTrace({ state: "stopped", script_execution: "cancelled" }),
|
||||
];
|
||||
|
||||
@customElement("demo-automation-trace-timeline")
|
||||
export class DemoAutomationTraceTimeline extends LitElement {
|
||||
@property({ attribute: false }) hass?: HomeAssistant;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
${traces.map(
|
||||
(trace) => html`
|
||||
<ha-card .header=${trace.trace.config.alias}>
|
||||
<div class="card-content">
|
||||
<hat-trace-timeline
|
||||
.hass=${this.hass}
|
||||
.trace=${trace.trace}
|
||||
.logbookEntries=${trace.logbookEntries}
|
||||
></hat-trace-timeline>
|
||||
<button @click=${() => console.log(trace)}>Log trace</button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`
|
||||
)}
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
const hass = provideHass(this);
|
||||
hass.updateTranslations(null, "en");
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
ha-card {
|
||||
max-width: 600px;
|
||||
margin: 24px;
|
||||
}
|
||||
.card-content {
|
||||
display: flex;
|
||||
}
|
||||
button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-automation-trace-timeline": DemoAutomationTraceTimeline;
|
||||
}
|
||||
}
|
@ -4,9 +4,11 @@ import {
|
||||
css,
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
internalProperty,
|
||||
property,
|
||||
} from "lit-element";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/trace/hat-script-graph";
|
||||
import "../../../src/components/trace/hat-trace-timeline";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
@ -20,20 +22,38 @@ const traces: DemoTrace[] = [basicTrace, motionLightTrace];
|
||||
export class DemoAutomationTrace extends LitElement {
|
||||
@property({ attribute: false }) hass?: HomeAssistant;
|
||||
|
||||
@internalProperty() private _selected = {};
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
${traces.map(
|
||||
(trace) => html`
|
||||
<ha-card .heading=${trace.trace.config.alias}>
|
||||
(trace, idx) => html`
|
||||
<ha-card .header=${trace.trace.config.alias}>
|
||||
<div class="card-content">
|
||||
<hat-script-graph
|
||||
.trace=${trace.trace}
|
||||
.selected=${this._selected[idx]}
|
||||
@graph-node-selected=${(ev) => {
|
||||
this._selected = { ...this._selected, [idx]: ev.detail.path };
|
||||
}}
|
||||
></hat-script-graph>
|
||||
<hat-trace-timeline
|
||||
allowPick
|
||||
.hass=${this.hass}
|
||||
.trace=${trace.trace}
|
||||
.logbookEntries=${trace.logbookEntries}
|
||||
.selectedPath=${this._selected[idx]}
|
||||
@value-changed=${(ev) => {
|
||||
this._selected = {
|
||||
...this._selected,
|
||||
[idx]: ev.detail.value,
|
||||
};
|
||||
}}
|
||||
></hat-trace-timeline>
|
||||
<button @click=${() => console.log(trace)}>Log trace</button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`
|
||||
@ -53,6 +73,20 @@ export class DemoAutomationTrace extends LitElement {
|
||||
max-width: 600px;
|
||||
margin: 24px;
|
||||
}
|
||||
.card-content {
|
||||
display: flex;
|
||||
}
|
||||
.card-content > * {
|
||||
margin-right: 16px;
|
||||
}
|
||||
.card-content > *:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@ -10,10 +10,10 @@ function patch(version) {
|
||||
|
||||
function today() {
|
||||
const now = new Date();
|
||||
return `${now.getFullYear()}${String(now.getMonth() + 1).padStart(
|
||||
return `${now.getUTCFullYear()}${String(now.getUTCMonth() + 1).padStart(
|
||||
2,
|
||||
"0"
|
||||
)}${String(now.getDate()).padStart(2, "0")}.0`;
|
||||
)}${String(now.getUTCDate()).padStart(2, "0")}.0`;
|
||||
}
|
||||
|
||||
function auto(version) {
|
||||
|
2
setup.py
2
setup.py
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20210331.0",
|
||||
version="20210402.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
@ -1,6 +1,8 @@
|
||||
export const ensureArray = (value?: any) => {
|
||||
if (!value || Array.isArray(value)) {
|
||||
export function ensureArray(value: undefined): undefined;
|
||||
export function ensureArray<T>(value: T | T[]): T[];
|
||||
export function ensureArray(value) {
|
||||
if (value === undefined || Array.isArray(value)) {
|
||||
return value;
|
||||
}
|
||||
return [value];
|
||||
};
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
const isTemplateRegex = new RegExp("{%|{{|{#");
|
||||
const isTemplateRegex = new RegExp("{%|{{");
|
||||
|
||||
export const isTemplate = (value: string): boolean =>
|
||||
isTemplateRegex.test(value);
|
||||
|
||||
@ -11,7 +12,7 @@ export const hasTemplate = (value: unknown): boolean => {
|
||||
}
|
||||
if (typeof value === "object") {
|
||||
const values = Array.isArray(value) ? value : Object.values(value!);
|
||||
return values.some((val) => hasTemplate(val));
|
||||
return values.some((val) => val && hasTemplate(val));
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
@ -63,7 +63,7 @@ export interface DataTableSortColumnData {
|
||||
}
|
||||
|
||||
export interface DataTableColumnData extends DataTableSortColumnData {
|
||||
title: string;
|
||||
title: TemplateResult | string;
|
||||
type?: "numeric" | "icon" | "icon-button";
|
||||
template?: <T>(data: any, row: T) => TemplateResult | string;
|
||||
width?: string;
|
||||
@ -74,7 +74,7 @@ export interface DataTableColumnData extends DataTableSortColumnData {
|
||||
}
|
||||
|
||||
type ClonedDataTableColumnData = Omit<DataTableColumnData, "title"> & {
|
||||
title?: string;
|
||||
title?: TemplateResult | string;
|
||||
};
|
||||
|
||||
export interface DataTableRowData {
|
||||
|
@ -125,35 +125,41 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
return html``;
|
||||
}
|
||||
return html`<div class="mdc-chip-set items">
|
||||
${ensureArray(this.value?.area_id)?.map((area_id) => {
|
||||
const area = this._areas![area_id];
|
||||
return this._renderChip(
|
||||
"area_id",
|
||||
area_id,
|
||||
area?.name || area_id,
|
||||
undefined,
|
||||
mdiSofa
|
||||
);
|
||||
})}
|
||||
${ensureArray(this.value?.device_id)?.map((device_id) => {
|
||||
const device = this._devices![device_id];
|
||||
return this._renderChip(
|
||||
"device_id",
|
||||
device_id,
|
||||
device ? computeDeviceName(device, this.hass) : device_id,
|
||||
undefined,
|
||||
mdiDevices
|
||||
);
|
||||
})}
|
||||
${ensureArray(this.value?.entity_id)?.map((entity_id) => {
|
||||
const entity = this.hass.states[entity_id];
|
||||
return this._renderChip(
|
||||
"entity_id",
|
||||
entity_id,
|
||||
entity ? computeStateName(entity) : entity_id,
|
||||
entity ? stateIcon(entity) : undefined
|
||||
);
|
||||
})}
|
||||
${this.value?.area_id
|
||||
? ensureArray(this.value.area_id).map((area_id) => {
|
||||
const area = this._areas![area_id];
|
||||
return this._renderChip(
|
||||
"area_id",
|
||||
area_id,
|
||||
area?.name || area_id,
|
||||
undefined,
|
||||
mdiSofa
|
||||
);
|
||||
})
|
||||
: ""}
|
||||
${this.value?.device_id
|
||||
? ensureArray(this.value.device_id).map((device_id) => {
|
||||
const device = this._devices![device_id];
|
||||
return this._renderChip(
|
||||
"device_id",
|
||||
device_id,
|
||||
device ? computeDeviceName(device, this.hass) : device_id,
|
||||
undefined,
|
||||
mdiDevices
|
||||
);
|
||||
})
|
||||
: ""}
|
||||
${this.value?.entity_id
|
||||
? ensureArray(this.value.entity_id).map((entity_id) => {
|
||||
const entity = this.hass.states[entity_id];
|
||||
return this._renderChip(
|
||||
"entity_id",
|
||||
entity_id,
|
||||
entity ? computeStateName(entity) : entity_id,
|
||||
entity ? stateIcon(entity) : undefined
|
||||
);
|
||||
})
|
||||
: ""}
|
||||
</div>
|
||||
${this._renderPicker()}
|
||||
<div class="mdc-chip-set">
|
||||
|
@ -48,6 +48,7 @@ import {
|
||||
WaitAction,
|
||||
WaitForTriggerAction,
|
||||
} from "../../data/script";
|
||||
import { ensureArray } from "../../common/ensure-array";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
@ -93,7 +94,7 @@ class HatScriptGraph extends LitElement {
|
||||
const path = `condition/${i}`;
|
||||
const trace = this.trace.trace[path] as ConditionTraceStep[] | undefined;
|
||||
const track_path =
|
||||
trace === undefined ? 0 : trace![0].result.result ? 1 : 2;
|
||||
trace?.[0].result === undefined ? 0 : trace[0].result.result ? 1 : 2;
|
||||
if (trace) {
|
||||
this.trackedNodes[path] = { config, path };
|
||||
}
|
||||
@ -139,7 +140,7 @@ class HatScriptGraph extends LitElement {
|
||||
|
||||
private render_choose_node(config: ChooseAction, path: string) {
|
||||
const trace = this.trace.trace[path] as ChooseActionTraceStep[] | undefined;
|
||||
const trace_path = trace
|
||||
const trace_path = trace?.[0].result
|
||||
? trace[0].result.choice === "default"
|
||||
? [config.choose.length]
|
||||
: [trace[0].result.choice]
|
||||
@ -173,7 +174,7 @@ class HatScriptGraph extends LitElement {
|
||||
.iconPath=${mdiCheckBoxOutline}
|
||||
nofocus
|
||||
class=${classMap({
|
||||
track: trace !== undefined && trace[0].result.choice === i,
|
||||
track: trace !== undefined && trace[0].result?.choice === i,
|
||||
})}
|
||||
></hat-graph-node>
|
||||
${branch.sequence.map((action, j) =>
|
||||
@ -188,7 +189,7 @@ class HatScriptGraph extends LitElement {
|
||||
nofocus
|
||||
class=${classMap({
|
||||
track:
|
||||
trace !== undefined && trace[0].result.choice === "default",
|
||||
trace !== undefined && trace[0].result?.choice === "default",
|
||||
})}
|
||||
></hat-graph-node>
|
||||
${config.default?.map((action, i) =>
|
||||
@ -200,8 +201,9 @@ class HatScriptGraph extends LitElement {
|
||||
}
|
||||
|
||||
private render_condition_node(node: Condition, path: string) {
|
||||
const trace: any = this.trace.trace[path];
|
||||
const track_path = trace === undefined ? 0 : trace[0].result.result ? 1 : 2;
|
||||
const trace = (this.trace.trace[path] as ConditionTraceStep[]) || undefined;
|
||||
const track_path =
|
||||
trace?.[0].result === undefined ? 0 : trace[0].result.result ? 1 : 2;
|
||||
return html`
|
||||
<hat-graph
|
||||
branching
|
||||
@ -218,7 +220,7 @@ class HatScriptGraph extends LitElement {
|
||||
<hat-graph-node
|
||||
slot="head"
|
||||
class=${classMap({
|
||||
track: trace,
|
||||
track: Boolean(trace),
|
||||
})}
|
||||
.iconPath=${mdiAbTesting}
|
||||
nofocus
|
||||
@ -411,16 +413,14 @@ class HatScriptGraph extends LitElement {
|
||||
|
||||
const manual_triggered = this.trace && "trigger" in this.trace.trace;
|
||||
let track_path = manual_triggered ? undefined : [0];
|
||||
const trigger_nodes = (Array.isArray(this.trace.config.trigger)
|
||||
? this.trace.config.trigger
|
||||
: [this.trace.config.trigger]
|
||||
).map((trigger, i) => {
|
||||
if (this.trace && `trigger/${i}` in this.trace.trace) {
|
||||
track_path = [i];
|
||||
const trigger_nodes = ensureArray(this.trace.config.trigger).map(
|
||||
(trigger, 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);
|
||||
});
|
||||
|
||||
);
|
||||
return html`
|
||||
<hat-graph class="parent">
|
||||
<div></div>
|
||||
@ -434,16 +434,13 @@ class HatScriptGraph extends LitElement {
|
||||
${trigger_nodes}
|
||||
</hat-graph>
|
||||
<hat-graph id="condition">
|
||||
${(!this.trace.config.condition ||
|
||||
Array.isArray(this.trace.config.condition)
|
||||
? this.trace.config.condition
|
||||
: [this.trace.config.condition]
|
||||
)?.map((condition, i) => this.render_condition(condition, i))}
|
||||
${ensureArray(this.trace.config.condition)?.map((condition, i) =>
|
||||
this.render_condition(condition!, i)
|
||||
)}
|
||||
</hat-graph>
|
||||
${(Array.isArray(this.trace.config.action)
|
||||
? this.trace.config.action
|
||||
: [this.trace.config.action]
|
||||
).map((action, i) => this.render_node(action, `action/${i}`))}
|
||||
${ensureArray(this.trace.config.action).map((action, i) =>
|
||||
this.render_node(action, `action/${i}`)
|
||||
)}
|
||||
</hat-graph>
|
||||
<div class="actions">
|
||||
<mwc-icon-button
|
||||
|
@ -20,9 +20,11 @@ import { HomeAssistant } from "../../types";
|
||||
import "./ha-timeline";
|
||||
import type { HaTimeline } from "./ha-timeline";
|
||||
import {
|
||||
mdiAlertCircle,
|
||||
mdiCircle,
|
||||
mdiCircleOutline,
|
||||
mdiPauseCircleOutline,
|
||||
mdiProgressClock,
|
||||
mdiProgressWrench,
|
||||
mdiRecordCircleOutline,
|
||||
} from "@mdi/js";
|
||||
import { LogbookEntry } from "../../data/logbook";
|
||||
@ -33,6 +35,8 @@ import {
|
||||
} from "../../data/script";
|
||||
import relativeTime from "../../common/datetime/relative_time";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { describeAction } from "../../data/script_i18n";
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
|
||||
const LOGBOOK_ENTRIES_BEFORE_FOLD = 2;
|
||||
|
||||
@ -262,7 +266,7 @@ class ActionRenderer {
|
||||
return this._handleChoose(index);
|
||||
}
|
||||
|
||||
this._renderEntry(path, data.alias || actionType);
|
||||
this._renderEntry(path, describeAction(this.hass, data, actionType));
|
||||
return index + 1;
|
||||
}
|
||||
|
||||
@ -272,7 +276,7 @@ class ActionRenderer {
|
||||
`Triggered ${
|
||||
triggerStep.path === "trigger"
|
||||
? "manually"
|
||||
: `by the ${triggerStep.changed_variables.trigger.description}`
|
||||
: `by the ${this.trace.trigger}`
|
||||
} at
|
||||
${formatDateTimeWithSeconds(
|
||||
new Date(triggerStep.timestamp),
|
||||
@ -302,7 +306,7 @@ class ActionRenderer {
|
||||
const startLevel = choosePath.split("/").length - 1;
|
||||
|
||||
const chooseTrace = this._getItem(index)[0] as ChooseActionTraceStep;
|
||||
const defaultExecuted = chooseTrace.result.choice === "default";
|
||||
const defaultExecuted = chooseTrace.result?.choice === "default";
|
||||
const chooseConfig = this._getDataFromPath(
|
||||
this.keys[index]
|
||||
) as ChooseAction;
|
||||
@ -312,11 +316,14 @@ class ActionRenderer {
|
||||
this._renderEntry(choosePath, `${name}: Default action executed`);
|
||||
} else {
|
||||
const choiceConfig = this._getDataFromPath(
|
||||
`${this.keys[index]}/choose/${chooseTrace.result.choice}`
|
||||
) as ChooseActionChoice;
|
||||
const choiceName =
|
||||
choiceConfig.alias || `Choice ${chooseTrace.result.choice}`;
|
||||
this._renderEntry(choosePath, `${name}: ${choiceName} executed`);
|
||||
`${this.keys[index]}/choose/${chooseTrace.result?.choice}`
|
||||
) as ChooseActionChoice | undefined;
|
||||
const choiceName = choiceConfig
|
||||
? `${
|
||||
choiceConfig.alias || `Choice ${chooseTrace.result?.choice}`
|
||||
} executed`
|
||||
: `Error: ${chooseTrace.error}`;
|
||||
this._renderEntry(choosePath, `${name}: ${choiceName}`);
|
||||
}
|
||||
|
||||
let i;
|
||||
@ -331,7 +338,10 @@ class ActionRenderer {
|
||||
}
|
||||
|
||||
// We're going to skip all conditions
|
||||
if (parts[startLevel + 3] === "sequence") {
|
||||
if (
|
||||
(defaultExecuted && parts[startLevel + 1] === "default") ||
|
||||
(!defaultExecuted && parts[startLevel + 3] === "sequence")
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -414,29 +424,92 @@ export class HaAutomationTracer extends LitElement {
|
||||
|
||||
logbookRenderer.flush();
|
||||
|
||||
// Render footer
|
||||
const renderFinishedAt = () =>
|
||||
formatDateTimeWithSeconds(
|
||||
new Date(this.trace!.timestamp.finish!),
|
||||
this.hass.locale
|
||||
);
|
||||
const renderRuntime = () => `(runtime:
|
||||
${(
|
||||
(new Date(this.trace!.timestamp.finish!).getTime() -
|
||||
new Date(this.trace!.timestamp.start).getTime()) /
|
||||
1000
|
||||
).toFixed(2)}
|
||||
seconds)`;
|
||||
|
||||
let entry: {
|
||||
description: TemplateResult | string;
|
||||
icon: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
if (this.trace.state === "running") {
|
||||
entry = {
|
||||
description: "Still running",
|
||||
icon: mdiProgressClock,
|
||||
};
|
||||
} else if (this.trace.state === "debugged") {
|
||||
entry = {
|
||||
description: "Debugged",
|
||||
icon: mdiProgressWrench,
|
||||
};
|
||||
} else if (this.trace.script_execution === "finished") {
|
||||
entry = {
|
||||
description: `Finished at ${renderFinishedAt()} ${renderRuntime()}`,
|
||||
icon: mdiCircle,
|
||||
};
|
||||
} else if (this.trace.script_execution === "aborted") {
|
||||
entry = {
|
||||
description: `Aborted at ${renderFinishedAt()} ${renderRuntime()}`,
|
||||
icon: mdiAlertCircle,
|
||||
};
|
||||
} else if (this.trace.script_execution === "cancelled") {
|
||||
entry = {
|
||||
description: `Cancelled at ${renderFinishedAt()} ${renderRuntime()}`,
|
||||
icon: mdiAlertCircle,
|
||||
};
|
||||
} else {
|
||||
let reason: string;
|
||||
let isError = false;
|
||||
let extra: TemplateResult | undefined;
|
||||
|
||||
switch (this.trace.script_execution) {
|
||||
case "failed_condition":
|
||||
reason = "a condition failed";
|
||||
break;
|
||||
case "failed_single":
|
||||
reason = "only a single execution is allowed";
|
||||
break;
|
||||
case "failed_max_runs":
|
||||
reason = "maximum number of parallel runs reached";
|
||||
break;
|
||||
case "error":
|
||||
reason = "an error was encountered";
|
||||
isError = true;
|
||||
extra = html`<br /><br />${this.trace.error!}`;
|
||||
break;
|
||||
default:
|
||||
reason = `of unknown reason "${this.trace.script_execution}"`;
|
||||
isError = true;
|
||||
}
|
||||
|
||||
entry = {
|
||||
description: html`Stopped because ${reason} at ${renderFinishedAt()}
|
||||
${renderRuntime()}${extra || ""}`,
|
||||
icon: mdiAlertCircle,
|
||||
className: isError ? "error" : undefined,
|
||||
};
|
||||
}
|
||||
// null means it was stopped by a condition
|
||||
if (this.trace.last_action !== null) {
|
||||
if (entry) {
|
||||
entries.push(html`
|
||||
<ha-timeline
|
||||
lastItem
|
||||
.icon=${this.trace.timestamp.finish
|
||||
? mdiCircle
|
||||
: mdiPauseCircleOutline}
|
||||
.icon=${entry.icon}
|
||||
class=${ifDefined(entry.className)}
|
||||
>
|
||||
${this.trace.timestamp.finish
|
||||
? html`Finished at
|
||||
${formatDateTimeWithSeconds(
|
||||
new Date(this.trace.timestamp.finish),
|
||||
this.hass.locale
|
||||
)}
|
||||
(runtime:
|
||||
${(
|
||||
(new Date(this.trace.timestamp.finish!).getTime() -
|
||||
new Date(this.trace.timestamp.start).getTime()) /
|
||||
1000
|
||||
).toFixed(2)}
|
||||
seconds)`
|
||||
: "Still running"}
|
||||
${entry.description}
|
||||
</ha-timeline>
|
||||
`);
|
||||
}
|
||||
@ -468,17 +541,20 @@ export class HaAutomationTracer extends LitElement {
|
||||
this.shadowRoot!.querySelectorAll<HaTimeline>(
|
||||
"ha-timeline[data-path]"
|
||||
).forEach((el) => {
|
||||
el.style.setProperty(
|
||||
"--timeline-ball-color",
|
||||
this.selectedPath === el.dataset.path ? "var(--primary-color)" : null
|
||||
);
|
||||
if (!this.allowPick || el.dataset.upgraded) {
|
||||
el.toggleAttribute("selected", this.selectedPath === el.dataset.path);
|
||||
if (!this.allowPick || el.tabIndex === 0) {
|
||||
return;
|
||||
}
|
||||
el.dataset.upgraded = "1";
|
||||
el.addEventListener("click", () => {
|
||||
el.tabIndex = 0;
|
||||
const selectEl = () => {
|
||||
this.selectedPath = el.dataset.path;
|
||||
fireEvent(this, "value-changed", { value: el.dataset.path });
|
||||
};
|
||||
el.addEventListener("click", selectEl);
|
||||
el.addEventListener("keydown", (ev: KeyboardEvent) => {
|
||||
if (ev.key === "Enter" || ev.key === " ") {
|
||||
selectEl();
|
||||
}
|
||||
});
|
||||
el.addEventListener("mouseover", () => {
|
||||
el.raised = true;
|
||||
@ -499,6 +575,17 @@ export class HaAutomationTracer extends LitElement {
|
||||
ha-timeline[data-path] {
|
||||
cursor: pointer;
|
||||
}
|
||||
ha-timeline[selected] {
|
||||
--timeline-ball-color: var(--primary-color);
|
||||
}
|
||||
ha-timeline:focus {
|
||||
outline: none;
|
||||
--timeline-ball-color: var(--accent-color);
|
||||
}
|
||||
.error {
|
||||
--timeline-ball-color: var(--error-color);
|
||||
color: var(--error-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -238,6 +238,9 @@ export const deleteAutomation = (hass: HomeAssistant, id: string) =>
|
||||
|
||||
let inititialAutomationEditorData: Partial<AutomationConfig> | undefined;
|
||||
|
||||
export const getAutomationConfig = (hass: HomeAssistant, id: string) =>
|
||||
hass.callApi<AutomationConfig>("GET", `config/automation/config/${id}`);
|
||||
|
||||
export const showAutomationEditor = (
|
||||
el: HTMLElement,
|
||||
data?: Partial<AutomationConfig>
|
||||
|
15
src/data/automation_i18n.ts
Normal file
15
src/data/automation_i18n.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { Trigger, Condition } from "./automation";
|
||||
|
||||
export const describeTrigger = (trigger: Trigger) => {
|
||||
return `${trigger.platform} trigger`;
|
||||
};
|
||||
|
||||
export const describeCondition = (condition: Condition) => {
|
||||
if (condition.alias) {
|
||||
return condition.alias;
|
||||
}
|
||||
if (condition.condition === "template") {
|
||||
return "Test a template";
|
||||
}
|
||||
return `${condition.condition} condition`;
|
||||
};
|
@ -45,7 +45,7 @@ export interface DataEntryFlowStepCreateEntry {
|
||||
flow_id: string;
|
||||
handler: string;
|
||||
title: string;
|
||||
result: ConfigEntry;
|
||||
result?: ConfigEntry;
|
||||
description: string;
|
||||
description_placeholders: Record<string, string>;
|
||||
}
|
||||
|
@ -37,7 +37,8 @@ export interface EventAction {
|
||||
|
||||
export interface ServiceAction {
|
||||
alias?: string;
|
||||
service: string;
|
||||
service?: string;
|
||||
service_template?: string;
|
||||
entity_id?: string;
|
||||
target?: HassServiceTarget;
|
||||
data?: Record<string, any>;
|
||||
@ -76,7 +77,7 @@ export interface WaitAction {
|
||||
|
||||
export interface WaitForTriggerAction {
|
||||
alias?: string;
|
||||
wait_for_trigger: Trigger[];
|
||||
wait_for_trigger: Trigger | Trigger[];
|
||||
timeout?: number;
|
||||
continue_on_timeout?: boolean;
|
||||
}
|
||||
@ -115,6 +116,16 @@ export interface ChooseAction {
|
||||
default?: Action[];
|
||||
}
|
||||
|
||||
export interface VariablesAction {
|
||||
alias?: string;
|
||||
variables: Record<string, unknown>;
|
||||
}
|
||||
|
||||
interface UnknownAction {
|
||||
alias?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export type Action =
|
||||
| EventAction
|
||||
| DeviceAction
|
||||
@ -125,7 +136,26 @@ export type Action =
|
||||
| WaitAction
|
||||
| WaitForTriggerAction
|
||||
| RepeatAction
|
||||
| ChooseAction;
|
||||
| ChooseAction
|
||||
| VariablesAction
|
||||
| UnknownAction;
|
||||
|
||||
export interface ActionTypes {
|
||||
delay: DelayAction;
|
||||
wait_template: WaitAction;
|
||||
check_condition: Condition;
|
||||
fire_event: EventAction;
|
||||
device_action: DeviceAction;
|
||||
activate_scene: SceneAction;
|
||||
repeat: RepeatAction;
|
||||
choose: ChooseAction;
|
||||
wait_for_trigger: WaitForTriggerAction;
|
||||
variables: VariablesAction;
|
||||
service: ServiceAction;
|
||||
unknown: UnknownAction;
|
||||
}
|
||||
|
||||
export type ActionType = keyof ActionTypes;
|
||||
|
||||
export const triggerScript = (
|
||||
hass: HomeAssistant,
|
||||
@ -166,7 +196,7 @@ export const getScriptEditorInitData = () => {
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getActionType = (action: Action) => {
|
||||
export const getActionType = (action: Action): ActionType => {
|
||||
// Check based on config_validation.py#determine_script_action
|
||||
if ("delay" in action) {
|
||||
return "delay";
|
||||
|
142
src/data/script_i18n.ts
Normal file
142
src/data/script_i18n.ts
Normal file
@ -0,0 +1,142 @@
|
||||
import secondsToDuration from "../common/datetime/seconds_to_duration";
|
||||
import { ensureArray } from "../common/ensure-array";
|
||||
import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import { isTemplate } from "../common/string/has-template";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { Condition } from "./automation";
|
||||
import { describeCondition, describeTrigger } from "./automation_i18n";
|
||||
import {
|
||||
ActionType,
|
||||
getActionType,
|
||||
DelayAction,
|
||||
SceneAction,
|
||||
WaitForTriggerAction,
|
||||
ActionTypes,
|
||||
VariablesAction,
|
||||
EventAction,
|
||||
} from "./script";
|
||||
|
||||
export const describeAction = <T extends ActionType>(
|
||||
hass: HomeAssistant,
|
||||
action: ActionTypes[T],
|
||||
actionType?: T
|
||||
): string => {
|
||||
if (action.alias) {
|
||||
return action.alias;
|
||||
}
|
||||
if (!actionType) {
|
||||
actionType = getActionType(action) as T;
|
||||
}
|
||||
|
||||
if (actionType === "service") {
|
||||
const config = action as ActionTypes["service"];
|
||||
|
||||
let base: string | undefined;
|
||||
|
||||
if (
|
||||
config.service_template ||
|
||||
(config.service && isTemplate(config.service))
|
||||
) {
|
||||
base = "Call a service based on a template";
|
||||
} else if (config.service) {
|
||||
base = `Call service ${config.service}`;
|
||||
} else {
|
||||
return actionType;
|
||||
}
|
||||
if (config.target) {
|
||||
const targets: string[] = [];
|
||||
|
||||
for (const [key, label] of Object.entries({
|
||||
area_id: "areas",
|
||||
device_id: "devices",
|
||||
entity_id: "entities",
|
||||
})) {
|
||||
if (!(key in config.target)) {
|
||||
continue;
|
||||
}
|
||||
const keyConf: string[] = Array.isArray(config.target[key])
|
||||
? config.target[key]
|
||||
: [config.target[key]];
|
||||
|
||||
const values: string[] = [];
|
||||
|
||||
let renderValues = true;
|
||||
|
||||
for (const targetThing of keyConf) {
|
||||
if (isTemplate(targetThing)) {
|
||||
targets.push(`templated ${label}`);
|
||||
renderValues = false;
|
||||
break;
|
||||
} else {
|
||||
values.push(targetThing);
|
||||
}
|
||||
}
|
||||
|
||||
if (renderValues) {
|
||||
targets.push(`${label} ${values.join(", ")}`);
|
||||
}
|
||||
}
|
||||
if (targets.length > 0) {
|
||||
base += ` on ${targets.join(", ")}`;
|
||||
}
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
if (actionType === "delay") {
|
||||
const config = action as DelayAction;
|
||||
|
||||
let duration: string;
|
||||
|
||||
if (typeof config.delay === "number") {
|
||||
duration = `for ${secondsToDuration(config.delay)!}`;
|
||||
} else if (typeof config.delay === "string") {
|
||||
duration = isTemplate(config.delay)
|
||||
? "based on a template"
|
||||
: `for ${config.delay}`;
|
||||
} else {
|
||||
duration = `for ${JSON.stringify(config.delay)}`;
|
||||
}
|
||||
|
||||
return `Delay ${duration}`;
|
||||
}
|
||||
|
||||
if (actionType === "activate_scene") {
|
||||
const config = action as SceneAction;
|
||||
const sceneStateObj = hass.states[config.scene];
|
||||
return `Activate scene ${
|
||||
sceneStateObj ? computeStateName(sceneStateObj) : config.scene
|
||||
}`;
|
||||
}
|
||||
|
||||
if (actionType === "wait_for_trigger") {
|
||||
const config = action as WaitForTriggerAction;
|
||||
return `Wait for ${ensureArray(config.wait_for_trigger)
|
||||
.map((trigger) => describeTrigger(trigger))
|
||||
.join(", ")}`;
|
||||
}
|
||||
|
||||
if (actionType === "variables") {
|
||||
const config = action as VariablesAction;
|
||||
return `Define variables ${Object.keys(config.variables).join(", ")}`;
|
||||
}
|
||||
|
||||
if (actionType === "fire_event") {
|
||||
const config = action as EventAction;
|
||||
if (isTemplate(config.event)) {
|
||||
return "Fire event based on a template";
|
||||
}
|
||||
return `Fire event ${config.event}`;
|
||||
}
|
||||
|
||||
if (actionType === "wait_template") {
|
||||
return "Wait for a template to render true";
|
||||
}
|
||||
|
||||
if (actionType === "check_condition") {
|
||||
return `Test ${describeCondition(action as Condition)}`;
|
||||
}
|
||||
|
||||
return actionType;
|
||||
};
|
@ -1,10 +1,14 @@
|
||||
import { strStartsWith } from "../common/string/starts-with";
|
||||
import { HomeAssistant, Context } from "../types";
|
||||
import { AutomationConfig } from "./automation";
|
||||
import {
|
||||
BlueprintAutomationConfig,
|
||||
ManualAutomationConfig,
|
||||
} from "./automation";
|
||||
|
||||
interface BaseTraceStep {
|
||||
path: string;
|
||||
timestamp: string;
|
||||
error?: string;
|
||||
changed_variables?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
@ -19,11 +23,11 @@ export interface TriggerTraceStep extends BaseTraceStep {
|
||||
}
|
||||
|
||||
export interface ConditionTraceStep extends BaseTraceStep {
|
||||
result: { result: boolean };
|
||||
result?: { result: boolean };
|
||||
}
|
||||
|
||||
export interface CallServiceActionTraceStep extends BaseTraceStep {
|
||||
result: {
|
||||
result?: {
|
||||
limit: number;
|
||||
running_script: boolean;
|
||||
params: Record<string, unknown>;
|
||||
@ -36,11 +40,11 @@ export interface CallServiceActionTraceStep extends BaseTraceStep {
|
||||
}
|
||||
|
||||
export interface ChooseActionTraceStep extends BaseTraceStep {
|
||||
result: { choice: number | "default" };
|
||||
result?: { choice: number | "default" };
|
||||
}
|
||||
|
||||
export interface ChooseChoiceActionTraceStep extends BaseTraceStep {
|
||||
result: { result: boolean };
|
||||
result?: { result: boolean };
|
||||
}
|
||||
|
||||
export type ActionTraceStep =
|
||||
@ -53,22 +57,41 @@ export type ActionTraceStep =
|
||||
export interface AutomationTrace {
|
||||
domain: string;
|
||||
item_id: string;
|
||||
last_action: string | null;
|
||||
last_condition: string | null;
|
||||
last_step: string | null;
|
||||
run_id: string;
|
||||
state: "running" | "stopped" | "debugged";
|
||||
timestamp: {
|
||||
start: string;
|
||||
finish: string | null;
|
||||
};
|
||||
trigger: unknown;
|
||||
script_execution:
|
||||
| // The script was not executed because the automation's condition failed
|
||||
"failed_condition"
|
||||
// The script was not executed because the run mode is single
|
||||
| "failed_single"
|
||||
// The script was not executed because max parallel runs would be exceeded
|
||||
| "failed_max_runs"
|
||||
// All script steps finished:
|
||||
| "finished"
|
||||
// Script execution stopped by the script itself because a condition fails, wait_for_trigger timeouts etc:
|
||||
| "aborted"
|
||||
// Details about failing condition, timeout etc. is in the last element of the trace
|
||||
// Script execution stops because of an unexpected exception:
|
||||
| "error"
|
||||
// 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:
|
||||
| "cancelled"
|
||||
| string;
|
||||
// Automation only, should become it's own type when we support script in frontend
|
||||
trigger: string;
|
||||
}
|
||||
|
||||
export interface AutomationTraceExtended extends AutomationTrace {
|
||||
trace: Record<string, ActionTraceStep[]>;
|
||||
context: Context;
|
||||
variables: Record<string, unknown>;
|
||||
config: AutomationConfig;
|
||||
config: ManualAutomationConfig;
|
||||
blueprint_inputs?: BlueprintAutomationConfig;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
interface TraceTypes {
|
||||
@ -119,7 +142,7 @@ export const loadTraceContexts = (
|
||||
});
|
||||
|
||||
export const getDataFromPath = (
|
||||
config: AutomationConfig,
|
||||
config: ManualAutomationConfig,
|
||||
path: string
|
||||
): any => {
|
||||
const parts = path.split("/").reverse();
|
||||
|
@ -314,7 +314,7 @@ class DataEntryFlowDialog extends LitElement {
|
||||
this._step &&
|
||||
this._step.type === "create_entry"
|
||||
) {
|
||||
if (this._params!.flowConfig.loadDevicesAndAreas) {
|
||||
if (this._step.result && this._params!.flowConfig.loadDevicesAndAreas) {
|
||||
this._fetchDevices(this._step.result.entry_id);
|
||||
this._fetchAreas();
|
||||
} else {
|
||||
|
@ -43,7 +43,7 @@ class StepFlowCreateEntry extends LitElement {
|
||||
<h2>Success!</h2>
|
||||
<div class="content">
|
||||
${this.flowConfig.renderCreateEntryDescription(this.hass, this.step)}
|
||||
${this.step.result.state === "not_loaded"
|
||||
${this.step.result?.state === "not_loaded"
|
||||
? html`<span class="error"
|
||||
>${localize(
|
||||
"ui.panel.config.integrations.config_flow.not_loaded"
|
||||
|
@ -36,6 +36,7 @@ import {
|
||||
AutomationConfig,
|
||||
AutomationEntity,
|
||||
deleteAutomation,
|
||||
getAutomationConfig,
|
||||
getAutomationEditorInitData,
|
||||
showAutomationEditor,
|
||||
triggerAutomationActions,
|
||||
@ -303,39 +304,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
oldAutomationId !== this.automationId
|
||||
) {
|
||||
this._setEntityId();
|
||||
this.hass
|
||||
.callApi<AutomationConfig>(
|
||||
"GET",
|
||||
`config/automation/config/${this.automationId}`
|
||||
)
|
||||
.then(
|
||||
(config) => {
|
||||
// Normalize data: ensure trigger, action and condition are lists
|
||||
// Happens when people copy paste their automations into the config
|
||||
for (const key of ["trigger", "condition", "action"]) {
|
||||
const value = config[key];
|
||||
if (value && !Array.isArray(value)) {
|
||||
config[key] = [value];
|
||||
}
|
||||
}
|
||||
this._dirty = false;
|
||||
this._config = config;
|
||||
},
|
||||
(resp) => {
|
||||
showAlertDialog(this, {
|
||||
text:
|
||||
resp.status_code === 404
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.automation.editor.load_error_not_editable"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.load_error_unknown",
|
||||
"err_no",
|
||||
resp.status_code
|
||||
),
|
||||
}).then(() => history.back());
|
||||
}
|
||||
);
|
||||
this._loadConfig();
|
||||
}
|
||||
|
||||
if (changedProps.has("automationId") && !this.automationId && this.hass) {
|
||||
@ -378,6 +347,36 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
this._entityId = automation?.entity_id;
|
||||
}
|
||||
|
||||
private async _loadConfig() {
|
||||
try {
|
||||
const config = await getAutomationConfig(this.hass, this.automationId);
|
||||
|
||||
// Normalize data: ensure trigger, action and condition are lists
|
||||
// Happens when people copy paste their automations into the config
|
||||
for (const key of ["trigger", "condition", "action"]) {
|
||||
const value = config[key];
|
||||
if (value && !Array.isArray(value)) {
|
||||
config[key] = [value];
|
||||
}
|
||||
}
|
||||
this._dirty = false;
|
||||
this._config = config;
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
text:
|
||||
err.status_code === 404
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.automation.editor.load_error_not_editable"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.load_error_unknown",
|
||||
"err_no",
|
||||
err.status_code
|
||||
),
|
||||
}).then(() => history.back());
|
||||
}
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent<{ value: AutomationConfig }>) {
|
||||
ev.stopPropagation();
|
||||
this._config = ev.detail.value;
|
||||
|
@ -1,5 +1,12 @@
|
||||
import "@material/mwc-icon-button";
|
||||
import { mdiHelpCircle, mdiPlus } from "@mdi/js";
|
||||
import {
|
||||
mdiHelpCircle,
|
||||
mdiHistory,
|
||||
mdiInformationOutline,
|
||||
mdiPencil,
|
||||
mdiPencilOff,
|
||||
mdiPlus,
|
||||
} from "@mdi/js";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import {
|
||||
CSSResult,
|
||||
@ -70,6 +77,7 @@ class HaAutomationPicker extends LitElement {
|
||||
return {
|
||||
...automation,
|
||||
name: computeStateName(automation),
|
||||
last_triggered: automation.attributes.last_triggered || undefined,
|
||||
};
|
||||
});
|
||||
}
|
||||
@ -97,23 +105,41 @@ class HaAutomationPicker extends LitElement {
|
||||
filterable: true,
|
||||
direction: "asc",
|
||||
grows: true,
|
||||
template: (name, automation: any) => html`
|
||||
${name}
|
||||
<div class="secondary">
|
||||
${this.hass.localize("ui.card.automation.last_triggered")}:
|
||||
${automation.attributes.last_triggered
|
||||
? formatDateTime(
|
||||
new Date(automation.attributes.last_triggered),
|
||||
this.hass.locale
|
||||
)
|
||||
: this.hass.localize("ui.components.relative_time.never")}
|
||||
</div>
|
||||
`,
|
||||
template: narrow
|
||||
? (name, automation: any) =>
|
||||
html`
|
||||
${name}
|
||||
<div class="secondary">
|
||||
${this.hass.localize("ui.card.automation.last_triggered")}:
|
||||
${automation.attributes.last_triggered
|
||||
? formatDateTime(
|
||||
new Date(automation.attributes.last_triggered),
|
||||
this.hass.locale
|
||||
)
|
||||
: this.hass.localize("ui.components.relative_time.never")}
|
||||
</div>
|
||||
`
|
||||
: undefined,
|
||||
},
|
||||
};
|
||||
if (!narrow) {
|
||||
columns.last_triggered = {
|
||||
sortable: true,
|
||||
width: "20%",
|
||||
title: this.hass.localize("ui.card.automation.last_triggered"),
|
||||
template: (last_triggered) => html`
|
||||
${last_triggered
|
||||
? formatDateTime(new Date(last_triggered), this.hass.locale)
|
||||
: this.hass.localize("ui.components.relative_time.never")}
|
||||
`,
|
||||
};
|
||||
columns.trigger = {
|
||||
title: "",
|
||||
title: html`
|
||||
<mwc-button style="visibility: hidden">
|
||||
${this.hass.localize("ui.card.automation.trigger")}
|
||||
</mwc-button>
|
||||
`,
|
||||
width: "20%",
|
||||
template: (_info, automation: any) => html`
|
||||
<mwc-button
|
||||
.automation=${automation}
|
||||
@ -129,14 +155,15 @@ class HaAutomationPicker extends LitElement {
|
||||
title: "",
|
||||
type: "icon-button",
|
||||
template: (_info, automation) => html`
|
||||
<ha-icon-button
|
||||
<mwc-icon-button
|
||||
.automation=${automation}
|
||||
@click=${this._showInfo}
|
||||
icon="hass:information-outline"
|
||||
title="${this.hass.localize(
|
||||
.label="${this.hass.localize(
|
||||
"ui.panel.config.automation.picker.show_info_automation"
|
||||
)}"
|
||||
></ha-icon-button>
|
||||
>
|
||||
<ha-svg-icon .path=${mdiInformationOutline}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
`,
|
||||
};
|
||||
columns.trace = {
|
||||
@ -150,13 +177,14 @@ class HaAutomationPicker extends LitElement {
|
||||
: undefined
|
||||
)}
|
||||
>
|
||||
<ha-icon-button
|
||||
icon="hass:graph-outline"
|
||||
.disabled=${!automation.attributes.id}
|
||||
title="${this.hass.localize(
|
||||
<mwc-icon-button
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.picker.dev_automation"
|
||||
)}"
|
||||
></ha-icon-button>
|
||||
)}
|
||||
.disabled=${!automation.attributes.id}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiHistory}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
</a>
|
||||
${!automation.attributes.id
|
||||
? html`
|
||||
@ -180,25 +208,26 @@ class HaAutomationPicker extends LitElement {
|
||||
: undefined
|
||||
)}
|
||||
>
|
||||
<ha-icon-button
|
||||
.icon=${automation.attributes.id
|
||||
? "hass:pencil"
|
||||
: "hass:pencil-off"}
|
||||
<mwc-icon-button
|
||||
.disabled=${!automation.attributes.id}
|
||||
title="${this.hass.localize(
|
||||
.label="${this.hass.localize(
|
||||
"ui.panel.config.automation.picker.edit_automation"
|
||||
)}"
|
||||
></ha-icon-button>
|
||||
><ha-svg-icon .path=${
|
||||
automation.attributes.id ? mdiPencil : mdiPencilOff
|
||||
}></ha-svg-icon>
|
||||
</a>
|
||||
${!automation.attributes.id
|
||||
? html`
|
||||
<paper-tooltip animation-delay="0" position="left">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.picker.only_editable"
|
||||
)}
|
||||
</paper-tooltip>
|
||||
`
|
||||
: ""}
|
||||
${
|
||||
!automation.attributes.id
|
||||
? html`
|
||||
<paper-tooltip animation-delay="0" position="left">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.picker.only_editable"
|
||||
)}
|
||||
</paper-tooltip>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
`,
|
||||
};
|
||||
return columns;
|
||||
|
@ -0,0 +1,34 @@
|
||||
import { safeDump } from "js-yaml";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-code-editor";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { AutomationTraceExtended } from "../../../../data/trace";
|
||||
|
||||
@customElement("ha-automation-trace-blueprint-config")
|
||||
export class HaAutomationTraceBlueprintConfig extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public trace!: AutomationTraceExtended;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-code-editor
|
||||
.value=${safeDump(this.trace.blueprint_inputs || "").trimRight()}
|
||||
readOnly
|
||||
></ha-code-editor>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-automation-trace-blueprint-config": HaAutomationTraceBlueprintConfig;
|
||||
}
|
||||
}
|
@ -57,13 +57,13 @@ export class HaAutomationTracePathDetails extends LitElement {
|
||||
["logbook", "Related logbook entries"],
|
||||
].map(
|
||||
([view, label]) => html`
|
||||
<div
|
||||
<button
|
||||
.view=${view}
|
||||
class=${classMap({ active: this._view === view })}
|
||||
@click=${this._showTab}
|
||||
>
|
||||
${label}
|
||||
</div>
|
||||
</button>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
|
@ -39,6 +39,7 @@ import {
|
||||
mdiRefresh,
|
||||
mdiDownload,
|
||||
} from "@mdi/js";
|
||||
import "./ha-automation-trace-blueprint-config";
|
||||
|
||||
@customElement("ha-automation-trace")
|
||||
export class HaAutomationTrace extends LitElement {
|
||||
@ -66,8 +67,12 @@ export class HaAutomationTrace extends LitElement {
|
||||
|
||||
@internalProperty() private _logbookEntries?: LogbookEntry[];
|
||||
|
||||
@internalProperty() private _view: "details" | "config" | "timeline" =
|
||||
"details";
|
||||
@internalProperty() private _view:
|
||||
| "details"
|
||||
| "config"
|
||||
| "timeline"
|
||||
| "logbook"
|
||||
| "blueprint" = "details";
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const stateObj = this._entityId
|
||||
@ -117,7 +122,7 @@ export class HaAutomationTrace extends LitElement {
|
||||
class="linkButton"
|
||||
href="/config/automation/edit/${this.automationId}"
|
||||
>
|
||||
<mwc-icon-button label="Edit Automation">
|
||||
<mwc-icon-button label="Edit Automation" tabindex="-1">
|
||||
<ha-svg-icon .path=${mdiPencil}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
</a>
|
||||
@ -181,18 +186,34 @@ export class HaAutomationTrace extends LitElement {
|
||||
${[
|
||||
["details", "Step Details"],
|
||||
["timeline", "Trace Timeline"],
|
||||
["logbook", "Related logbook entries"],
|
||||
["config", "Automation Config"],
|
||||
].map(
|
||||
([view, label]) => html`
|
||||
<div
|
||||
<button
|
||||
tabindex="0"
|
||||
.view=${view}
|
||||
class=${classMap({ active: this._view === view })}
|
||||
@click=${this._showTab}
|
||||
>
|
||||
${label}
|
||||
</div>
|
||||
</button>
|
||||
`
|
||||
)}
|
||||
${this._trace.blueprint_inputs
|
||||
? html`
|
||||
<button
|
||||
tabindex="0"
|
||||
.view=${"blueprint"}
|
||||
class=${classMap({
|
||||
active: this._view === "blueprint",
|
||||
})}
|
||||
@click=${this._showTab}
|
||||
>
|
||||
Blueprint Config
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
${this._selected === undefined ||
|
||||
this._logbookEntries === undefined ||
|
||||
@ -216,6 +237,20 @@ export class HaAutomationTrace extends LitElement {
|
||||
.trace=${this._trace}
|
||||
></ha-automation-trace-config>
|
||||
`
|
||||
: this._view === "logbook"
|
||||
? html`
|
||||
<ha-logbook
|
||||
.hass=${this.hass}
|
||||
.entries=${this._logbookEntries}
|
||||
></ha-logbook>
|
||||
`
|
||||
: this._view === "blueprint"
|
||||
? html`
|
||||
<ha-automation-trace-blueprint-config
|
||||
.hass=${this.hass}
|
||||
.trace=${this._trace}
|
||||
></ha-automation-trace-blueprint-config>
|
||||
`
|
||||
: html`
|
||||
<ha-automation-trace-timeline
|
||||
.hass=${this.hass}
|
||||
|
@ -18,8 +18,10 @@ export const traceTabStyles = css`
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
bottom: -1px;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
user-select: none;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.tabs > *.active {
|
||||
|
@ -66,14 +66,15 @@ class HaScriptPicker extends LitElement {
|
||||
...script,
|
||||
name: computeStateName(script),
|
||||
icon: stateIcon(script),
|
||||
last_triggered: script.attributes.last_triggered || undefined,
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
private _columns = memoizeOne(
|
||||
(_language): DataTableColumnContainer => {
|
||||
return {
|
||||
(narrow, _locale): DataTableColumnContainer => {
|
||||
const columns: DataTableColumnContainer = {
|
||||
activate: {
|
||||
title: "",
|
||||
type: "icon-button",
|
||||
@ -103,50 +104,65 @@ class HaScriptPicker extends LitElement {
|
||||
filterable: true,
|
||||
direction: "asc",
|
||||
grows: true,
|
||||
template: (name, script: any) => html`
|
||||
${name}
|
||||
<div class="secondary">
|
||||
${this.hass.localize("ui.card.automation.last_triggered")}:
|
||||
${script.attributes.last_triggered
|
||||
? formatDateTime(
|
||||
new Date(script.attributes.last_triggered),
|
||||
this.hass.locale
|
||||
)
|
||||
: this.hass.localize("ui.components.relative_time.never")}
|
||||
</div>
|
||||
`,
|
||||
},
|
||||
info: {
|
||||
title: "",
|
||||
type: "icon-button",
|
||||
template: (_info, script) => html`
|
||||
<mwc-icon-button
|
||||
.script=${script}
|
||||
@click=${this._showInfo}
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.script.picker.show_info"
|
||||
)}"
|
||||
>
|
||||
<ha-svg-icon .path=${mdiInformationOutline}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
`,
|
||||
},
|
||||
edit: {
|
||||
title: "",
|
||||
type: "icon-button",
|
||||
template: (_info, script: any) => html`
|
||||
<a href="/config/script/edit/${script.entity_id}">
|
||||
<mwc-icon-button
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.script.picker.edit_script"
|
||||
)}"
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPencil}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
</a>
|
||||
`,
|
||||
template: narrow
|
||||
? (name, script: any) => html`
|
||||
${name}
|
||||
<div class="secondary">
|
||||
${this.hass.localize("ui.card.automation.last_triggered")}:
|
||||
${script.attributes.last_triggered
|
||||
? formatDateTime(
|
||||
new Date(script.attributes.last_triggered),
|
||||
this.hass.locale
|
||||
)
|
||||
: this.hass.localize("ui.components.relative_time.never")}
|
||||
</div>
|
||||
`
|
||||
: undefined,
|
||||
},
|
||||
};
|
||||
if (!narrow) {
|
||||
columns.last_triggered = {
|
||||
sortable: true,
|
||||
width: "20%",
|
||||
title: this.hass.localize("ui.card.automation.last_triggered"),
|
||||
template: (last_triggered) => html`
|
||||
${last_triggered
|
||||
? formatDateTime(new Date(last_triggered), this.hass.locale)
|
||||
: this.hass.localize("ui.components.relative_time.never")}
|
||||
`,
|
||||
};
|
||||
}
|
||||
columns.info = {
|
||||
title: "",
|
||||
type: "icon-button",
|
||||
template: (_info, script) => html`
|
||||
<mwc-icon-button
|
||||
.script=${script}
|
||||
@click=${this._showInfo}
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.script.picker.show_info"
|
||||
)}"
|
||||
>
|
||||
<ha-svg-icon .path=${mdiInformationOutline}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
`,
|
||||
};
|
||||
columns.edit = {
|
||||
title: "",
|
||||
type: "icon-button",
|
||||
template: (_info, script: any) => html`
|
||||
<a href="/config/script/edit/${script.entity_id}">
|
||||
<mwc-icon-button
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.script.picker.edit_script"
|
||||
)}"
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPencil}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
</a>
|
||||
`,
|
||||
};
|
||||
return columns;
|
||||
}
|
||||
);
|
||||
|
||||
@ -158,7 +174,7 @@ class HaScriptPicker extends LitElement {
|
||||
back-path="/config"
|
||||
.route=${this.route}
|
||||
.tabs=${configSections.automation}
|
||||
.columns=${this._columns(this.hass.language)}
|
||||
.columns=${this._columns(this.narrow, this.hass.locale)}
|
||||
.data=${this._scripts(this.scripts, this._filteredScripts)}
|
||||
.activeFilters=${this._activeFilters}
|
||||
id="entity_id"
|
||||
|
@ -286,7 +286,7 @@ class HaPanelDevService extends LitElement {
|
||||
}
|
||||
|
||||
private _checkUiSupported() {
|
||||
if (hasTemplate(this._serviceData)) {
|
||||
if (this._serviceData && hasTemplate(this._serviceData)) {
|
||||
this._yamlMode = true;
|
||||
this._uiAvailable = false;
|
||||
} else {
|
||||
|
@ -259,7 +259,7 @@ class HaLogbook extends LitElement {
|
||||
haStyle,
|
||||
haStyleScrollbar,
|
||||
css`
|
||||
:host {
|
||||
:host([virtualize]) {
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
|
@ -1188,6 +1188,9 @@
|
||||
"zha_device_card": {
|
||||
"device_name_placeholder": "Změnit název zařízení"
|
||||
}
|
||||
},
|
||||
"zha_reconfigure_device": {
|
||||
"heading": "Znovunastavení zařízení"
|
||||
}
|
||||
},
|
||||
"duration": {
|
||||
@ -1691,6 +1694,7 @@
|
||||
"info": "Díky integraci Google Assistant pro Home Assistant Cloud budete moci ovládat všechna zařízení v Home Assistant pomocí jakéhokoli zařízení podporujícího Google Assistant.",
|
||||
"info_state_reporting": "Pokud povolíte hlášení stavu, Home Assistant bude posílat veškeré změny stavů všech exponovaných entit do Google. Toto vám umožní sledovat vždy aktuální stavy entit v aplikaci Google.",
|
||||
"manage_entities": "Správa entit",
|
||||
"not_configured_text": "Než budete moci používat Google Assistant, musíte v aplikaci Google Home aktivovat dovednost Home Assistant Cloud pro Google Assistant.",
|
||||
"not_configured_title": "Google Asistent není aktivován",
|
||||
"security_devices": "Zabezpečovací zařízení",
|
||||
"sync_entities": "Synchronizovat entity s Google",
|
||||
@ -1859,6 +1863,7 @@
|
||||
"header": "Analytika",
|
||||
"instance_id": "ID instance: {huuid}",
|
||||
"introduction": "Sdílejte analytiku ze své instance. Tato data budou veřejně dostupná na {link}",
|
||||
"learn_more": "Zjistěte více, jak budou vaše údaje zpracovány.",
|
||||
"needs_base": "Aby byla tato možnost k dispozici, musíte povolit základní analytiku",
|
||||
"preference": {
|
||||
"base": {
|
||||
@ -2070,6 +2075,9 @@
|
||||
"filtering_by": "Filtrování podle",
|
||||
"show": "Zobrazit"
|
||||
},
|
||||
"hassio": {
|
||||
"button": "Nastavit"
|
||||
},
|
||||
"header": "Nastavení Home Assistant",
|
||||
"helpers": {
|
||||
"caption": "Pomocníci",
|
||||
@ -2236,10 +2244,17 @@
|
||||
"clear": "Zrušit",
|
||||
"description": "Zobrazení logů Home Assistant",
|
||||
"details": "Detaily protokolu ({level})",
|
||||
"level": {
|
||||
"critical": "KRITICKÉ",
|
||||
"debug": "LADĚNÍ",
|
||||
"error": "CHYBA",
|
||||
"info": "INFO",
|
||||
"warning": "VAROVÁNÍ"
|
||||
},
|
||||
"load_full_log": "Načíst úplný protokol Home Assistanta",
|
||||
"loading_log": "Načítání protokolu chyb...",
|
||||
"multiple_messages": "zpráva se poprvé objevila v {time} a zobrazuje se {counter} krát",
|
||||
"no_errors": "Nebyly hlášeny žádné chyby.",
|
||||
"no_errors": "Nebyly hlášeny žádné chyby",
|
||||
"no_issues": "Nejsou žádné nové problémy!",
|
||||
"refresh": "Obnovit"
|
||||
},
|
||||
|
@ -1188,6 +1188,9 @@
|
||||
"zha_device_card": {
|
||||
"device_name_placeholder": "Change device name"
|
||||
}
|
||||
},
|
||||
"zha_reconfigure_device": {
|
||||
"heading": "Reconfiguring device"
|
||||
}
|
||||
},
|
||||
"duration": {
|
||||
@ -1859,7 +1862,7 @@
|
||||
"documentation": "Before you enable this make sure you visit the analytics documentation page {link} to understand what you are sending and how it's stored.",
|
||||
"header": "Analytics",
|
||||
"instance_id": "Instance ID: {huuid}",
|
||||
"introduction": "Share analytics from your instance. This data will be publiclly available at {link}",
|
||||
"introduction": "Share analytics from your instance. This data will be publicly available at {link}",
|
||||
"learn_more": "Learn more about how your data will be processed.",
|
||||
"needs_base": "You need to enable base analytics for this option to be available",
|
||||
"preference": {
|
||||
@ -3746,7 +3749,7 @@
|
||||
"page-onboarding": {
|
||||
"analytics": {
|
||||
"finish": "Next",
|
||||
"intro": "Share analytics from your instance. This data will be publiclly available at {link}"
|
||||
"intro": "Share analytics from your instance. This data will be publicly available at {link}"
|
||||
},
|
||||
"core-config": {
|
||||
"button_detect": "Detect",
|
||||
|
@ -1187,6 +1187,9 @@
|
||||
"zha_device_card": {
|
||||
"device_name_placeholder": "Cambiar el nombre del dispositivo"
|
||||
}
|
||||
},
|
||||
"zha_reconfigure_device": {
|
||||
"heading": "Reconfigurando el dispositivo"
|
||||
}
|
||||
},
|
||||
"duration": {
|
||||
@ -1859,6 +1862,7 @@
|
||||
"header": "Analítica",
|
||||
"instance_id": "ID de instancia: {huuid}",
|
||||
"introduction": "Comparte análisis de tu instancia. Estos datos estarán disponibles públicamente en {link}",
|
||||
"learn_more": "Aprende más sobre cómo se procesarán tus datos.",
|
||||
"needs_base": "Debes habilitar el análisis base para que esta opción esté disponible",
|
||||
"preference": {
|
||||
"base": {
|
||||
@ -2070,6 +2074,9 @@
|
||||
"filtering_by": "Filtrando por",
|
||||
"show": "Mostrar"
|
||||
},
|
||||
"hassio": {
|
||||
"button": "Configurar"
|
||||
},
|
||||
"header": "Configurar Home Assistant",
|
||||
"helpers": {
|
||||
"caption": "Ayudantes",
|
||||
@ -2236,10 +2243,17 @@
|
||||
"clear": "Limpiar",
|
||||
"description": "Ve los registros de Home Assistant",
|
||||
"details": "Detalles de registro ({level})",
|
||||
"level": {
|
||||
"critical": "CRÍTICO",
|
||||
"debug": "DEPURACIÓN",
|
||||
"error": "ERROR",
|
||||
"info": "INFO",
|
||||
"warning": "ADVERTENCIA"
|
||||
},
|
||||
"load_full_log": "Cargar registro completo de Home Assistant",
|
||||
"loading_log": "Cargando registro de errores...",
|
||||
"multiple_messages": "el mensaje se produjo por primera vez a las {time} y aparece {counter} veces",
|
||||
"no_errors": "No se han reportado errores.",
|
||||
"no_errors": "No se han reportado errores",
|
||||
"no_issues": "¡No hay nuevos problemas!",
|
||||
"refresh": "Actualizar"
|
||||
},
|
||||
|
@ -1188,6 +1188,9 @@
|
||||
"zha_device_card": {
|
||||
"device_name_placeholder": "Muuda seadme nime"
|
||||
}
|
||||
},
|
||||
"zha_reconfigure_device": {
|
||||
"heading": "Seadme sätete muutmine"
|
||||
}
|
||||
},
|
||||
"duration": {
|
||||
@ -2072,6 +2075,9 @@
|
||||
"filtering_by": "Filtreeri",
|
||||
"show": "Kuva"
|
||||
},
|
||||
"hassio": {
|
||||
"button": "Seadista"
|
||||
},
|
||||
"header": "Home Assistant'i seadistamine",
|
||||
"helpers": {
|
||||
"caption": "Abimehed",
|
||||
|
@ -440,6 +440,9 @@
|
||||
"area-picker": {
|
||||
"show_areas": "Amosar áreas"
|
||||
},
|
||||
"data-table": {
|
||||
"clear": "Elim"
|
||||
},
|
||||
"device-picker": {
|
||||
"show_devices": "Amosar dispositivos"
|
||||
},
|
||||
@ -457,6 +460,12 @@
|
||||
"is_opening": "estase abrindo"
|
||||
},
|
||||
"show_trace": "Amosar rastro"
|
||||
},
|
||||
"related-filter-menu": {
|
||||
"filter_by_area": "Filtrar por área",
|
||||
"filter_by_device": "Filtrar por dispositivo",
|
||||
"filtered_by_area": "área: {area_name}",
|
||||
"filtered_by_device": "dispositivo: {device_name}"
|
||||
}
|
||||
},
|
||||
"dialogs": {
|
||||
@ -531,6 +540,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"show_trace": "Mostrar rastro",
|
||||
"triggers": {
|
||||
"type": {
|
||||
"mqtt": {
|
||||
@ -555,6 +565,12 @@
|
||||
}
|
||||
},
|
||||
"cloud": {
|
||||
"account": {
|
||||
"google": {
|
||||
"not_configured_text": "Antes de usar o Asistente de Google, debes activar a skill Home Assistant Cloud para Google Assistant na aplicación Google Home.",
|
||||
"not_configured_title": "Google Assistant non está activado"
|
||||
}
|
||||
},
|
||||
"forgot_password": {
|
||||
"instructions": "Introduce o teu enderezo de correo electrónico e enviarémosche unha ligazón para restablecer o teu contrasinal."
|
||||
},
|
||||
@ -562,6 +578,41 @@
|
||||
"email_address": "Enderezo electrónico"
|
||||
}
|
||||
},
|
||||
"core": {
|
||||
"section": {
|
||||
"core": {
|
||||
"analytics": {
|
||||
"documentation": "Antes de habilitalo, asegúrese de visitar a páxina de documentación de análise {link} para comprender o que está a enviar e como se almacena.",
|
||||
"header": "Analítica",
|
||||
"instance_id": "ID de instancia: {huuid}",
|
||||
"introduction": "Comparte analítica desde a túa instancia. Estes datos estarán dispoñibles publicamente en {link}",
|
||||
"learn_more": "Máis información sobre como se procesarán os teus datos.",
|
||||
"needs_base": "Debe habilitar a analítica base para que esta opción estea dispoñible",
|
||||
"preference": {
|
||||
"base": {
|
||||
"description": "Isto inclúe o ID de instancia, a versión e o tipo de instalación",
|
||||
"title": "Analítica básica"
|
||||
},
|
||||
"diagnostics": {
|
||||
"description": "Comparte informes de fallos e información de diagnóstico",
|
||||
"title": "Diagnóstico"
|
||||
},
|
||||
"statistics": {
|
||||
"description": "Isto inclúe un reconto de elementos na súa instalación, para ver unha lista completa consulta a documentación",
|
||||
"title": "Estatísticas de uso"
|
||||
},
|
||||
"usage_supervisor": {
|
||||
"title": "Integracións e complementos usados"
|
||||
},
|
||||
"usage": {
|
||||
"description": "Isto inclúe os nomes das túas integracións",
|
||||
"title": "Integracións usadas"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"devices": {
|
||||
"enabled_description": "Os dispositivos desactivados non se amosarán e as entidades que pertencen ao dispositivo desactivaranse e non se engadirán ao Asistente doméstico.",
|
||||
"picker": {
|
||||
@ -585,7 +636,15 @@
|
||||
"show": "Amosar"
|
||||
},
|
||||
"integrations": {
|
||||
"config_entry": {
|
||||
"logs": "rexistros",
|
||||
"not_loaded": "Non cargado, comproba o {logs_link}"
|
||||
},
|
||||
"config_flow": {
|
||||
"not_loaded": "Non se puido cargar a integración. Tenta reiniciar Home Assistant."
|
||||
},
|
||||
"disable": {
|
||||
"show": "Amosar",
|
||||
"show_disabled": "Amosar as integracións desactivadas"
|
||||
},
|
||||
"ignore": {
|
||||
@ -593,6 +652,15 @@
|
||||
"show_ignored": "Amosar integracións ignoradas"
|
||||
}
|
||||
},
|
||||
"logs": {
|
||||
"level": {
|
||||
"critical": "CRÍTICO",
|
||||
"debug": "DEPURAR",
|
||||
"error": "ERRO",
|
||||
"info": "INFORMACIÓN",
|
||||
"warning": "AVISO"
|
||||
}
|
||||
},
|
||||
"lovelace": {
|
||||
"dashboards": {
|
||||
"detail": {
|
||||
@ -853,9 +921,14 @@
|
||||
"not_supported": "Esta redirección non é compatible coa túa instancia de Home Assistant. Comprobe a {link} para coñecer as redireccións compatibles e a versión na que se introduciron."
|
||||
},
|
||||
"page-onboarding": {
|
||||
"analytics": {
|
||||
"finish": "Seguinte"
|
||||
},
|
||||
"core-config": {
|
||||
"intro_location": "Gustaríanos saber onde vives. Esta información axudará a amosar información e configurar automatismos baseados no sol. Estes datos nunca se comparten fóra da túa rede."
|
||||
},
|
||||
"finish": "Rematar",
|
||||
"next": "Seguinte",
|
||||
"restore": {
|
||||
"show_log": "Amosar rexistro completo"
|
||||
}
|
||||
@ -863,6 +936,19 @@
|
||||
"profile": {
|
||||
"long_lived_access_tokens": {
|
||||
"prompt_copy_token": "Copia o teu token de acceso. Non aparecerá de novo."
|
||||
},
|
||||
"number_format": {
|
||||
"description": "Escolle como se formatean os números.",
|
||||
"dropdown_label": "Formato de número",
|
||||
"formats": {
|
||||
"comma_decimal": "1,234,567.89",
|
||||
"decimal_comma": "1.234.567,89",
|
||||
"language": "Automático (usar a configuración do idioma)",
|
||||
"none": "Ningunha",
|
||||
"space_comma": "1 234 567,89",
|
||||
"system": "Usa a configuración rexional do sistema"
|
||||
},
|
||||
"header": "Formato de número"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1860,6 +1860,7 @@
|
||||
"header": "분석",
|
||||
"instance_id": "인스턴스 ID: {huuid}",
|
||||
"introduction": "인스턴스의 분석 내용을 공유합니다. 이 데이터는 {link}에서 공개적으로 사용할 수 있습니다",
|
||||
"learn_more": "통계자료가 어떻게 처리되는지 알아보기.",
|
||||
"needs_base": "이 옵션을 사용하려면 기본 분석을 활성화해야 합니다",
|
||||
"preference": {
|
||||
"base": {
|
||||
@ -2237,6 +2238,13 @@
|
||||
"clear": "지우기",
|
||||
"description": "Home Assistant 로그 내역을 봅니다",
|
||||
"details": "로그 상세정보 ({level})",
|
||||
"level": {
|
||||
"critical": "치명적오류",
|
||||
"debug": "디버그",
|
||||
"error": "오류",
|
||||
"info": "정보",
|
||||
"warning": "경고"
|
||||
},
|
||||
"load_full_log": "Home Assistant 로그 전부 불러오기",
|
||||
"loading_log": "오류 로그를 읽는 중...",
|
||||
"multiple_messages": "{time}에 처음 발생했으며, {counter}번 발생했습니다.",
|
||||
|
@ -1860,6 +1860,7 @@
|
||||
"header": "Analytics",
|
||||
"instance_id": "Forekomst-ID: {huuid}",
|
||||
"introduction": "Del analyse fra forekomsten din. Disse dataene vil være offentlig tilgjengelige på {link}",
|
||||
"learn_more": "Lær mer om hvordan dataene dine blir behandlet.",
|
||||
"needs_base": "Du må aktivere basisanalyse for at dette alternativet skal være tilgjengelig",
|
||||
"preference": {
|
||||
"base": {
|
||||
@ -2071,6 +2072,9 @@
|
||||
"filtering_by": "Filtrering etter",
|
||||
"show": "Vis"
|
||||
},
|
||||
"hassio": {
|
||||
"button": "Konfigurer"
|
||||
},
|
||||
"header": "Konfigurer Home Assistant",
|
||||
"helpers": {
|
||||
"caption": "Hjelpere",
|
||||
@ -2237,10 +2241,17 @@
|
||||
"clear": "Tøm",
|
||||
"description": "Vis Home Assistant loggene",
|
||||
"details": "Loggdetaljer ({level})",
|
||||
"level": {
|
||||
"critical": "KRITISK",
|
||||
"debug": "DEBUG",
|
||||
"error": "FEIL",
|
||||
"info": "INFO",
|
||||
"warning": "ADVARSEL"
|
||||
},
|
||||
"load_full_log": "Last inn fullstendig Home Assistant logg",
|
||||
"loading_log": "Laster inn feillogg ...",
|
||||
"multiple_messages": "meldingen oppstod først ved {time} og vist {counter} ganger",
|
||||
"no_errors": "Ingen feil er rapportert.",
|
||||
"no_errors": "Ingen feil er rapportert",
|
||||
"no_issues": "Det er ingen nye problemer!",
|
||||
"refresh": "Oppdater"
|
||||
},
|
||||
|
@ -1188,6 +1188,9 @@
|
||||
"zha_device_card": {
|
||||
"device_name_placeholder": "Wijzig apparaatnaam"
|
||||
}
|
||||
},
|
||||
"zha_reconfigure_device": {
|
||||
"heading": "Apparaat opnieuw configureren"
|
||||
}
|
||||
},
|
||||
"duration": {
|
||||
@ -1656,8 +1659,8 @@
|
||||
},
|
||||
"introduction": "Met de Blueprinteditor kunt je Blueprints maken en bewerken.",
|
||||
"learn_more": "Meer informatie over Blueprints",
|
||||
"share_blueprint": "Deel blauwdruk",
|
||||
"share_blueprint_no_url": "Kan blauwdruk niet delen: geen bron-URL",
|
||||
"share_blueprint": "Deel Blueprint",
|
||||
"share_blueprint_no_url": "Kan Blueprint niet delen: geen bron-URL",
|
||||
"use_blueprint": "Automatisering maken"
|
||||
}
|
||||
},
|
||||
@ -1860,6 +1863,7 @@
|
||||
"header": "Analytics",
|
||||
"instance_id": "Instantie-ID: {huuid}",
|
||||
"introduction": "Deel analyses vanuit uw instantie. Deze gegevens zijn openbaar beschikbaar op {link}",
|
||||
"learn_more": "Lees meer over hoe uw gegevens worden verwerkt.",
|
||||
"needs_base": "U moet basisanalyses inschakelen om deze optie beschikbaar te maken",
|
||||
"preference": {
|
||||
"base": {
|
||||
@ -2071,6 +2075,9 @@
|
||||
"filtering_by": "Filteren op",
|
||||
"show": "Toon"
|
||||
},
|
||||
"hassio": {
|
||||
"button": "Configureer"
|
||||
},
|
||||
"header": "Configureer Home Assistant",
|
||||
"helpers": {
|
||||
"caption": "Helpers",
|
||||
@ -2237,6 +2244,13 @@
|
||||
"clear": "Wis",
|
||||
"description": "Home Assistant logboek bekijken",
|
||||
"details": "Logboekdetails ({level})",
|
||||
"level": {
|
||||
"critical": "KRITISCH",
|
||||
"debug": "DEBUG",
|
||||
"error": "FOUT",
|
||||
"info": "INFO",
|
||||
"warning": "WAARSCHUWING"
|
||||
},
|
||||
"load_full_log": "Laad volledige Home Assistant logboek",
|
||||
"loading_log": "Foutenlogboek laden ...",
|
||||
"multiple_messages": "bericht kwam voor het eerst om {time} en verschijnt {counter} keer",
|
||||
|
@ -1188,6 +1188,9 @@
|
||||
"zha_device_card": {
|
||||
"device_name_placeholder": "Название"
|
||||
}
|
||||
},
|
||||
"zha_reconfigure_device": {
|
||||
"heading": "Перенастройка устройства"
|
||||
}
|
||||
},
|
||||
"duration": {
|
||||
@ -1859,7 +1862,8 @@
|
||||
"documentation": "Прежде всего, посетите страницу документации по аналитике {link}, чтобы понять, что Вы будете отправлять и как это будет храниться.",
|
||||
"header": "Аналитика",
|
||||
"instance_id": "Идентификатор экземпляра Home Assistant: {huuid}",
|
||||
"introduction": "Поделитесь аналитикой из Вашего Home Assistant. Эти данные будут доступны для всех по адресу {ссылка}.",
|
||||
"introduction": "Поделитесь аналитикой из Вашего Home Assistant. Эти данные будут доступны для всех по адресу {link}.",
|
||||
"learn_more": "Узнайте больше о том, как будут обрабатываться Ваши данные.",
|
||||
"needs_base": "Включите базовую аналитику, чтобы эта опция была доступна",
|
||||
"preference": {
|
||||
"base": {
|
||||
@ -2071,6 +2075,9 @@
|
||||
"filtering_by": "Отфильтровано по принадлежности к",
|
||||
"show": "Показать"
|
||||
},
|
||||
"hassio": {
|
||||
"button": "Настроить"
|
||||
},
|
||||
"header": "Настройка Home Assistant",
|
||||
"helpers": {
|
||||
"caption": "Вспомогательное",
|
||||
@ -2237,10 +2244,17 @@
|
||||
"clear": "Очистить",
|
||||
"description": "Журналы работы сервера",
|
||||
"details": "Уровень: {level}",
|
||||
"level": {
|
||||
"critical": "КРИТИЧЕСКАЯ НЕИСПРАВНОСТЬ",
|
||||
"debug": "ОТЛАДКА",
|
||||
"error": "ОШИБКА",
|
||||
"info": "ИНФОРМАЦИЯ",
|
||||
"warning": "ПРЕДУПРЕЖДЕНИЕ"
|
||||
},
|
||||
"load_full_log": "Показать весь журнал",
|
||||
"loading_log": "Загрузка журнала…",
|
||||
"multiple_messages": "первое сообщение получено {time} и повторялось {counter} раз",
|
||||
"no_errors": "Нет сообщений об ошибках.",
|
||||
"no_errors": "Нет сообщений об ошибках",
|
||||
"no_issues": "Нет сообщений о проблемах.",
|
||||
"refresh": "Обновить"
|
||||
},
|
||||
@ -3735,7 +3749,7 @@
|
||||
"page-onboarding": {
|
||||
"analytics": {
|
||||
"finish": "Далее",
|
||||
"intro": "Поделитесь аналитикой из Вашего Home Assistant. Эти данные будут доступны для всех по адресу {ссылка}."
|
||||
"intro": "Поделитесь аналитикой из Вашего Home Assistant. Эти данные будут доступны для всех по адресу {link}."
|
||||
},
|
||||
"core-config": {
|
||||
"button_detect": "Заполнить",
|
||||
|
@ -1188,6 +1188,9 @@
|
||||
"zha_device_card": {
|
||||
"device_name_placeholder": "變更裝置名稱"
|
||||
}
|
||||
},
|
||||
"zha_reconfigure_device": {
|
||||
"heading": "重新設定裝置"
|
||||
}
|
||||
},
|
||||
"duration": {
|
||||
@ -1860,6 +1863,7 @@
|
||||
"header": "分析資料",
|
||||
"instance_id": "實例 ID:{huuid}",
|
||||
"introduction": "分享實例分析資料,資料將可透過 {link} 連結公開取得",
|
||||
"learn_more": "詳細了解資料會如何處理。",
|
||||
"needs_base": "需要開啟基本分析、方能使用此選項",
|
||||
"preference": {
|
||||
"base": {
|
||||
@ -2071,6 +2075,9 @@
|
||||
"filtering_by": "篩選",
|
||||
"show": "顯示"
|
||||
},
|
||||
"hassio": {
|
||||
"button": "設定"
|
||||
},
|
||||
"header": "設定 Home Assistant",
|
||||
"helpers": {
|
||||
"caption": "助手",
|
||||
@ -2237,10 +2244,17 @@
|
||||
"clear": "清除",
|
||||
"description": "檢視 Home Assistant 日誌",
|
||||
"details": "記錄詳細資料({level})",
|
||||
"level": {
|
||||
"critical": "緊急",
|
||||
"debug": "除錯",
|
||||
"error": "錯誤",
|
||||
"info": "資訊",
|
||||
"warning": "警告"
|
||||
},
|
||||
"load_full_log": "載入完整 Home Assistant 記錄",
|
||||
"loading_log": "載入錯誤記錄中...",
|
||||
"multiple_messages": "訊息首次出現於 {time}、共顯示 {counter} 次",
|
||||
"no_errors": "未回報任何錯誤。",
|
||||
"no_errors": "未回報任何錯誤",
|
||||
"no_issues": "沒有新問題!",
|
||||
"refresh": "更新"
|
||||
},
|
||||
|
@ -1926,7 +1926,7 @@
|
||||
"@codemirror/state" "^0.18.0"
|
||||
"@codemirror/view" "^0.18.0"
|
||||
|
||||
"@codemirror/highlight@^0.18.0":
|
||||
"@codemirror/highlight@^0.18.0", "@codemirror/highlight@^0.18.1":
|
||||
version "0.18.3"
|
||||
resolved "https://registry.yarnpkg.com/@codemirror/highlight/-/highlight-0.18.3.tgz#50e268630f113c322a2dc97c9f68d71934fffcb0"
|
||||
integrity sha512-NmRmkmWl8ht6Y6Y39ghov84AMPCqhUPIH9fmILs2NaWxZFZf4jGCTzrULnmREGsTie+26+LbKUncIU+PBu1Qng==
|
||||
|
Loading…
x
Reference in New Issue
Block a user