mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-28 11:46:42 +00:00
commit
f55cbd9e9a
1
.github/ISSUE_TEMPLATE.md
vendored
1
.github/ISSUE_TEMPLATE.md
vendored
@ -1,7 +1,6 @@
|
||||
<!-- READ THIS FIRST:
|
||||
- If you need additional help with this template please refer to https://www.home-assistant.io/help/reporting_issues/
|
||||
- Make sure you are running the latest version of Home Assistant before reporting an issue: https://github.com/home-assistant/home-assistant/releases
|
||||
- This is for bugs only. Feature and enhancement requests should go in our community forum: https://community.home-assistant.io/c/feature-requests
|
||||
- Provide as many details as possible. Do not delete any text from this template!
|
||||
-->
|
||||
|
||||
|
@ -104,7 +104,10 @@ gulp.task("compress-static", () => compressStatic(paths.static));
|
||||
|
||||
gulp.task("copy-static-demo", (done) => {
|
||||
// Copy app static files
|
||||
fs.copySync(polyPath("public"), paths.demo_root);
|
||||
fs.copySync(
|
||||
polyPath("public/static"),
|
||||
path.resolve(paths.demo_root, "static")
|
||||
);
|
||||
// Copy demo static files
|
||||
fs.copySync(path.resolve(paths.demo_dir, "public"), paths.demo_root);
|
||||
|
||||
|
20
cast/public/_headers
Normal file
20
cast/public/_headers
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
Cache-Control: public, max-age: 0, s-maxage=3600, must-revalidate
|
||||
Content-Security-Policy: form-action https:
|
||||
Feature-Policy: vibrate 'none'; geolocation 'none'; midi 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; speaker 'none'; vibrate 'none'; payment 'none'
|
||||
Referrer-Policy: no-referrer-when-downgrade
|
||||
X-Content-Type-Options: nosniff
|
||||
X-Frame-Options: DENY
|
||||
X-XSS-Protection: 1; mode=block
|
||||
|
||||
/images/*
|
||||
Cache-Control: public, max-age: 604800, s-maxage=604800
|
||||
|
||||
/manifest.json
|
||||
Cache-Control: public, max-age: 3600, s-maxage=3600
|
||||
|
||||
/frontend_es5/*
|
||||
Cache-Control: public, max-age: 604800, s-maxage=604800
|
||||
|
||||
/frontend_latest/*
|
||||
Cache-Control: public, max-age: 604800, s-maxage=604800
|
18
demo/public/_headers
Normal file
18
demo/public/_headers
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
Cache-Control: public, max-age: 0, s-maxage=3600, must-revalidate
|
||||
Content-Security-Policy: form-action https:
|
||||
Referrer-Policy: no-referrer-when-downgrade
|
||||
X-Content-Type-Options: nosniff
|
||||
X-XSS-Protection: 1; mode=block
|
||||
|
||||
/api/*
|
||||
Cache-Control: public, max-age: 604800, s-maxage=604800
|
||||
|
||||
/assets/*
|
||||
Cache-Control: public, max-age: 604800, s-maxage=604800
|
||||
|
||||
/frontend_es5/*
|
||||
Cache-Control: public, max-age: 604800, s-maxage=604800
|
||||
|
||||
/frontend_latest/*
|
||||
Cache-Control: public, max-age: 604800, s-maxage=604800
|
@ -25,7 +25,7 @@
|
||||
"@material/mwc-fab": "^0.8.0",
|
||||
"@material/mwc-ripple": "^0.8.0",
|
||||
"@material/mwc-switch": "^0.8.0",
|
||||
"@mdi/svg": "4.4.95",
|
||||
"@mdi/svg": "4.5.95",
|
||||
"@polymer/app-layout": "^3.0.2",
|
||||
"@polymer/app-localize-behavior": "^3.0.1",
|
||||
"@polymer/app-route": "^3.0.2",
|
||||
@ -74,7 +74,7 @@
|
||||
"@webcomponents/webcomponentsjs": "^2.2.7",
|
||||
"chart.js": "~2.8.0",
|
||||
"chartjs-chart-timeline": "^0.3.0",
|
||||
"codemirror": "^5.45.0",
|
||||
"codemirror": "^5.49.0",
|
||||
"cpx": "^1.5.0",
|
||||
"deep-clone-simple": "^1.1.1",
|
||||
"es6-object-assign": "^1.1.0",
|
||||
@ -117,6 +117,7 @@
|
||||
"@types/chai": "^4.1.7",
|
||||
"@types/chromecast-caf-receiver": "^3.0.12",
|
||||
"@types/chromecast-caf-sender": "^1.0.1",
|
||||
"@types/codemirror": "^0.0.78",
|
||||
"@types/hls.js": "^0.12.3",
|
||||
"@types/leaflet": "^1.4.3",
|
||||
"@types/memoize-one": "4.1.0",
|
||||
|
2
setup.py
2
setup.py
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20191002.2",
|
||||
version="20191014.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
@ -59,31 +59,31 @@ export interface SortingChangedEvent {
|
||||
|
||||
export type SortingDirection = "desc" | "asc" | null;
|
||||
|
||||
export interface DataTabelColumnContainer {
|
||||
[key: string]: DataTabelColumnData;
|
||||
export interface DataTableColumnContainer {
|
||||
[key: string]: DataTableColumnData;
|
||||
}
|
||||
|
||||
export interface DataTabelSortColumnData {
|
||||
export interface DataTableSortColumnData {
|
||||
sortable?: boolean;
|
||||
filterable?: boolean;
|
||||
filterKey?: string;
|
||||
direction?: SortingDirection;
|
||||
}
|
||||
|
||||
export interface DataTabelColumnData extends DataTabelSortColumnData {
|
||||
export interface DataTableColumnData extends DataTableSortColumnData {
|
||||
title: string;
|
||||
type?: "numeric";
|
||||
template?: (data: any) => TemplateResult;
|
||||
type?: "numeric" | "icon";
|
||||
template?: <T>(data: any, row: T) => TemplateResult;
|
||||
}
|
||||
|
||||
export interface DataTabelRowData {
|
||||
export interface DataTableRowData {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
@customElement("ha-data-table")
|
||||
export class HaDataTable extends BaseElement {
|
||||
@property({ type: Object }) public columns: DataTabelColumnContainer = {};
|
||||
@property({ type: Array }) public data: DataTabelRowData[] = [];
|
||||
@property({ type: Object }) public columns: DataTableColumnContainer = {};
|
||||
@property({ type: Array }) public data: DataTableRowData[] = [];
|
||||
@property({ type: Boolean }) public selectable = false;
|
||||
@property({ type: String }) public id = "id";
|
||||
protected mdcFoundation!: MDCDataTableFoundation;
|
||||
@ -98,9 +98,9 @@ export class HaDataTable extends BaseElement {
|
||||
@property({ type: String }) private _filter = "";
|
||||
@property({ type: String }) private _sortColumn?: string;
|
||||
@property({ type: String }) private _sortDirection: SortingDirection = null;
|
||||
@property({ type: Array }) private _filteredData: DataTabelRowData[] = [];
|
||||
@property({ type: Array }) private _filteredData: DataTableRowData[] = [];
|
||||
private _sortColumns: {
|
||||
[key: string]: DataTabelSortColumnData;
|
||||
[key: string]: DataTableSortColumnData;
|
||||
} = {};
|
||||
private curRequest = 0;
|
||||
private _worker: any | undefined;
|
||||
@ -134,8 +134,8 @@ export class HaDataTable extends BaseElement {
|
||||
}
|
||||
}
|
||||
|
||||
const clonedColumns: DataTabelColumnContainer = deepClone(this.columns);
|
||||
Object.values(clonedColumns).forEach((column: DataTabelColumnData) => {
|
||||
const clonedColumns: DataTableColumnContainer = deepClone(this.columns);
|
||||
Object.values(clonedColumns).forEach((column: DataTableColumnData) => {
|
||||
delete column.title;
|
||||
delete column.type;
|
||||
delete column.template;
|
||||
@ -190,9 +190,12 @@ export class HaDataTable extends BaseElement {
|
||||
const [key, column] = columnEntry;
|
||||
const sorted = key === this._sortColumn;
|
||||
const classes = {
|
||||
"mdc-data-table__cell--numeric": Boolean(
|
||||
"mdc-data-table__header-cell--numeric": Boolean(
|
||||
column.type && column.type === "numeric"
|
||||
),
|
||||
"mdc-data-table__header-cell--icon": Boolean(
|
||||
column.type && column.type === "icon"
|
||||
),
|
||||
sortable: Boolean(column.sortable),
|
||||
"not-sorted": Boolean(column.sortable && !sorted),
|
||||
};
|
||||
@ -222,8 +225,8 @@ export class HaDataTable extends BaseElement {
|
||||
<tbody class="mdc-data-table__content">
|
||||
${repeat(
|
||||
this._filteredData!,
|
||||
(row: DataTabelRowData) => row[this.id],
|
||||
(row: DataTabelRowData) => html`
|
||||
(row: DataTableRowData) => row[this.id],
|
||||
(row: DataTableRowData) => html`
|
||||
<tr
|
||||
data-row-id="${row[this.id]}"
|
||||
@click=${this._handleRowClick}
|
||||
@ -251,10 +254,13 @@ export class HaDataTable extends BaseElement {
|
||||
"mdc-data-table__cell--numeric": Boolean(
|
||||
column.type && column.type === "numeric"
|
||||
),
|
||||
"mdc-data-table__cell--icon": Boolean(
|
||||
column.type && column.type === "icon"
|
||||
),
|
||||
})}"
|
||||
>
|
||||
${column.template
|
||||
? column.template(row[key])
|
||||
? column.template(row[key], row)
|
||||
: row[key]}
|
||||
</td>
|
||||
`;
|
||||
@ -516,6 +522,11 @@ export class HaDataTable extends BaseElement {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.mdc-data-table__cell--icon {
|
||||
color: var(--secondary-text-color);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mdc-data-table__header-cell {
|
||||
font-family: Roboto, sans-serif;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
@ -543,6 +554,10 @@ export class HaDataTable extends BaseElement {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.mdc-data-table__header-cell--icon {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* custom from here */
|
||||
|
||||
.mdc-data-table {
|
||||
@ -554,7 +569,7 @@ export class HaDataTable extends BaseElement {
|
||||
.mdc-data-table__header-cell.sortable {
|
||||
cursor: pointer;
|
||||
}
|
||||
.mdc-data-table__header-cell.not-sorted:not(.mdc-data-table__cell--numeric)
|
||||
.mdc-data-table__header-cell.not-sorted:not(.mdc-data-table__header-cell--numeric):not(.mdc-data-table__header-cell--icon)
|
||||
span {
|
||||
position: relative;
|
||||
left: -24px;
|
||||
@ -565,7 +580,7 @@ export class HaDataTable extends BaseElement {
|
||||
.mdc-data-table__header-cell.not-sorted ha-icon {
|
||||
left: -36px;
|
||||
}
|
||||
.mdc-data-table__header-cell.not-sorted:not(.mdc-data-table__cell--numeric):hover
|
||||
.mdc-data-table__header-cell.not-sorted:not(.mdc-data-table__header-cell--numeric):not(.mdc-data-table__header-cell--icon):hover
|
||||
span {
|
||||
left: 0px;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {
|
||||
DataTabelColumnContainer,
|
||||
DataTabelColumnData,
|
||||
DataTabelRowData,
|
||||
DataTableColumnContainer,
|
||||
DataTableColumnData,
|
||||
DataTableRowData,
|
||||
SortingDirection,
|
||||
} from "./ha-data-table";
|
||||
|
||||
@ -9,8 +9,8 @@ import memoizeOne from "memoize-one";
|
||||
|
||||
export const filterSortData = memoizeOne(
|
||||
async (
|
||||
data: DataTabelRowData[],
|
||||
columns: DataTabelColumnContainer,
|
||||
data: DataTableRowData[],
|
||||
columns: DataTableColumnContainer,
|
||||
filter: string,
|
||||
direction: SortingDirection,
|
||||
sortColumn?: string
|
||||
@ -27,8 +27,8 @@ export const filterSortData = memoizeOne(
|
||||
|
||||
const _memFilterData = memoizeOne(
|
||||
async (
|
||||
data: DataTabelRowData[],
|
||||
columns: DataTabelColumnContainer,
|
||||
data: DataTableRowData[],
|
||||
columns: DataTableColumnContainer,
|
||||
filter: string
|
||||
) => {
|
||||
if (!filter) {
|
||||
@ -40,8 +40,8 @@ const _memFilterData = memoizeOne(
|
||||
|
||||
const _memSortData = memoizeOne(
|
||||
(
|
||||
data: DataTabelRowData[],
|
||||
columns: DataTabelColumnContainer,
|
||||
data: DataTableRowData[],
|
||||
columns: DataTableColumnContainer,
|
||||
direction: SortingDirection,
|
||||
sortColumn: string
|
||||
) => {
|
||||
@ -50,8 +50,8 @@ const _memSortData = memoizeOne(
|
||||
);
|
||||
|
||||
export const filterData = (
|
||||
data: DataTabelRowData[],
|
||||
columns: DataTabelColumnContainer,
|
||||
data: DataTableRowData[],
|
||||
columns: DataTableColumnContainer,
|
||||
filter: string
|
||||
) =>
|
||||
data.filter((row) => {
|
||||
@ -71,8 +71,8 @@ export const filterData = (
|
||||
});
|
||||
|
||||
export const sortData = (
|
||||
data: DataTabelRowData[],
|
||||
column: DataTabelColumnData,
|
||||
data: DataTableRowData[],
|
||||
column: DataTableColumnData,
|
||||
direction: SortingDirection,
|
||||
sortColumn: string
|
||||
) =>
|
||||
|
160
src/components/ha-code-editor.ts
Normal file
160
src/components/ha-code-editor.ts
Normal file
@ -0,0 +1,160 @@
|
||||
import { loadCodeMirror } from "../resources/codemirror.ondemand";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import {
|
||||
UpdatingElement,
|
||||
property,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
import { Editor } from "codemirror";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"editor-save": undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("ha-code-editor")
|
||||
export class HaCodeEditor extends UpdatingElement {
|
||||
public codemirror?: Editor;
|
||||
@property() public mode?: string;
|
||||
@property() public autofocus = false;
|
||||
@property() public rtl = false;
|
||||
@property() public error = false;
|
||||
@property() private _value = "";
|
||||
|
||||
public set value(value: string) {
|
||||
this._value = value;
|
||||
}
|
||||
|
||||
public get value(): string {
|
||||
return this.codemirror ? this.codemirror.getValue() : this._value;
|
||||
}
|
||||
|
||||
public get hasComments(): boolean {
|
||||
return this.shadowRoot!.querySelector("span.cm-comment") ? true : false;
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
if (!this.codemirror) {
|
||||
return;
|
||||
}
|
||||
this.codemirror.refresh();
|
||||
if (this.autofocus !== false) {
|
||||
this.codemirror.focus();
|
||||
}
|
||||
}
|
||||
|
||||
protected update(changedProps: PropertyValues): void {
|
||||
super.update(changedProps);
|
||||
|
||||
if (!this.codemirror) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (changedProps.has("mode")) {
|
||||
this.codemirror.setOption("mode", this.mode);
|
||||
}
|
||||
if (changedProps.has("autofocus")) {
|
||||
this.codemirror.setOption("autofocus", this.autofocus !== false);
|
||||
}
|
||||
if (changedProps.has("_value") && this._value !== this.value) {
|
||||
this.codemirror.setValue(this._value);
|
||||
}
|
||||
if (changedProps.has("rtl")) {
|
||||
this.codemirror.setOption("gutters", this._calcGutters());
|
||||
this._setScrollBarDirection();
|
||||
}
|
||||
if (changedProps.has("error")) {
|
||||
this.classList.toggle("error-state", this.error);
|
||||
}
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues): void {
|
||||
super.firstUpdated(changedProps);
|
||||
this._load();
|
||||
}
|
||||
|
||||
private async _load(): Promise<void> {
|
||||
const loaded = await loadCodeMirror();
|
||||
|
||||
const codeMirror = loaded.codeMirror;
|
||||
|
||||
const shadowRoot = this.attachShadow({ mode: "open" });
|
||||
|
||||
shadowRoot!.innerHTML = `
|
||||
<style>
|
||||
${loaded.codeMirrorCss}
|
||||
.CodeMirror {
|
||||
height: var(--code-mirror-height, auto);
|
||||
direction: var(--code-mirror-direction, ltr);
|
||||
}
|
||||
.CodeMirror-scroll {
|
||||
max-height: var(--code-mirror-max-height, --code-mirror-height);
|
||||
}
|
||||
.CodeMirror-gutters {
|
||||
border-right: 1px solid var(--paper-input-container-color, var(--secondary-text-color));
|
||||
background-color: var(--paper-dialog-background-color, var(--primary-background-color));
|
||||
transition: 0.2s ease border-right;
|
||||
}
|
||||
:host(.error-state) .CodeMirror-gutters {
|
||||
border-color: var(--error-state-color, red);
|
||||
}
|
||||
.CodeMirror-focused .CodeMirror-gutters {
|
||||
border-right: 2px solid var(--paper-input-container-focus-color, var(--primary-color));
|
||||
}
|
||||
.CodeMirror-linenumber {
|
||||
color: var(--paper-dialog-color, var(--primary-text-color));
|
||||
}
|
||||
.rtl .CodeMirror-vscrollbar {
|
||||
right: auto;
|
||||
left: 0px;
|
||||
}
|
||||
.rtl-gutter {
|
||||
width: 20px;
|
||||
}
|
||||
</style>`;
|
||||
|
||||
this.codemirror = codeMirror(shadowRoot, {
|
||||
value: this._value,
|
||||
lineNumbers: true,
|
||||
tabSize: 2,
|
||||
mode: this.mode,
|
||||
autofocus: this.autofocus !== false,
|
||||
viewportMargin: Infinity,
|
||||
extraKeys: {
|
||||
Tab: "indentMore",
|
||||
"Shift-Tab": "indentLess",
|
||||
},
|
||||
gutters: this._calcGutters(),
|
||||
});
|
||||
this._setScrollBarDirection();
|
||||
this.codemirror!.on("changes", () => this._onChange());
|
||||
}
|
||||
|
||||
private _onChange(): void {
|
||||
const newValue = this.value;
|
||||
if (newValue === this._value) {
|
||||
return;
|
||||
}
|
||||
this._value = newValue;
|
||||
fireEvent(this, "value-changed", { value: this._value });
|
||||
}
|
||||
|
||||
private _calcGutters(): string[] {
|
||||
return this.rtl ? ["rtl-gutter", "CodeMirror-linenumbers"] : [];
|
||||
}
|
||||
|
||||
private _setScrollBarDirection(): void {
|
||||
if (this.codemirror) {
|
||||
this.codemirror.getWrapperElement().classList.toggle("rtl", this.rtl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-code-editor": HaCodeEditor;
|
||||
}
|
||||
}
|
@ -77,7 +77,12 @@ class LocationEditor extends LitElement {
|
||||
}
|
||||
|
||||
private _updateLocation(latlng: LatLng) {
|
||||
this.location = this._ignoreFitToMap = [latlng.lat, latlng.lng];
|
||||
let longitude = latlng.lng;
|
||||
if (Math.abs(longitude) > 180.0) {
|
||||
// Normalize longitude if map provides values beyond -180 to +180 degrees.
|
||||
longitude = (((longitude % 360.0) + 540.0) % 360.0) - 180.0;
|
||||
}
|
||||
this.location = this._ignoreFitToMap = [latlng.lat, longitude];
|
||||
fireEvent(this, "change", undefined, { bubbles: false });
|
||||
}
|
||||
|
||||
|
@ -39,6 +39,15 @@ export const fetchDeviceTriggers = (hass: HomeAssistant, deviceId: string) =>
|
||||
device_id: deviceId,
|
||||
});
|
||||
|
||||
export const fetchDeviceConditionCapabilities = (
|
||||
hass: HomeAssistant,
|
||||
condition: DeviceCondition
|
||||
) =>
|
||||
hass.callWS<DeviceCondition[]>({
|
||||
type: "device_automation/condition/capabilities",
|
||||
condition,
|
||||
});
|
||||
|
||||
export const fetchDeviceTriggerCapabilities = (
|
||||
hass: HomeAssistant,
|
||||
trigger: DeviceTrigger
|
||||
|
@ -18,6 +18,11 @@ export interface LovelaceViewConfig {
|
||||
theme?: string;
|
||||
panel?: boolean;
|
||||
background?: string;
|
||||
visible?: boolean | ShowViewConfig[];
|
||||
}
|
||||
|
||||
export interface ShowViewConfig {
|
||||
user?: string;
|
||||
}
|
||||
|
||||
export interface LovelaceCardConfig {
|
||||
|
@ -82,14 +82,16 @@ class DialogConfigEntrySystemOptions extends LitElement {
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.config_entry_system_options.enable_new_entities_label"
|
||||
)}
|
||||
</div>
|
||||
<div class="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.config_entry_system_options.enable_new_entities_description"
|
||||
)}
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.config_entry_system_options.enable_new_entities_label"
|
||||
)}
|
||||
</p>
|
||||
<p class="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.config_entry_system_options.enable_new_entities_description"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</ha-switch>
|
||||
</div>
|
||||
@ -160,7 +162,9 @@ class DialogConfigEntrySystemOptions extends LitElement {
|
||||
padding-bottom: 24px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
.secondary {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
@ -79,6 +79,18 @@ class StepFlowPickHandler extends LitElement {
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.note_about_integrations"
|
||||
)}<br />
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.note_about_website_reference"
|
||||
)}<a href="https://www.home-assistant.io/integrations/"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.home_assistant_website"
|
||||
)}.</a
|
||||
>
|
||||
</p>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -119,6 +131,9 @@ class StepFlowPickHandler extends LitElement {
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
p {
|
||||
text-align: center;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@ -63,19 +63,21 @@ class CloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
</style>
|
||||
<hass-subpage header="Home Assistant Cloud">
|
||||
<hass-subpage header="[[localize('ui.panel.config.cloud.caption')]]">
|
||||
<div class="content">
|
||||
<ha-config-section is-wide="[[isWide]]">
|
||||
<span slot="header">Home Assistant Cloud</span>
|
||||
<span slot="header"
|
||||
>[[localize('ui.panel.config.cloud.caption')]]</span
|
||||
>
|
||||
<div slot="introduction">
|
||||
<p>
|
||||
Thank you for being part of Home Assistant Cloud. It's because
|
||||
of people like you that we are able to make a great home
|
||||
automation experience for everyone. Thank you!
|
||||
[[localize('ui.panel.config.cloud.account.thank_you_note')]]
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ha-card header="Nabu Casa Account">
|
||||
<ha-card
|
||||
header="[[localize('ui.panel.config.cloud.account.nabu_casa_account')]]"
|
||||
>
|
||||
<div class="account-row">
|
||||
<paper-item-body two-line="">
|
||||
[[cloudStatus.email]]
|
||||
@ -86,33 +88,37 @@ class CloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
</div>
|
||||
|
||||
<div class="account-row">
|
||||
<paper-item-body> Cloud connection status </paper-item-body>
|
||||
<paper-item-body
|
||||
>[[localize('ui.panel.config.cloud.account.connection_status')]]</paper-item-body
|
||||
>
|
||||
<div class="status">[[cloudStatus.cloud]]</div>
|
||||
</div>
|
||||
|
||||
<div class="card-actions">
|
||||
<a href="https://account.nabucasa.com" target="_blank"
|
||||
><mwc-button>Manage Account</mwc-button></a
|
||||
><mwc-button
|
||||
>[[localize('ui.panel.config.cloud.account.manage_account')]]</mwc-button
|
||||
></a
|
||||
>
|
||||
<mwc-button style="float: right" on-click="handleLogout"
|
||||
>Sign out</mwc-button
|
||||
>[[localize('ui.panel.config.cloud.account.sign_out')]]</mwc-button
|
||||
>
|
||||
</div>
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
|
||||
<ha-config-section is-wide="[[isWide]]">
|
||||
<span slot="header">Integrations</span>
|
||||
<span slot="header"
|
||||
>[[localize('ui.panel.config.cloud.account.integrations')]]</span
|
||||
>
|
||||
<div slot="introduction">
|
||||
<p>
|
||||
Integrations for Home Assistant Cloud allow you to connect with
|
||||
services in the cloud without having to expose your Home
|
||||
Assistant instance publicly on the internet.
|
||||
[[localize('ui.panel.config.cloud.account.integrations_introduction')]]
|
||||
</p>
|
||||
<p>
|
||||
Check the website for
|
||||
[[localize('ui.panel.config.cloud.account.integrations_introduction2')]]
|
||||
<a href="https://www.nabucasa.com" target="_blank"
|
||||
>all available features</a
|
||||
>[[localize('ui.panel.config.cloud.account.integrations_link_all_features')]]</a
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
@ -160,7 +166,9 @@ class CloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
}
|
||||
|
||||
_computeRemoteConnected(connected) {
|
||||
return connected ? "Connected" : "Not Connected";
|
||||
return connected
|
||||
? this.hass.localize("ui.panel.config.cloud.account.connected")
|
||||
: this.hass.localize("ui.panel.config.cloud.account.not_connected");
|
||||
}
|
||||
|
||||
async _fetchSubscriptionInfo() {
|
||||
@ -182,7 +190,9 @@ class CloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
|
||||
_formatSubscription(subInfo) {
|
||||
if (subInfo === null) {
|
||||
return "Fetching subscription…";
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.cloud.account.fetching_subscription"
|
||||
);
|
||||
}
|
||||
|
||||
let description = subInfo.human_description;
|
||||
|
@ -31,7 +31,11 @@ export class CloudAlexaPref extends LitElement {
|
||||
const { alexa_enabled, alexa_report_state } = this.cloudStatus!.prefs;
|
||||
|
||||
return html`
|
||||
<ha-card header="Alexa">
|
||||
<ha-card
|
||||
header=${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.alexa.title"
|
||||
)}
|
||||
>
|
||||
<div class="switch">
|
||||
<ha-switch
|
||||
.checked=${alexa_enabled}
|
||||
@ -39,15 +43,16 @@ export class CloudAlexaPref extends LitElement {
|
||||
></ha-switch>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
With the Alexa integration for Home Assistant Cloud you'll be able to
|
||||
control all your Home Assistant devices via any Alexa-enabled device.
|
||||
${this.hass!.localize("ui.panel.config.cloud.account.alexa.info")}
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
href="https://skills-store.amazon.com/deeplink/dp/B0772J1QKB?deviceType=app"
|
||||
target="_blank"
|
||||
>
|
||||
Enable the Home Assistant skill for Alexa
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.alexa.enable_ha_skill"
|
||||
)}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
@ -55,37 +60,48 @@ export class CloudAlexaPref extends LitElement {
|
||||
href="https://www.nabucasa.com/config/amazon_alexa/"
|
||||
target="_blank"
|
||||
>
|
||||
Config documentation
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.alexa.config_documentation"
|
||||
)}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<em
|
||||
>This integration requires an Alexa-enabled device like the Amazon
|
||||
Echo.</em
|
||||
>
|
||||
${alexa_enabled
|
||||
? html`
|
||||
<h3>Enable State Reporting</h3>
|
||||
<div class="state-reporting">
|
||||
<h3>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.alexa.enable_state_reporting"
|
||||
)}
|
||||
</h3>
|
||||
<div class="state-reporting-switch">
|
||||
<ha-switch
|
||||
.checked=${alexa_report_state}
|
||||
@change=${this._reportToggleChanged}
|
||||
></ha-switch>
|
||||
</div>
|
||||
</div>
|
||||
<p>
|
||||
If you enable state reporting, Home Assistant will send
|
||||
<b>all</b> state changes of exposed entities to Amazon. This
|
||||
allows you to always see the latest states in the Alexa app
|
||||
and use the state changes to create routines.
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.alexa.info_state_reporting"
|
||||
)}
|
||||
</p>
|
||||
<ha-switch
|
||||
.checked=${alexa_report_state}
|
||||
@change=${this._reportToggleChanged}
|
||||
></ha-switch>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._handleSync} .disabled=${this._syncing}>
|
||||
Sync Entities
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.alexa.sync_entities"
|
||||
)}
|
||||
</mwc-button>
|
||||
<div class="spacer"></div>
|
||||
<a href="/config/cloud/alexa">
|
||||
<mwc-button>Manage Entities</mwc-button>
|
||||
<mwc-button
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.alexa.manage_entities"
|
||||
)}</mwc-button
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
</ha-card>
|
||||
@ -97,7 +113,11 @@ export class CloudAlexaPref extends LitElement {
|
||||
try {
|
||||
await syncCloudAlexaEntities(this.hass!);
|
||||
} catch (err) {
|
||||
alert(`Failed to sync entities: ${err.body.message}`);
|
||||
alert(
|
||||
`${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.alexa.sync_entities_error"
|
||||
)} ${err.body.message}`
|
||||
);
|
||||
} finally {
|
||||
this._syncing = false;
|
||||
}
|
||||
@ -122,9 +142,15 @@ export class CloudAlexaPref extends LitElement {
|
||||
fireEvent(this, "ha-refresh-cloud-status");
|
||||
} catch (err) {
|
||||
alert(
|
||||
`Unable to ${toggle.checked ? "enable" : "disable"} report state. ${
|
||||
err.message
|
||||
}`
|
||||
`${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.alexa.state_reporting_error",
|
||||
"enable_disable",
|
||||
this.hass!.localize(
|
||||
toggle.checked
|
||||
? "ui.panel.config.cloud.account.alexa.enable"
|
||||
: "ui.panel.config.cloud.account.alexa.disable"
|
||||
)
|
||||
)} ${err.message}`
|
||||
);
|
||||
toggle.checked = !toggle.checked;
|
||||
}
|
||||
@ -149,12 +175,22 @@ export class CloudAlexaPref extends LitElement {
|
||||
.spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
h3 {
|
||||
margin-bottom: 0;
|
||||
.state-reporting {
|
||||
display: flex;
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
h3 + p {
|
||||
.state-reporting + p {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
.state-reporting h3 {
|
||||
flex-grow: 1;
|
||||
margin: 0;
|
||||
}
|
||||
.state-reporting-switch {
|
||||
margin-top: 0.25em;
|
||||
margin-right: 7px;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,11 @@ export class CloudGooglePref extends LitElement {
|
||||
} = this.cloudStatus.prefs;
|
||||
|
||||
return html`
|
||||
<ha-card header="Google Assistant">
|
||||
<ha-card
|
||||
header=${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.google.title"
|
||||
)}
|
||||
>
|
||||
<div class="switch">
|
||||
<ha-switch
|
||||
id="google_enabled"
|
||||
@ -52,16 +56,16 @@ export class CloudGooglePref extends LitElement {
|
||||
></ha-switch>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
With the Google Assistant integration for Home Assistant Cloud you'll
|
||||
be able to control all your Home Assistant devices via any Google
|
||||
Assistant-enabled device.
|
||||
${this.hass!.localize("ui.panel.config.cloud.account.google.info")}
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
href="https://assistant.google.com/services/a/uid/00000091fd5fb875?hl=en-US"
|
||||
target="_blank"
|
||||
>
|
||||
Activate the Home Assistant skill for Google Assistant
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.google.enable_ha_skill"
|
||||
)}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
@ -69,36 +73,49 @@ export class CloudGooglePref extends LitElement {
|
||||
href="https://www.nabucasa.com/config/google_assistant/"
|
||||
target="_blank"
|
||||
>
|
||||
Config documentation
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.google.config_documentation"
|
||||
)}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<em
|
||||
>This integration requires a Google Assistant-enabled device like
|
||||
the Google Home or Android phone.</em
|
||||
>
|
||||
${google_enabled
|
||||
? html`
|
||||
<h3>Enable State Reporting</h3>
|
||||
<div class="state-reporting">
|
||||
<h3>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.google.enable_state_reporting"
|
||||
)}
|
||||
</h3>
|
||||
<div class="state-reporting-switch">
|
||||
<ha-switch
|
||||
.checked=${google_report_state}
|
||||
@change=${this._reportToggleChanged}
|
||||
></ha-switch>
|
||||
</div>
|
||||
</div>
|
||||
<p>
|
||||
If you enable state reporting, Home Assistant will send
|
||||
<b>all</b> state changes of exposed entities to Google. This
|
||||
allows you to always see the latest states in the Google app.
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.google.info_state_reporting"
|
||||
)}
|
||||
</p>
|
||||
<ha-switch
|
||||
.checked=${google_report_state}
|
||||
@change=${this._reportToggleChanged}
|
||||
></ha-switch>
|
||||
|
||||
<div class="secure_devices">
|
||||
Please enter a pin to interact with security devices. Security
|
||||
devices are doors, garage doors and locks. You will be asked
|
||||
to say/enter this pin when interacting with such devices via
|
||||
Google Assistant.
|
||||
<h3>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.google.security_devices"
|
||||
)}
|
||||
</h3>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.google.enter_pin_info"
|
||||
)}
|
||||
<paper-input
|
||||
label="Secure Devices Pin"
|
||||
label="${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.google.devices_pin"
|
||||
)}"
|
||||
id="google_secure_devices_pin"
|
||||
placeholder="Enter a PIN to use secure devices"
|
||||
placeholder="${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.google.enter_pin_hint"
|
||||
)}"
|
||||
.value=${google_secure_devices_pin || ""}
|
||||
@change="${this._pinChanged}"
|
||||
></paper-input>
|
||||
@ -112,11 +129,17 @@ export class CloudGooglePref extends LitElement {
|
||||
.disabled="${!google_enabled}"
|
||||
path="cloud/google_actions/sync"
|
||||
>
|
||||
Sync entities to Google
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.google.sync_entities"
|
||||
)}
|
||||
</ha-call-api-button>
|
||||
<div class="spacer"></div>
|
||||
<a href="/config/cloud/google-assistant">
|
||||
<mwc-button>Manage Entities</mwc-button>
|
||||
<mwc-button
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.google.manage_entities"
|
||||
)}</mwc-button
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
</ha-card>
|
||||
@ -159,7 +182,11 @@ export class CloudGooglePref extends LitElement {
|
||||
showSaveSuccessToast(this, this.hass!);
|
||||
fireEvent(this, "ha-refresh-cloud-status");
|
||||
} catch (err) {
|
||||
alert(`Unable to store pin: ${err.message}`);
|
||||
alert(
|
||||
`${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.google.enter_pin_error"
|
||||
)} ${err.message}`
|
||||
);
|
||||
input.value = this.cloudStatus!.prefs.google_secure_devices_pin;
|
||||
}
|
||||
}
|
||||
@ -179,7 +206,7 @@ export class CloudGooglePref extends LitElement {
|
||||
font-weight: 500;
|
||||
}
|
||||
.secure_devices {
|
||||
padding-top: 16px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
paper-input {
|
||||
width: 250px;
|
||||
@ -193,6 +220,25 @@ export class CloudGooglePref extends LitElement {
|
||||
.spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.state-reporting {
|
||||
display: flex;
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
.state-reporting + p {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
h3 {
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
.state-reporting h3 {
|
||||
flex-grow: 1;
|
||||
margin: 0;
|
||||
}
|
||||
.state-reporting-switch {
|
||||
margin-top: 0.25em;
|
||||
margin-right: 7px;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@ -49,16 +49,26 @@ export class CloudRemotePref extends LitElement {
|
||||
|
||||
if (!remote_certificate) {
|
||||
return html`
|
||||
<ha-card header="Remote Control">
|
||||
<ha-card
|
||||
header=${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.remote.title"
|
||||
)}
|
||||
>
|
||||
<div class="preparing">
|
||||
Remote access is being prepared. We will notify you when it's ready.
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.remote.access_is_being_prepared"
|
||||
)}
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-card header="Remote Control">
|
||||
<ha-card
|
||||
header=${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.remote.title"
|
||||
)}
|
||||
>
|
||||
<div class="switch">
|
||||
<ha-switch
|
||||
.checked="${remote_connected}"
|
||||
@ -66,22 +76,33 @@ export class CloudRemotePref extends LitElement {
|
||||
></ha-switch>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
Home Assistant Cloud provides a secure remote connection to your
|
||||
instance while away from home. Your instance
|
||||
${remote_connected ? "is" : "will be"} available at
|
||||
<a href="https://${remote_domain}" target="_blank">
|
||||
${this.hass!.localize("ui.panel.config.cloud.account.remote.info")}
|
||||
${remote_connected
|
||||
? this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.remote.instance_is_available"
|
||||
)
|
||||
: this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.remote.instance_will_be_available"
|
||||
)}
|
||||
<a href="https://${remote_domain}" target="_blank" class="break-word">
|
||||
https://${remote_domain}</a
|
||||
>.
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<a href="https://www.nabucasa.com/config/remote/" target="_blank">
|
||||
<mwc-button>Learn how it works</mwc-button>
|
||||
<mwc-button
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.remote.link_learn_how_it_works"
|
||||
)}</mwc-button
|
||||
>
|
||||
</a>
|
||||
${remote_certificate
|
||||
? html`
|
||||
<div class="spacer"></div>
|
||||
<mwc-button @click=${this._openCertInfo}>
|
||||
Certificate Info
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.remote.certificate_info"
|
||||
)}
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
@ -120,6 +141,9 @@ export class CloudRemotePref extends LitElement {
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.break-word {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
.switch {
|
||||
position: absolute;
|
||||
right: 24px;
|
||||
|
@ -51,16 +51,20 @@ export class CloudWebhooks extends LitElement {
|
||||
protected render() {
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
<ha-card header="Webhooks">
|
||||
<ha-card
|
||||
header=${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.webhooks.title"
|
||||
)}
|
||||
>
|
||||
<div class="card-content">
|
||||
Anything that is configured to be triggered by a webhook can be given
|
||||
a publicly accessible URL to allow you to send data back to Home
|
||||
Assistant from anywhere, without exposing your instance to the
|
||||
internet. ${this._renderBody()}
|
||||
${this.hass!.localize("ui.panel.config.cloud.account.webhooks.info")}
|
||||
${this._renderBody()}
|
||||
|
||||
<div class="footer">
|
||||
<a href="https://www.nabucasa.com/config/webhooks" target="_blank">
|
||||
Learn more about creating webhook-powered automations.
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.webhooks.link_learn_more"
|
||||
)}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@ -78,16 +82,33 @@ export class CloudWebhooks extends LitElement {
|
||||
private _renderBody() {
|
||||
if (!this.cloudStatus || !this._localHooks || !this._cloudHooks) {
|
||||
return html`
|
||||
<div class="body-text">Loading…</div>
|
||||
<div class="body-text">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.webhooks.loading"
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (this._localHooks.length === 0) {
|
||||
return html`
|
||||
<div class="body-text">
|
||||
Looks like you have no webhooks yet. Get started by configuring a
|
||||
<a href="/config/integrations">webhook-based integration</a> or by
|
||||
creating a <a href="/config/automation/new">webhook automation</a>.
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.webhooks.no_hooks_yet"
|
||||
)}
|
||||
<a href="/config/integrations"
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.webhooks.no_hooks_yet_link_integration"
|
||||
)}</a
|
||||
>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.webhooks.no_hooks_yet2"
|
||||
)}
|
||||
<a href="/config/automation/new"
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.webhooks.no_hooks_yet_link_automation"
|
||||
)}</a
|
||||
>.
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@ -113,7 +134,9 @@ export class CloudWebhooks extends LitElement {
|
||||
: this._cloudHooks![entry.webhook_id]
|
||||
? html`
|
||||
<mwc-button @click="${this._handleManageButton}">
|
||||
Manage
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.webhooks.manage"
|
||||
)}
|
||||
</mwc-button>
|
||||
`
|
||||
: html`
|
||||
@ -171,7 +194,11 @@ export class CloudWebhooks extends LitElement {
|
||||
try {
|
||||
await deleteCloudhook(this.hass!, webhookId!);
|
||||
} catch (err) {
|
||||
alert(`Failed to disable webhook: ${(err as WebhookError).message}`);
|
||||
alert(
|
||||
`${this.hass!.localize(
|
||||
"ui.panel.config.cloud.account.webhooks.disable_hook_error_msg"
|
||||
)} ${(err as WebhookError).message}`
|
||||
);
|
||||
return;
|
||||
} finally {
|
||||
this._progress = this._progress.filter((wid) => wid !== webhookId);
|
||||
|
@ -136,7 +136,7 @@ class CloudAlexa extends LitElement {
|
||||
.checked=${isExposed}
|
||||
@change=${this._exposeChanged}
|
||||
>
|
||||
Expose to Alexa
|
||||
${this.hass!.localize("ui.panel.config.cloud.alexa.expose")}
|
||||
</ha-switch>
|
||||
</div>
|
||||
</ha-card>
|
||||
@ -148,7 +148,9 @@ class CloudAlexa extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
<hass-subpage header="Alexa">
|
||||
<hass-subpage header="${this.hass!.localize(
|
||||
"ui.panel.config.cloud.alexa.title"
|
||||
)}">
|
||||
<span slot="toolbar-icon">
|
||||
${selected}${
|
||||
!this.narrow
|
||||
@ -173,9 +175,7 @@ class CloudAlexa extends LitElement {
|
||||
!emptyFilter
|
||||
? html`
|
||||
<div class="banner">
|
||||
Editing which entities are exposed via this UI is disabled
|
||||
because you have configured entity filters in
|
||||
configuration.yaml.
|
||||
${this.hass!.localize("ui.panel.config.cloud.alexa.banner")}
|
||||
</div>
|
||||
`
|
||||
: ""
|
||||
@ -183,7 +183,11 @@ class CloudAlexa extends LitElement {
|
||||
${
|
||||
exposedCards.length > 0
|
||||
? html`
|
||||
<h1>Exposed entities</h1>
|
||||
<h1>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.alexa.exposed_entities"
|
||||
)}
|
||||
</h1>
|
||||
<div class="content">${exposedCards}</div>
|
||||
`
|
||||
: ""
|
||||
@ -191,7 +195,11 @@ class CloudAlexa extends LitElement {
|
||||
${
|
||||
notExposedCards.length > 0
|
||||
? html`
|
||||
<h1>Not Exposed entities</h1>
|
||||
<h1>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.alexa.not_exposed_entities"
|
||||
)}
|
||||
</h1>
|
||||
<div class="content">${notExposedCards}</div>
|
||||
`
|
||||
: ""
|
||||
|
@ -40,23 +40,38 @@ class DialogCloudCertificate extends LitElement {
|
||||
|
||||
return html`
|
||||
<ha-paper-dialog with-backdrop>
|
||||
<h2>Certificate Information</h2>
|
||||
<h2>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.dialog_certificate.certificate_information"
|
||||
)}
|
||||
</h2>
|
||||
<div>
|
||||
<p>
|
||||
Certificate expiration date:
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.dialog_certificate.certificate_expiration_date"
|
||||
)}
|
||||
${format_date_time(
|
||||
new Date(certificateInfo.expire_date),
|
||||
this.hass!.language
|
||||
)}<br />
|
||||
(Will be automatically renewed)
|
||||
(${this.hass!.localize(
|
||||
"ui.panel.config.cloud.dialog_certificate.will_be_auto_renewed"
|
||||
)})
|
||||
</p>
|
||||
<p>
|
||||
Certificate fingerprint: ${certificateInfo.fingerprint}
|
||||
<p class="break-word">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.dialog_certificate.fingerprint"
|
||||
)}
|
||||
${certificateInfo.fingerprint}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="paper-dialog-buttons">
|
||||
<mwc-button @click="${this._closeDialog}">CLOSE</mwc-button>
|
||||
<mwc-button @click="${this._closeDialog}"
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.cloud.dialog_certificate.close"
|
||||
)}</mwc-button
|
||||
>
|
||||
</div>
|
||||
</ha-paper-dialog>
|
||||
`;
|
||||
@ -77,6 +92,9 @@ class DialogCloudCertificate extends LitElement {
|
||||
ha-paper-dialog {
|
||||
width: 535px;
|
||||
}
|
||||
.break-word {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -50,9 +50,19 @@ export class DialogManageCloudhook extends LitElement {
|
||||
: `https://www.home-assistant.io/integrations/${webhook.domain}/`;
|
||||
return html`
|
||||
<ha-paper-dialog with-backdrop>
|
||||
<h2>Webhook for ${webhook.name}</h2>
|
||||
<h2>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.dialog_cloudhook.webhook_for",
|
||||
"name",
|
||||
webhook.name
|
||||
)}
|
||||
</h2>
|
||||
<div>
|
||||
<p>The webhook is available at the following url:</p>
|
||||
<p>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.dialog_cloudhook.available_at"
|
||||
)}
|
||||
</p>
|
||||
<paper-input
|
||||
label="${inputLabel}"
|
||||
value="${cloudhook.cloudhook_url}"
|
||||
@ -62,13 +72,18 @@ export class DialogManageCloudhook extends LitElement {
|
||||
<p>
|
||||
${cloudhook.managed
|
||||
? html`
|
||||
This webhook is managed by an integration and cannot be
|
||||
disabled.
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.dialog_cloudhook.managed_by_integration"
|
||||
)}
|
||||
`
|
||||
: html`
|
||||
If you no longer want to use this webhook, you can
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.dialog_cloudhook.info_disable_webhook"
|
||||
)}
|
||||
<button class="link" @click="${this._disableWebhook}">
|
||||
disable it</button
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.dialog_cloudhook.link_disable_webhook"
|
||||
)}</button
|
||||
>.
|
||||
`}
|
||||
</p>
|
||||
@ -76,9 +91,17 @@ export class DialogManageCloudhook extends LitElement {
|
||||
|
||||
<div class="paper-dialog-buttons">
|
||||
<a href="${docsUrl}" target="_blank">
|
||||
<mwc-button>VIEW DOCUMENTATION</mwc-button>
|
||||
<mwc-button
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.cloud.dialog_cloudhook.view_documentation"
|
||||
)}</mwc-button
|
||||
>
|
||||
</a>
|
||||
<mwc-button @click="${this._closeDialog}">CLOSE</mwc-button>
|
||||
<mwc-button @click="${this._closeDialog}"
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.cloud.dialog_cloudhook.close"
|
||||
)}</mwc-button
|
||||
>
|
||||
</div>
|
||||
</ha-paper-dialog>
|
||||
`;
|
||||
@ -97,7 +120,13 @@ export class DialogManageCloudhook extends LitElement {
|
||||
}
|
||||
|
||||
private async _disableWebhook() {
|
||||
if (!confirm("Are you sure you want to disable this webhook?")) {
|
||||
if (
|
||||
!confirm(
|
||||
this.hass!.localize(
|
||||
"ui.panel.config.cloud.dialog_cloudhook.confirm_disable"
|
||||
)
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -113,7 +142,9 @@ export class DialogManageCloudhook extends LitElement {
|
||||
input.setSelectionRange(0, input.value.length);
|
||||
try {
|
||||
document.execCommand("copy");
|
||||
paperInput.label = "COPIED TO CLIPBOARD";
|
||||
paperInput.label = this.hass!.localize(
|
||||
"ui.panel.config.cloud.dialog_cloudhook.copied_to_clipboard"
|
||||
);
|
||||
} catch (err) {
|
||||
// Copying failed. Oh no
|
||||
}
|
||||
|
@ -7,11 +7,12 @@ import "../../../../components/buttons/ha-progress-button";
|
||||
import "../../../../layouts/hass-subpage";
|
||||
import "../../../../resources/ha-style";
|
||||
import { EventsMixin } from "../../../../mixins/events-mixin";
|
||||
|
||||
import LocalizeMixin from "../../../../mixins/localize-mixin";
|
||||
/*
|
||||
* @appliesMixin EventsMixin
|
||||
* @appliesMixin LocalizeMixin
|
||||
*/
|
||||
class CloudForgotPassword extends EventsMixin(PolymerElement) {
|
||||
class CloudForgotPassword extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="iron-flex ha-style">
|
||||
@ -44,30 +45,29 @@ class CloudForgotPassword extends EventsMixin(PolymerElement) {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<hass-subpage header="Forgot Password">
|
||||
<hass-subpage header=[[localize('ui.panel.config.cloud.forgot_password.title')]]>
|
||||
<div class="content">
|
||||
<ha-card header="Forgot your password">
|
||||
<ha-card header=[[localize('ui.panel.config.cloud.forgot_password.subtitle')]]>
|
||||
<div class="card-content">
|
||||
<p>
|
||||
Enter your email address and we will send you a link to reset
|
||||
your password.
|
||||
[[localize('ui.panel.config.cloud.forgot_password.instructions')]]
|
||||
</p>
|
||||
<div class="error" hidden$="[[!_error]]">[[_error]]</div>
|
||||
<paper-input
|
||||
autofocus=""
|
||||
id="email"
|
||||
label="E-mail"
|
||||
label="[[localize('ui.panel.config.cloud.forgot_password.email')]]"
|
||||
value="{{email}}"
|
||||
type="email"
|
||||
on-keydown="_keyDown"
|
||||
error-message="Invalid email"
|
||||
error-message="[[localize('ui.panel.config.cloud.forgot_password.email_error_msg')]]"
|
||||
></paper-input>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button
|
||||
on-click="_handleEmailPasswordReset"
|
||||
progress="[[_requestInProgress]]"
|
||||
>Send reset email</ha-progress-button
|
||||
>[[localize('ui.panel.config.cloud.forgot_password.send_reset_email')]]</ha-progress-button
|
||||
>
|
||||
</div>
|
||||
</ha-card>
|
||||
@ -126,7 +126,7 @@ class CloudForgotPassword extends EventsMixin(PolymerElement) {
|
||||
this._requestInProgress = false;
|
||||
this.fire("cloud-done", {
|
||||
flashMessage:
|
||||
"Check your email for instructions on how to reset your password.",
|
||||
"[[localize('ui.panel.config.cloud.forgot_password.check_your_email')]]",
|
||||
});
|
||||
},
|
||||
(err) =>
|
||||
|
@ -132,7 +132,7 @@ class CloudGoogleAssistant extends LitElement {
|
||||
.checked=${isExposed}
|
||||
@change=${this._exposeChanged}
|
||||
>
|
||||
Expose to Google Assistant
|
||||
${this.hass!.localize("ui.panel.config.cloud.google.expose")}
|
||||
</ha-switch>
|
||||
${entity.might_2fa
|
||||
? html`
|
||||
@ -141,7 +141,9 @@ class CloudGoogleAssistant extends LitElement {
|
||||
.checked=${Boolean(config.disable_2fa)}
|
||||
@change=${this._disable2FAChanged}
|
||||
>
|
||||
Disable two factor authentication
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.google.disable_2FA"
|
||||
)}
|
||||
</ha-switch>
|
||||
`
|
||||
: ""}
|
||||
@ -155,7 +157,9 @@ class CloudGoogleAssistant extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
<hass-subpage header="Google Assistant">
|
||||
<hass-subpage header="${this.hass!.localize(
|
||||
"ui.panel.config.cloud.google.title"
|
||||
)}">
|
||||
<span slot="toolbar-icon">
|
||||
${selected}${
|
||||
!this.narrow
|
||||
@ -180,9 +184,7 @@ class CloudGoogleAssistant extends LitElement {
|
||||
!emptyFilter
|
||||
? html`
|
||||
<div class="banner">
|
||||
Editing which entities are exposed via this UI is disabled
|
||||
because you have configured entity filters in
|
||||
configuration.yaml.
|
||||
${this.hass!.localize("ui.panel.config.cloud.google.banner")}
|
||||
</div>
|
||||
`
|
||||
: ""
|
||||
@ -190,7 +192,11 @@ class CloudGoogleAssistant extends LitElement {
|
||||
${
|
||||
exposedCards.length > 0
|
||||
? html`
|
||||
<h1>Exposed entities</h1>
|
||||
<h1>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.google.exposed_entities"
|
||||
)}
|
||||
</h1>
|
||||
<div class="content">${exposedCards}</div>
|
||||
`
|
||||
: ""
|
||||
@ -198,7 +204,11 @@ class CloudGoogleAssistant extends LitElement {
|
||||
${
|
||||
notExposedCards.length > 0
|
||||
? html`
|
||||
<h1>Not Exposed entities</h1>
|
||||
<h1>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.google.not_exposed_entities"
|
||||
)}
|
||||
</h1>
|
||||
<div class="content">${notExposedCards}</div>
|
||||
`
|
||||
: ""
|
||||
@ -323,7 +333,11 @@ class CloudGoogleAssistant extends LitElement {
|
||||
window.addEventListener(
|
||||
"popstate",
|
||||
() => {
|
||||
showToast(parent, { message: "Synchronizing changes to Google." });
|
||||
showToast(parent, {
|
||||
message: this.hass!.localize(
|
||||
"ui.panel.config.cloud.googe.sync_to_google"
|
||||
),
|
||||
});
|
||||
cloudSyncGoogleAssistant(this.hass);
|
||||
},
|
||||
{ once: true }
|
||||
|
@ -16,11 +16,15 @@ import "../../ha-config-section";
|
||||
import { EventsMixin } from "../../../../mixins/events-mixin";
|
||||
import NavigateMixin from "../../../../mixins/navigate-mixin";
|
||||
import "../../../../components/ha-icon-next";
|
||||
import LocalizeMixin from "../../../../mixins/localize-mixin";
|
||||
/*
|
||||
* @appliesMixin NavigateMixin
|
||||
* @appliesMixin EventsMixin
|
||||
* @appliesMixin LocalizeMixin
|
||||
*/
|
||||
class CloudLogin extends NavigateMixin(EventsMixin(PolymerElement)) {
|
||||
class CloudLogin extends LocalizeMixin(
|
||||
NavigateMixin(EventsMixin(PolymerElement))
|
||||
) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="iron-flex ha-style">
|
||||
@ -68,31 +72,29 @@ class CloudLogin extends NavigateMixin(EventsMixin(PolymerElement)) {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
</style>
|
||||
<hass-subpage header="Cloud Login">
|
||||
<hass-subpage header="[[localize('ui.panel.config.cloud.caption')]]">
|
||||
<div class="content">
|
||||
<ha-config-section is-wide="[[isWide]]">
|
||||
<span slot="header">Home Assistant Cloud</span>
|
||||
<span slot="header"
|
||||
>[[localize('ui.panel.config.cloud.caption')]]</span
|
||||
>
|
||||
<div slot="introduction">
|
||||
<p>
|
||||
Home Assistant Cloud provides you with a secure remote
|
||||
connection to your instance while away from home. It also allows
|
||||
you to connect with cloud-only services: Amazon Alexa and Google
|
||||
Assistant.
|
||||
[[localize('ui.panel.config.cloud.login.introduction')]]
|
||||
</p>
|
||||
<p>
|
||||
This service is run by our partner
|
||||
[[localize('ui.panel.config.cloud.login.introduction2')]]
|
||||
<a href="https://www.nabucasa.com" target="_blank"
|
||||
>Nabu Casa, Inc</a
|
||||
>, a company founded by the founders of Home Assistant and
|
||||
Hass.io.
|
||||
>
|
||||
[[localize('ui.panel.config.cloud.login.introduction2a')]]
|
||||
</p>
|
||||
<p>
|
||||
Home Assistant Cloud is a subscription service with a free one
|
||||
month trial. No payment information necessary.
|
||||
[[localize('ui.panel.config.cloud.login.introduction3')]]
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://www.nabucasa.com" target="_blank"
|
||||
>Learn more about Home Assistant Cloud</a
|
||||
>[[localize('ui.panel.config.cloud.login.learn_more_link')]]</a
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
@ -101,44 +103,46 @@ class CloudLogin extends NavigateMixin(EventsMixin(PolymerElement)) {
|
||||
<div class="card-content flash-msg">
|
||||
[[flashMessage]]
|
||||
<paper-icon-button icon="hass:close" on-click="_dismissFlash"
|
||||
>Dismiss</paper-icon-button
|
||||
>[[localize('ui.panel.config.cloud.login.dismiss')]]</paper-icon-button
|
||||
>
|
||||
<paper-ripple id="flashRipple" noink=""></paper-ripple>
|
||||
</div>
|
||||
</ha-card>
|
||||
|
||||
<ha-card header="Sign in">
|
||||
<ha-card
|
||||
header="[[localize('ui.panel.config.cloud.login.sign_in')]]"
|
||||
>
|
||||
<div class="card-content">
|
||||
<div class="error" hidden$="[[!_error]]">[[_error]]</div>
|
||||
<paper-input
|
||||
label="Email"
|
||||
label="[[localize('ui.panel.config.cloud.login.email')]]"
|
||||
id="email"
|
||||
type="email"
|
||||
value="{{email}}"
|
||||
on-keydown="_keyDown"
|
||||
error-message="Invalid email"
|
||||
error-message="[[localize('ui.panel.config.cloud.login.email_error_msg')]]"
|
||||
></paper-input>
|
||||
<paper-input
|
||||
id="password"
|
||||
label="Password"
|
||||
label="[[localize('ui.panel.config.cloud.login.password')]]"
|
||||
value="{{_password}}"
|
||||
type="password"
|
||||
on-keydown="_keyDown"
|
||||
error-message="Passwords are at least 8 characters"
|
||||
error-message="[[localize('ui.panel.config.cloud.login.password_error_msg')]]"
|
||||
></paper-input>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button
|
||||
on-click="_handleLogin"
|
||||
progress="[[_requestInProgress]]"
|
||||
>Sign in</ha-progress-button
|
||||
>[[localize('ui.panel.config.cloud.login.sign_in')]]</ha-progress-button
|
||||
>
|
||||
<button
|
||||
class="link"
|
||||
hidden="[[_requestInProgress]]"
|
||||
on-click="_handleForgotPassword"
|
||||
>
|
||||
forgot password?
|
||||
[[localize('ui.panel.config.cloud.login.forgot_password')]]
|
||||
</button>
|
||||
</div>
|
||||
</ha-card>
|
||||
@ -146,8 +150,10 @@ class CloudLogin extends NavigateMixin(EventsMixin(PolymerElement)) {
|
||||
<ha-card>
|
||||
<paper-item on-click="_handleRegister">
|
||||
<paper-item-body two-line="">
|
||||
Start your free 1 month trial
|
||||
<div secondary="">No payment information necessary</div>
|
||||
[[localize('ui.panel.config.cloud.login.start_trial')]]
|
||||
<div secondary="">
|
||||
[[localize('ui.panel.config.cloud.login.trial_info')]]
|
||||
</div>
|
||||
</paper-item-body>
|
||||
<ha-icon-next></ha-icon-next>
|
||||
</paper-item>
|
||||
@ -251,7 +257,9 @@ class CloudLogin extends NavigateMixin(EventsMixin(PolymerElement)) {
|
||||
|
||||
const errCode = err && err.body && err.body.code;
|
||||
if (errCode === "PasswordChangeRequired") {
|
||||
alert("You need to change your password before logging in.");
|
||||
alert(
|
||||
"[[localize('ui.panel.config.cloud.login.alert_password_change_required')]]"
|
||||
);
|
||||
this.navigate("/config/cloud/forgot-password");
|
||||
return;
|
||||
}
|
||||
@ -265,7 +273,8 @@ class CloudLogin extends NavigateMixin(EventsMixin(PolymerElement)) {
|
||||
};
|
||||
|
||||
if (errCode === "UserNotConfirmed") {
|
||||
props._error = "You need to confirm your email before logging in.";
|
||||
props._error =
|
||||
"[[localize('ui.panel.config.cloud.login.alert_email_confirm_necessary')]]";
|
||||
}
|
||||
|
||||
this.setProperties(props);
|
||||
|
@ -8,11 +8,13 @@ import "../../../../layouts/hass-subpage";
|
||||
import "../../../../resources/ha-style";
|
||||
import "../../ha-config-section";
|
||||
import { EventsMixin } from "../../../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../../../mixins/localize-mixin";
|
||||
|
||||
/*
|
||||
* @appliesMixin EventsMixin
|
||||
* @appliesMixin LocalizeMixin
|
||||
*/
|
||||
class CloudRegister extends EventsMixin(PolymerElement) {
|
||||
class CloudRegister extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="iron-flex ha-style">
|
||||
@ -48,47 +50,47 @@ class CloudRegister extends EventsMixin(PolymerElement) {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<hass-subpage header="Register Account">
|
||||
<hass-subpage header="[[localize('ui.panel.config.cloud.register.title')]]">
|
||||
<div class="content">
|
||||
<ha-config-section is-wide="[[isWide]]">
|
||||
<span slot="header">Start your free trial</span>
|
||||
<span slot="header">[[localize('ui.panel.config.cloud.register.headline')]]</span>
|
||||
<div slot="introduction">
|
||||
<p>
|
||||
Create an account to start your free one month trial with Home Assistant Cloud. No payment information necessary.
|
||||
[[localize('ui.panel.config.cloud.register.information')]]
|
||||
</p>
|
||||
<p>
|
||||
The trial will give you access to all the benefits of Home Assistant Cloud, including:
|
||||
[[localize('ui.panel.config.cloud.register.information2')]]
|
||||
</p>
|
||||
<ul>
|
||||
<li>Control of Home Assistant away from home</li>
|
||||
<li>Integration with Google Assistant</li>
|
||||
<li>Integration with Amazon Alexa</li>
|
||||
<li>Easy integration with webhook-based apps like OwnTracks</li>
|
||||
<li>[[localize('ui.panel.config.cloud.register.feature_remote_control')]]</li>
|
||||
<li>[[localize('ui.panel.config.cloud.register.feature_google_home')]]</li>
|
||||
<li>[[localize('ui.panel.config.cloud.register.feature_amazon_alexa')]]</li>
|
||||
<li>[[localize('ui.panel.config.cloud.register.feature_webhook_apps')]]</li>
|
||||
</ul>
|
||||
<p>
|
||||
This service is run by our partner <a href='https://www.nabucasa.com' target='_blank'>Nabu Casa, Inc</a>, a company founded by the founders of Home Assistant and Hass.io.
|
||||
[[localize('ui.panel.config.cloud.register.information3')]] <a href='https://www.nabucasa.com' target='_blank'>Nabu Casa, Inc</a>[[localize('ui.panel.config.cloud.register.information3a')]]
|
||||
</p>
|
||||
|
||||
<p>
|
||||
By registering an account you agree to the following terms and conditions.
|
||||
[[localize('ui.panel.config.cloud.register.information4')]]
|
||||
</p><ul>
|
||||
<li><a href="https://home-assistant.io/tos/" target="_blank">Terms and Conditions</a></li>
|
||||
<li><a href="https://home-assistant.io/privacy/" target="_blank">Privacy Policy</a></li>
|
||||
<li><a href="https://home-assistant.io/tos/" target="_blank">[[localize('ui.panel.config.cloud.register.link_terms_conditions')]]</a></li>
|
||||
<li><a href="https://home-assistant.io/privacy/" target="_blank">[[localize('ui.panel.config.cloud.register.link_privacy_policy')]]</a></li>
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ha-card header="Create Account">
|
||||
<ha-card header="[[localize('ui.panel.config.cloud.register.create_account')]]">
|
||||
<div class="card-content">
|
||||
<div class="header">
|
||||
<div class="error" hidden$="[[!_error]]">[[_error]]</div>
|
||||
</div>
|
||||
<paper-input autofocus="" id="email" label="Email address" type="email" value="{{email}}" on-keydown="_keyDown" error-message="Invalid email"></paper-input>
|
||||
<paper-input id="password" label="Password" value="{{_password}}" type="password" on-keydown="_keyDown" error-message="Your password needs to be at least 8 characters"></paper-input>
|
||||
<paper-input autofocus="" id="email" label="[[localize('ui.panel.config.cloud.register.email_address')]]" type="email" value="{{email}}" on-keydown="_keyDown" error-message="[[localize('ui.panel.config.cloud.register.email_error_msg')]]"></paper-input>
|
||||
<paper-input id="password" label="Password" value="{{_password}}" type="password" on-keydown="_keyDown" error-message="[[localize('ui.panel.config.cloud.register.password_error_msg')]]"></paper-input>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button on-click="_handleRegister" progress="[[_requestInProgress]]">Start trial</ha-progress-button>
|
||||
<button class="link" hidden="[[_requestInProgress]]" on-click="_handleResendVerifyEmail">Resend confirmation email</button>
|
||||
<ha-progress-button on-click="_handleRegister" progress="[[_requestInProgress]]">[[localize('ui.panel.config.cloud.register.start_trial')]]</ha-progress-button>
|
||||
<button class="link" hidden="[[_requestInProgress]]" on-click="_handleResendVerifyEmail">[[localize('ui.panel.config.cloud.register.resend_confirmation_email')]]</button>
|
||||
</div>
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
@ -211,8 +213,9 @@ class CloudRegister extends EventsMixin(PolymerElement) {
|
||||
_password: "",
|
||||
});
|
||||
this.fire("cloud-done", {
|
||||
flashMessage:
|
||||
"Account created! Check your email for instructions on how to activate your account.",
|
||||
flashMessage: this.hass.localize(
|
||||
"ui.panel.config.cloud.register.account_created"
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,35 +1,17 @@
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../layouts/hass-subpage";
|
||||
|
||||
import { EventsMixin } from "../../../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../../../mixins/localize-mixin";
|
||||
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
||||
import "../../../../components/entity/state-badge";
|
||||
import { compare } from "../../../../common/string/compare";
|
||||
import {
|
||||
subscribeDeviceRegistry,
|
||||
updateDeviceRegistryEntry,
|
||||
} from "../../../../data/device_registry";
|
||||
import { subscribeAreaRegistry } from "../../../../data/area_registry";
|
||||
import { updateDeviceRegistryEntry } from "../../../../data/device_registry";
|
||||
import {
|
||||
loadDeviceRegistryDetailDialog,
|
||||
showDeviceRegistryDetailDialog,
|
||||
} from "../../../../dialogs/device-registry-detail/show-dialog-device-registry-detail";
|
||||
|
||||
function computeEntityName(hass, entity) {
|
||||
if (entity.name) return entity.name;
|
||||
const state = hass.states[entity.entity_id];
|
||||
return state ? computeStateName(state) : null;
|
||||
}
|
||||
|
||||
/*
|
||||
* @appliesMixin EventsMixin
|
||||
*/
|
||||
@ -37,10 +19,6 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
:host(:not([narrow])) .device-entities {
|
||||
max-height: 225px;
|
||||
overflow: auto;
|
||||
}
|
||||
ha-card {
|
||||
flex: 1 0 100%;
|
||||
padding-bottom: 10px;
|
||||
@ -70,11 +48,6 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
.extra-info {
|
||||
margin-top: 8px;
|
||||
}
|
||||
paper-icon-item {
|
||||
cursor: pointer;
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
.manuf,
|
||||
.entity-id,
|
||||
.area {
|
||||
@ -82,15 +55,6 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
}
|
||||
</style>
|
||||
<ha-card>
|
||||
<div class="card-header">
|
||||
<template is="dom-if" if="[[!hideSettings]]">
|
||||
<div class="name">[[_deviceName(device)]]</div>
|
||||
<paper-icon-button
|
||||
icon="hass:settings"
|
||||
on-click="_gotoSettings"
|
||||
></paper-icon-button>
|
||||
</template>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="info">
|
||||
<div class="model">[[device.model]]</div>
|
||||
@ -122,27 +86,6 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<template is="dom-if" if="[[!hideEntities]]">
|
||||
<div class="device-entities">
|
||||
<template
|
||||
is="dom-repeat"
|
||||
items="[[_computeDeviceEntities(hass, device, entities)]]"
|
||||
as="entity"
|
||||
>
|
||||
<paper-icon-item on-click="_openMoreInfo">
|
||||
<state-badge
|
||||
state-obj="[[_computeStateObj(entity, hass)]]"
|
||||
slot="item-icon"
|
||||
></state-badge>
|
||||
<paper-item-body>
|
||||
<div class="name">[[_computeEntityName(entity, hass)]]</div>
|
||||
<div class="secondary entity-id">[[entity.entity_id]]</div>
|
||||
</paper-item-body>
|
||||
</paper-icon-item>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
@ -152,14 +95,11 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
device: Object,
|
||||
devices: Array,
|
||||
areas: Array,
|
||||
entities: Array,
|
||||
hass: Object,
|
||||
narrow: {
|
||||
type: Boolean,
|
||||
reflectToAttribute: true,
|
||||
},
|
||||
hideSettings: { type: Boolean, value: false },
|
||||
hideEntities: { type: Boolean, value: false },
|
||||
_childDevices: {
|
||||
type: Array,
|
||||
computed: "_computeChildDevices(device, devices)",
|
||||
@ -172,30 +112,6 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
loadDeviceRegistryDetailDialog();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._unsubAreas = subscribeAreaRegistry(this.hass.connection, (areas) => {
|
||||
this._areas = areas;
|
||||
});
|
||||
this._unsubDevices = subscribeDeviceRegistry(
|
||||
this.hass.connection,
|
||||
(devices) => {
|
||||
this.devices = devices;
|
||||
this.device = devices.find((device) => device.id === this.device.id);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
if (this._unsubAreas) {
|
||||
this._unsubAreas();
|
||||
}
|
||||
if (this._unsubDevices) {
|
||||
this._unsubDevices();
|
||||
}
|
||||
}
|
||||
|
||||
_computeArea(areas, device) {
|
||||
if (!areas || !device || !device.area_id) {
|
||||
return "No Area";
|
||||
@ -210,30 +126,6 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
.sort((dev1, dev2) => compare(dev1.name, dev2.name));
|
||||
}
|
||||
|
||||
_computeDeviceEntities(hass, device, entities) {
|
||||
return entities
|
||||
.filter((entity) => entity.device_id === device.id)
|
||||
.sort((ent1, ent2) =>
|
||||
compare(
|
||||
computeEntityName(hass, ent1) || `zzz${ent1.entity_id}`,
|
||||
computeEntityName(hass, ent2) || `zzz${ent2.entity_id}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
_computeStateObj(entity, hass) {
|
||||
return hass.states[entity.entity_id];
|
||||
}
|
||||
|
||||
_computeEntityName(entity, hass) {
|
||||
return (
|
||||
computeEntityName(hass, entity) ||
|
||||
`(${this.localize(
|
||||
"ui.panel.config.integrations.config_entry.entity_unavailable"
|
||||
)})`
|
||||
);
|
||||
}
|
||||
|
||||
_deviceName(device) {
|
||||
return device.name_by_user || device.name;
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import "../../../layouts/hass-subpage";
|
||||
import "../../../layouts/hass-error-screen";
|
||||
import "../ha-config-section";
|
||||
|
||||
import "./device-detail/ha-device-card";
|
||||
import "./device-detail/ha-device-triggers-card";
|
||||
import "./device-detail/ha-device-conditions-card";
|
||||
import "./device-detail/ha-device-actions-card";
|
||||
@ -144,9 +145,6 @@ export class HaConfigDevicePage extends LitElement {
|
||||
.areas=${this.areas}
|
||||
.devices=${this.devices}
|
||||
.device=${device}
|
||||
.entities=${this.entities}
|
||||
hide-settings
|
||||
hide-entities
|
||||
></ha-device-card>
|
||||
|
||||
${entities.length
|
||||
|
@ -1,19 +1,5 @@
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/iron-icon/iron-icon";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/data-table/ha-data-table";
|
||||
import "../../../components/entity/ha-state-icon";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import "../../../resources/ha-style";
|
||||
import "../../../components/ha-icon-next";
|
||||
|
||||
import "../ha-config-section";
|
||||
|
||||
import memoizeOne from "memoize-one";
|
||||
import "./ha-devices-data-table";
|
||||
|
||||
import {
|
||||
LitElement,
|
||||
@ -21,33 +7,14 @@ import {
|
||||
TemplateResult,
|
||||
property,
|
||||
customElement,
|
||||
CSSResult,
|
||||
css,
|
||||
} from "lit-element";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
// tslint:disable-next-line
|
||||
import {
|
||||
DataTabelColumnContainer,
|
||||
RowClickedEvent,
|
||||
DataTabelRowData,
|
||||
} from "../../../components/data-table/ha-data-table";
|
||||
// tslint:disable-next-line
|
||||
import { DeviceRegistryEntry } from "../../../data/device_registry";
|
||||
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||
import { ConfigEntry } from "../../../data/config_entries";
|
||||
import { AreaRegistryEntry } from "../../../data/area_registry";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
|
||||
interface DeviceRowData extends DeviceRegistryEntry {
|
||||
device?: DeviceRowData;
|
||||
area?: string;
|
||||
integration?: string;
|
||||
battery_entity?: string;
|
||||
}
|
||||
|
||||
interface DeviceEntityLookup {
|
||||
[deviceId: string]: EntityRegistryEntry[];
|
||||
}
|
||||
|
||||
@customElement("ha-config-devices-dashboard")
|
||||
export class HaConfigDeviceDashboard extends LitElement {
|
||||
@ -59,234 +26,35 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
@property() public areas!: AreaRegistryEntry[];
|
||||
@property() public domain!: string;
|
||||
|
||||
private _devices = memoizeOne(
|
||||
(
|
||||
devices: DeviceRegistryEntry[],
|
||||
entries: ConfigEntry[],
|
||||
entities: EntityRegistryEntry[],
|
||||
areas: AreaRegistryEntry[],
|
||||
domain: string,
|
||||
localize: LocalizeFunc
|
||||
) => {
|
||||
// Some older installations might have devices pointing at invalid entryIDs
|
||||
// So we guard for that.
|
||||
|
||||
let outputDevices: DeviceRowData[] = devices;
|
||||
|
||||
const deviceLookup: { [deviceId: string]: DeviceRegistryEntry } = {};
|
||||
for (const device of devices) {
|
||||
deviceLookup[device.id] = device;
|
||||
}
|
||||
|
||||
const deviceEntityLookup: DeviceEntityLookup = {};
|
||||
for (const entity of entities) {
|
||||
if (!entity.device_id) {
|
||||
continue;
|
||||
}
|
||||
if (!(entity.device_id in deviceEntityLookup)) {
|
||||
deviceEntityLookup[entity.device_id] = [];
|
||||
}
|
||||
deviceEntityLookup[entity.device_id].push(entity);
|
||||
}
|
||||
|
||||
const entryLookup: { [entryId: string]: ConfigEntry } = {};
|
||||
for (const entry of entries) {
|
||||
entryLookup[entry.entry_id] = entry;
|
||||
}
|
||||
|
||||
const areaLookup: { [areaId: string]: AreaRegistryEntry } = {};
|
||||
for (const area of areas) {
|
||||
areaLookup[area.area_id] = area;
|
||||
}
|
||||
|
||||
if (domain) {
|
||||
outputDevices = outputDevices.filter((device) =>
|
||||
device.config_entries.find(
|
||||
(entryId) =>
|
||||
entryId in entryLookup && entryLookup[entryId].domain === domain
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
outputDevices = outputDevices.map((device) => {
|
||||
return {
|
||||
...device,
|
||||
name:
|
||||
device.name_by_user ||
|
||||
device.name ||
|
||||
this._fallbackDeviceName(device.id, deviceEntityLookup) ||
|
||||
"No name",
|
||||
model: device.model || "<unknown>",
|
||||
manufacturer: device.manufacturer || "<unknown>",
|
||||
area: device.area_id ? areaLookup[device.area_id].name : "No area",
|
||||
integration: device.config_entries.length
|
||||
? device.config_entries
|
||||
.filter((entId) => entId in entryLookup)
|
||||
.map(
|
||||
(entId) =>
|
||||
localize(
|
||||
`component.${entryLookup[entId].domain}.config.title`
|
||||
) || entryLookup[entId].domain
|
||||
)
|
||||
.join(", ")
|
||||
: "No integration",
|
||||
battery_entity: this._batteryEntity(device.id, deviceEntityLookup),
|
||||
};
|
||||
});
|
||||
|
||||
return outputDevices;
|
||||
}
|
||||
);
|
||||
|
||||
private _columns = memoizeOne(
|
||||
(narrow: boolean): DataTabelColumnContainer =>
|
||||
narrow
|
||||
? {
|
||||
device: {
|
||||
title: "Device",
|
||||
sortable: true,
|
||||
filterKey: "name",
|
||||
filterable: true,
|
||||
direction: "asc",
|
||||
template: (device: DeviceRowData) => {
|
||||
const battery = device.battery_entity
|
||||
? this.hass.states[device.battery_entity]
|
||||
: undefined;
|
||||
// Have to work on a nice layout for mobile
|
||||
return html`
|
||||
${device.name_by_user || device.name}<br />
|
||||
${device.area} | ${device.integration}<br />
|
||||
${battery
|
||||
? html`
|
||||
${battery.state}%
|
||||
<ha-state-icon
|
||||
.hass=${this.hass!}
|
||||
.stateObj=${battery}
|
||||
></ha-state-icon>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
},
|
||||
},
|
||||
}
|
||||
: {
|
||||
device_name: {
|
||||
title: "Device",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
direction: "asc",
|
||||
},
|
||||
manufacturer: {
|
||||
title: "Manufacturer",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
},
|
||||
model: {
|
||||
title: "Model",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
},
|
||||
area: {
|
||||
title: "Area",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
},
|
||||
integration: {
|
||||
title: "Integration",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
},
|
||||
battery: {
|
||||
title: "Battery",
|
||||
sortable: true,
|
||||
type: "numeric",
|
||||
template: (batteryEntity: string) => {
|
||||
const battery = batteryEntity
|
||||
? this.hass.states[batteryEntity]
|
||||
: undefined;
|
||||
return battery
|
||||
? html`
|
||||
${battery.state}%
|
||||
<ha-state-icon
|
||||
.hass=${this.hass!}
|
||||
.stateObj=${battery}
|
||||
></ha-state-icon>
|
||||
`
|
||||
: html`
|
||||
-
|
||||
`;
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<hass-subpage
|
||||
header=${this.hass.localize("ui.panel.config.devices.caption")}
|
||||
>
|
||||
<ha-data-table
|
||||
.columns=${this._columns(this.narrow)}
|
||||
.data=${this._devices(
|
||||
this.devices,
|
||||
this.entries,
|
||||
this.entities,
|
||||
this.areas,
|
||||
this.domain,
|
||||
this.hass.localize
|
||||
).map((device: DeviceRowData) => {
|
||||
// We don't need a lot of this data for mobile view, but kept it for filtering...
|
||||
const data: DataTabelRowData = {
|
||||
device_name: device.name,
|
||||
id: device.id,
|
||||
manufacturer: device.manufacturer,
|
||||
model: device.model,
|
||||
area: device.area,
|
||||
integration: device.integration,
|
||||
};
|
||||
if (this.narrow) {
|
||||
data.device = device;
|
||||
return data;
|
||||
}
|
||||
data.battery = device.battery_entity;
|
||||
return data;
|
||||
})}
|
||||
@row-click=${this._handleRowClicked}
|
||||
></ha-data-table>
|
||||
<div class="content">
|
||||
<ha-devices-data-table
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.devices=${this.devices}
|
||||
.entries=${this.entries}
|
||||
.entities=${this.entities}
|
||||
.areas=${this.areas}
|
||||
.domain=${this.domain}
|
||||
></ha-devices-data-table>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
private _batteryEntity(
|
||||
deviceId: string,
|
||||
deviceEntityLookup: DeviceEntityLookup
|
||||
): string | undefined {
|
||||
const batteryEntity = (deviceEntityLookup[deviceId] || []).find(
|
||||
(entity) =>
|
||||
this.hass.states[entity.entity_id] &&
|
||||
this.hass.states[entity.entity_id].attributes.device_class === "battery"
|
||||
);
|
||||
|
||||
return batteryEntity ? batteryEntity.entity_id : undefined;
|
||||
}
|
||||
|
||||
private _fallbackDeviceName(
|
||||
deviceId: string,
|
||||
deviceEntityLookup: DeviceEntityLookup
|
||||
): string | undefined {
|
||||
for (const entity of deviceEntityLookup[deviceId] || []) {
|
||||
const stateObj = this.hass.states[entity.entity_id];
|
||||
if (stateObj) {
|
||||
return computeStateName(stateObj);
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
.content {
|
||||
padding: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _handleRowClicked(ev: CustomEvent) {
|
||||
const deviceId = (ev.detail as RowClickedEvent).id;
|
||||
navigate(this, `/config/devices/device/${deviceId}`);
|
||||
ha-devices-data-table {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
|
265
src/panels/config/devices/ha-devices-data-table.ts
Normal file
265
src/panels/config/devices/ha-devices-data-table.ts
Normal file
@ -0,0 +1,265 @@
|
||||
import "../../../components/data-table/ha-data-table";
|
||||
import "../../../components/entity/ha-state-icon";
|
||||
|
||||
import memoizeOne from "memoize-one";
|
||||
|
||||
import {
|
||||
LitElement,
|
||||
html,
|
||||
TemplateResult,
|
||||
property,
|
||||
customElement,
|
||||
} from "lit-element";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
// tslint:disable-next-line
|
||||
import {
|
||||
DataTableColumnContainer,
|
||||
RowClickedEvent,
|
||||
DataTableRowData,
|
||||
} from "../../../components/data-table/ha-data-table";
|
||||
// tslint:disable-next-line
|
||||
import { DeviceRegistryEntry } from "../../../data/device_registry";
|
||||
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||
import { ConfigEntry } from "../../../data/config_entries";
|
||||
import { AreaRegistryEntry } from "../../../data/area_registry";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
|
||||
export interface DeviceRowData extends DeviceRegistryEntry {
|
||||
device?: DeviceRowData;
|
||||
area?: string;
|
||||
integration?: string;
|
||||
battery_entity?: string;
|
||||
}
|
||||
|
||||
export interface DeviceEntityLookup {
|
||||
[deviceId: string]: EntityRegistryEntry[];
|
||||
}
|
||||
|
||||
@customElement("ha-devices-data-table")
|
||||
export class HaDevicesDataTable extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public narrow = false;
|
||||
@property() public devices!: DeviceRegistryEntry[];
|
||||
@property() public entries!: ConfigEntry[];
|
||||
@property() public entities!: EntityRegistryEntry[];
|
||||
@property() public areas!: AreaRegistryEntry[];
|
||||
@property() public domain!: string;
|
||||
|
||||
private _devices = memoizeOne(
|
||||
(
|
||||
devices: DeviceRegistryEntry[],
|
||||
entries: ConfigEntry[],
|
||||
entities: EntityRegistryEntry[],
|
||||
areas: AreaRegistryEntry[],
|
||||
domain: string,
|
||||
localize: LocalizeFunc
|
||||
) => {
|
||||
// Some older installations might have devices pointing at invalid entryIDs
|
||||
// So we guard for that.
|
||||
|
||||
let outputDevices: DeviceRowData[] = devices;
|
||||
|
||||
const deviceLookup: { [deviceId: string]: DeviceRegistryEntry } = {};
|
||||
for (const device of devices) {
|
||||
deviceLookup[device.id] = device;
|
||||
}
|
||||
|
||||
const deviceEntityLookup: DeviceEntityLookup = {};
|
||||
for (const entity of entities) {
|
||||
if (!entity.device_id) {
|
||||
continue;
|
||||
}
|
||||
if (!(entity.device_id in deviceEntityLookup)) {
|
||||
deviceEntityLookup[entity.device_id] = [];
|
||||
}
|
||||
deviceEntityLookup[entity.device_id].push(entity);
|
||||
}
|
||||
|
||||
const entryLookup: { [entryId: string]: ConfigEntry } = {};
|
||||
for (const entry of entries) {
|
||||
entryLookup[entry.entry_id] = entry;
|
||||
}
|
||||
|
||||
const areaLookup: { [areaId: string]: AreaRegistryEntry } = {};
|
||||
for (const area of areas) {
|
||||
areaLookup[area.area_id] = area;
|
||||
}
|
||||
|
||||
if (domain) {
|
||||
outputDevices = outputDevices.filter((device) =>
|
||||
device.config_entries.find(
|
||||
(entryId) =>
|
||||
entryId in entryLookup && entryLookup[entryId].domain === domain
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
outputDevices = outputDevices.map((device) => {
|
||||
return {
|
||||
...device,
|
||||
name:
|
||||
device.name_by_user ||
|
||||
device.name ||
|
||||
this._fallbackDeviceName(device.id, deviceEntityLookup) ||
|
||||
"No name",
|
||||
model: device.model || "<unknown>",
|
||||
manufacturer: device.manufacturer || "<unknown>",
|
||||
area: device.area_id ? areaLookup[device.area_id].name : "No area",
|
||||
integration: device.config_entries.length
|
||||
? device.config_entries
|
||||
.filter((entId) => entId in entryLookup)
|
||||
.map(
|
||||
(entId) =>
|
||||
localize(
|
||||
`component.${entryLookup[entId].domain}.config.title`
|
||||
) || entryLookup[entId].domain
|
||||
)
|
||||
.join(", ")
|
||||
: "No integration",
|
||||
battery_entity: this._batteryEntity(device.id, deviceEntityLookup),
|
||||
};
|
||||
});
|
||||
|
||||
return outputDevices;
|
||||
}
|
||||
);
|
||||
|
||||
private _columns = memoizeOne(
|
||||
(narrow: boolean): DataTableColumnContainer =>
|
||||
narrow
|
||||
? {
|
||||
name: {
|
||||
title: "Device",
|
||||
sortable: true,
|
||||
filterKey: "name",
|
||||
filterable: true,
|
||||
direction: "asc",
|
||||
template: (name, device: DataTableRowData) => {
|
||||
const battery = device.battery_entity
|
||||
? this.hass.states[device.battery_entity]
|
||||
: undefined;
|
||||
// Have to work on a nice layout for mobile
|
||||
return html`
|
||||
${name}<br />
|
||||
${device.area} | ${device.integration}<br />
|
||||
${battery
|
||||
? html`
|
||||
${battery.state}%
|
||||
<ha-state-icon
|
||||
.hass=${this.hass!}
|
||||
.stateObj=${battery}
|
||||
></ha-state-icon>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
},
|
||||
},
|
||||
}
|
||||
: {
|
||||
name: {
|
||||
title: "Device",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
direction: "asc",
|
||||
},
|
||||
manufacturer: {
|
||||
title: "Manufacturer",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
},
|
||||
model: {
|
||||
title: "Model",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
},
|
||||
area: {
|
||||
title: "Area",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
},
|
||||
integration: {
|
||||
title: "Integration",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
},
|
||||
battery_entity: {
|
||||
title: "Battery",
|
||||
sortable: true,
|
||||
type: "numeric",
|
||||
template: (batteryEntity: string) => {
|
||||
const battery = batteryEntity
|
||||
? this.hass.states[batteryEntity]
|
||||
: undefined;
|
||||
return battery
|
||||
? html`
|
||||
${battery.state}%
|
||||
<ha-state-icon
|
||||
.hass=${this.hass!}
|
||||
.stateObj=${battery}
|
||||
></ha-state-icon>
|
||||
`
|
||||
: html`
|
||||
-
|
||||
`;
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-data-table
|
||||
.columns=${this._columns(this.narrow)}
|
||||
.data=${this._devices(
|
||||
this.devices,
|
||||
this.entries,
|
||||
this.entities,
|
||||
this.areas,
|
||||
this.domain,
|
||||
this.hass.localize
|
||||
)}
|
||||
@row-click=${this._handleRowClicked}
|
||||
></ha-data-table>
|
||||
`;
|
||||
}
|
||||
|
||||
private _batteryEntity(
|
||||
deviceId: string,
|
||||
deviceEntityLookup: DeviceEntityLookup
|
||||
): string | undefined {
|
||||
const batteryEntity = (deviceEntityLookup[deviceId] || []).find(
|
||||
(entity) =>
|
||||
this.hass.states[entity.entity_id] &&
|
||||
this.hass.states[entity.entity_id].attributes.device_class === "battery"
|
||||
);
|
||||
|
||||
return batteryEntity ? batteryEntity.entity_id : undefined;
|
||||
}
|
||||
|
||||
private _fallbackDeviceName(
|
||||
deviceId: string,
|
||||
deviceEntityLookup: DeviceEntityLookup
|
||||
): string | undefined {
|
||||
for (const entity of deviceEntityLookup[deviceId] || []) {
|
||||
const stateObj = this.hass.states[entity.entity_id];
|
||||
if (stateObj) {
|
||||
return computeStateName(stateObj);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _handleRowClicked(ev: CustomEvent) {
|
||||
const deviceId = (ev.detail as RowClickedEvent).id;
|
||||
navigate(this, `/config/devices/device/${deviceId}`);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-devices-data-table": HaDevicesDataTable;
|
||||
}
|
||||
}
|
@ -6,9 +6,6 @@ import {
|
||||
CSSResult,
|
||||
property,
|
||||
} from "lit-element";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import {
|
||||
@ -18,7 +15,7 @@ import {
|
||||
} from "../../../data/entity_registry";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import "../../../layouts/hass-loading-screen";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/data-table/ha-data-table";
|
||||
import "../../../components/ha-icon";
|
||||
import "../../../components/ha-switch";
|
||||
import { domainIcon } from "../../../common/entity/domain_icon";
|
||||
@ -30,11 +27,14 @@ import {
|
||||
loadEntityRegistryDetailDialog,
|
||||
} from "./show-dialog-entity-registry-detail";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { compare } from "../../../common/string/compare";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
// tslint:disable-next-line
|
||||
import { HaSwitch } from "../../../components/ha-switch";
|
||||
import memoize from "memoize-one";
|
||||
// tslint:disable-next-line
|
||||
import {
|
||||
DataTableColumnContainer,
|
||||
RowClickedEvent,
|
||||
} from "../../../components/data-table/ha-data-table";
|
||||
|
||||
class HaConfigEntityRegistry extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@ -43,11 +43,76 @@ class HaConfigEntityRegistry extends LitElement {
|
||||
@property() private _showDisabled = false;
|
||||
private _unsubEntities?: UnsubscribeFunc;
|
||||
|
||||
private _columns = memoize(
|
||||
(_language): DataTableColumnContainer => {
|
||||
return {
|
||||
icon: {
|
||||
title: "",
|
||||
type: "icon",
|
||||
template: (icon) => html`
|
||||
<ha-icon slot="item-icon" .icon=${icon}></ha-icon>
|
||||
`,
|
||||
},
|
||||
name: {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.entity_registry.picker.headers.name"
|
||||
),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
direction: "asc",
|
||||
},
|
||||
entity_id: {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.entity_registry.picker.headers.entity_id"
|
||||
),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
},
|
||||
platform: {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.entity_registry.picker.headers.integration"
|
||||
),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
template: (platform) =>
|
||||
html`
|
||||
${this.hass.localize(`component.${platform}.config.title`) ||
|
||||
platform}
|
||||
`,
|
||||
},
|
||||
disabled_by: {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.entity_registry.picker.headers.enabled"
|
||||
),
|
||||
type: "icon",
|
||||
template: (disabledBy) => html`
|
||||
<ha-icon
|
||||
slot="item-icon"
|
||||
.icon=${disabledBy ? "hass:cancel" : "hass:check-circle"}
|
||||
></ha-icon>
|
||||
`,
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
private _filteredEntities = memoize(
|
||||
(entities: EntityRegistryEntry[], showDisabled: boolean) =>
|
||||
showDisabled
|
||||
(showDisabled
|
||||
? entities
|
||||
: entities.filter((entity) => !Boolean(entity.disabled_by))
|
||||
).map((entry) => {
|
||||
const state = this.hass!.states[entry.entity_id];
|
||||
return {
|
||||
...entry,
|
||||
icon: state
|
||||
? stateIcon(state)
|
||||
: domainIcon(computeDomain(entry.entity_id)),
|
||||
name:
|
||||
computeEntityRegistryName(this.hass!, entry) ||
|
||||
this.hass!.localize("state.default.unavailable"),
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
public disconnectedCallback() {
|
||||
@ -89,56 +154,21 @@ class HaConfigEntityRegistry extends LitElement {
|
||||
"ui.panel.config.entity_registry.picker.integrations_page"
|
||||
)}
|
||||
</a>
|
||||
</span>
|
||||
<ha-card>
|
||||
<paper-item>
|
||||
<ha-switch
|
||||
?checked=${this._showDisabled}
|
||||
@change=${this._showDisabledChanged}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.entity_registry.picker.show_disabled"
|
||||
)}</ha-switch
|
||||
></paper-item
|
||||
<ha-switch
|
||||
?checked=${this._showDisabled}
|
||||
@change=${this._showDisabledChanged}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.entity_registry.picker.show_disabled"
|
||||
)}</ha-switch
|
||||
>
|
||||
${this._filteredEntities(this._entities, this._showDisabled).map(
|
||||
(entry) => {
|
||||
const state = this.hass!.states[entry.entity_id];
|
||||
return html`
|
||||
<paper-icon-item
|
||||
@click=${this._openEditEntry}
|
||||
.entry=${entry}
|
||||
class=${classMap({ "disabled-entry": !!entry.disabled_by })}
|
||||
>
|
||||
<ha-icon
|
||||
slot="item-icon"
|
||||
.icon=${state
|
||||
? stateIcon(state)
|
||||
: domainIcon(computeDomain(entry.entity_id))}
|
||||
></ha-icon>
|
||||
<paper-item-body two-line>
|
||||
<div class="name">
|
||||
${computeEntityRegistryName(this.hass!, entry) ||
|
||||
`(${this.hass!.localize(
|
||||
"state.default.unavailable"
|
||||
)})`}
|
||||
</div>
|
||||
<div class="secondary entity-id">
|
||||
${entry.entity_id}
|
||||
</div>
|
||||
</paper-item-body>
|
||||
<div class="platform">
|
||||
${entry.platform}
|
||||
${entry.disabled_by
|
||||
? html`
|
||||
<br />(disabled)
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</paper-icon-item>
|
||||
`;
|
||||
}
|
||||
)}
|
||||
</ha-card>
|
||||
</span>
|
||||
<ha-data-table
|
||||
.columns=${this._columns(this.hass.language)}
|
||||
.data=${this._filteredEntities(this._entities, this._showDisabled)}
|
||||
@row-click=${this._openEditEntry}
|
||||
id="entity_id"
|
||||
>
|
||||
</ha-data-table>
|
||||
</ha-config-section>
|
||||
</hass-subpage>
|
||||
`;
|
||||
@ -155,9 +185,7 @@ class HaConfigEntityRegistry extends LitElement {
|
||||
this._unsubEntities = subscribeEntityRegistry(
|
||||
this.hass.connection,
|
||||
(entities) => {
|
||||
this._entities = entities.sort((ent1, ent2) =>
|
||||
compare(ent1.entity_id, ent2.entity_id)
|
||||
);
|
||||
this._entities = entities;
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -167,8 +195,14 @@ class HaConfigEntityRegistry extends LitElement {
|
||||
this._showDisabled = (ev.target as HaSwitch).checked;
|
||||
}
|
||||
|
||||
private _openEditEntry(ev: MouseEvent): void {
|
||||
const entry = (ev.currentTarget! as any).entry;
|
||||
private _openEditEntry(ev: CustomEvent): void {
|
||||
const entryId = (ev.detail as RowClickedEvent).id;
|
||||
const entry = this._entities!.find(
|
||||
(entity) => entity.entity_id === entryId
|
||||
);
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
showEntityRegistryDetailDialog(this, {
|
||||
entry,
|
||||
});
|
||||
@ -179,23 +213,12 @@ class HaConfigEntityRegistry extends LitElement {
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
ha-card {
|
||||
ha-data-table {
|
||||
margin-bottom: 24px;
|
||||
direction: ltr;
|
||||
margin-top: 0px;
|
||||
}
|
||||
paper-icon-item {
|
||||
cursor: pointer;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
ha-icon {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.platform {
|
||||
text-align: right;
|
||||
margin: 0 0 0 8px;
|
||||
}
|
||||
.disabled-entry {
|
||||
color: var(--secondary-text-color);
|
||||
ha-switch {
|
||||
margin-top: 16px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ class HaCeEntitiesCard extends LocalizeMixIn(EventsMixin(PolymerElement)) {
|
||||
return html`
|
||||
<style>
|
||||
ha-card {
|
||||
flex: 1 0 100%;
|
||||
margin-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
paper-icon-item {
|
||||
|
@ -2,10 +2,7 @@ import memoizeOne from "memoize-one";
|
||||
import "../../../../layouts/hass-subpage";
|
||||
import "../../../../layouts/hass-error-screen";
|
||||
|
||||
import "../../../../components/entity/state-badge";
|
||||
import { compare } from "../../../../common/string/compare";
|
||||
|
||||
import "../../devices/device-detail/ha-device-card";
|
||||
import "../../devices/ha-devices-data-table";
|
||||
import "./ha-ce-entities-card";
|
||||
import { showOptionsFlowDialog } from "../../../../dialogs/config-flow/show-dialog-options-flow";
|
||||
import { property, LitElement, CSSResult, css, html } from "lit-element";
|
||||
@ -43,15 +40,9 @@ class HaConfigEntryPage extends LitElement {
|
||||
if (!devices) {
|
||||
return [];
|
||||
}
|
||||
return devices
|
||||
.filter((device) =>
|
||||
device.config_entries.includes(configEntry.entry_id)
|
||||
)
|
||||
.sort(
|
||||
(dev1, dev2) =>
|
||||
Number(!!dev1.via_device_id) - Number(!!dev2.via_device_id) ||
|
||||
compare(dev1.name || "", dev2.name || "")
|
||||
);
|
||||
return devices.filter((device) =>
|
||||
device.config_entries.includes(configEntry.entry_id)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@ -116,24 +107,19 @@ class HaConfigEntryPage extends LitElement {
|
||||
)}
|
||||
</p>
|
||||
`
|
||||
: ""}
|
||||
${configEntryDevices.map(
|
||||
(device) => html`
|
||||
<ha-device-card
|
||||
class="card"
|
||||
.hass=${this.hass}
|
||||
.areas=${this.areas}
|
||||
.devices=${this.deviceRegistryEntries}
|
||||
.device=${device}
|
||||
.entities=${this.entityRegistryEntries}
|
||||
.narrow=${this.narrow}
|
||||
></ha-device-card>
|
||||
`
|
||||
)}
|
||||
: html`
|
||||
<ha-devices-data-table
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.devices=${configEntryDevices}
|
||||
.entries=${this.configEntries}
|
||||
.entities=${this.entityRegistryEntries}
|
||||
.areas=${this.areas}
|
||||
></ha-devices-data-table>
|
||||
`}
|
||||
${noDeviceEntities.length > 0
|
||||
? html`
|
||||
<ha-ce-entities-card
|
||||
class="card"
|
||||
.heading=${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.no_device"
|
||||
)}
|
||||
@ -185,18 +171,13 @@ class HaConfigEntryPage extends LitElement {
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
.content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 4px;
|
||||
justify-content: center;
|
||||
}
|
||||
.card {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex: 1 0 300px;
|
||||
min-width: 0;
|
||||
max-width: 500px;
|
||||
padding: 8px;
|
||||
p {
|
||||
text-align: center;
|
||||
}
|
||||
ha-devices-data-table {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@ -2,29 +2,49 @@ import { h, Component } from "preact";
|
||||
|
||||
import "../../../../components/device/ha-device-picker";
|
||||
import "../../../../components/device/ha-device-condition-picker";
|
||||
import "../../../../components/ha-form";
|
||||
|
||||
import {
|
||||
fetchDeviceConditionCapabilities,
|
||||
deviceAutomationsEqual,
|
||||
} from "../../../../data/device_automation";
|
||||
export default class DeviceCondition extends Component<any, any> {
|
||||
private _origCondition;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.devicePicked = this.devicePicked.bind(this);
|
||||
this.deviceConditionPicked = this.deviceConditionPicked.bind(this);
|
||||
this.state = { device_id: undefined };
|
||||
this._extraFieldsChanged = this._extraFieldsChanged.bind(this);
|
||||
this.state = { device_id: undefined, capabilities: undefined };
|
||||
}
|
||||
|
||||
public devicePicked(ev) {
|
||||
this.setState({ device_id: ev.target.value });
|
||||
this.setState({ ...this.state, device_id: ev.target.value });
|
||||
}
|
||||
|
||||
public deviceConditionPicked(ev) {
|
||||
const deviceCondition = ev.target.value;
|
||||
this.props.onChange(this.props.index, deviceCondition);
|
||||
let condition = ev.target.value;
|
||||
if (
|
||||
this._origCondition &&
|
||||
deviceAutomationsEqual(this._origCondition, condition)
|
||||
) {
|
||||
condition = this._origCondition;
|
||||
}
|
||||
this.props.onChange(this.props.index, condition);
|
||||
}
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
public render({ condition, hass }, { device_id }) {
|
||||
public render({ condition, hass }, { device_id, capabilities }) {
|
||||
if (device_id === undefined) {
|
||||
device_id = condition.device_id;
|
||||
}
|
||||
const extraFieldsData =
|
||||
capabilities && capabilities.extra_fields
|
||||
? capabilities.extra_fields.map((item) => {
|
||||
return { [item.name]: this.props.condition[item.name] };
|
||||
})
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<div>
|
||||
@ -41,9 +61,64 @@ export default class DeviceCondition extends Component<any, any> {
|
||||
hass={hass}
|
||||
label="Condition"
|
||||
/>
|
||||
{extraFieldsData && (
|
||||
<ha-form
|
||||
data={Object.assign({}, ...extraFieldsData)}
|
||||
onData-changed={this._extraFieldsChanged}
|
||||
schema={this.state.capabilities.extra_fields}
|
||||
computeLabel={this._extraFieldsComputeLabelCallback(hass.localize)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
if (!this.state.capabilities) {
|
||||
this._getCapabilities();
|
||||
}
|
||||
if (this.props.condition) {
|
||||
this._origCondition = this.props.condition;
|
||||
}
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps) {
|
||||
if (prevProps.condition !== this.props.condition) {
|
||||
this._getCapabilities();
|
||||
}
|
||||
}
|
||||
|
||||
private async _getCapabilities() {
|
||||
const condition = this.props.condition;
|
||||
|
||||
const capabilities = condition.domain
|
||||
? await fetchDeviceConditionCapabilities(this.props.hass, condition)
|
||||
: null;
|
||||
this.setState({ ...this.state, capabilities });
|
||||
}
|
||||
|
||||
private _extraFieldsChanged(ev) {
|
||||
if (!ev.detail.path) {
|
||||
return;
|
||||
}
|
||||
const item = ev.detail.path.replace("data.", "");
|
||||
const value = ev.detail.value || undefined;
|
||||
|
||||
this.props.onChange(this.props.index, {
|
||||
...this.props.condition,
|
||||
[item]: value,
|
||||
});
|
||||
}
|
||||
|
||||
private _extraFieldsComputeLabelCallback(localize) {
|
||||
// Returns a callback for ha-form to calculate labels per schema object
|
||||
return (schema) =>
|
||||
localize(
|
||||
`ui.panel.config.automation.editor.condition.type.device.extra_fields.${
|
||||
schema.name
|
||||
}`
|
||||
) || schema.name;
|
||||
}
|
||||
}
|
||||
|
||||
(DeviceCondition as any).defaultConfig = {
|
||||
|
@ -20,6 +20,7 @@ declare global {
|
||||
"ha-device-picker": any;
|
||||
"ha-device-condition-picker": any;
|
||||
"ha-textarea": any;
|
||||
"ha-code-editor": any;
|
||||
"ha-service-picker": any;
|
||||
"mwc-button": any;
|
||||
"ha-device-trigger-picker": any;
|
||||
|
@ -8,6 +8,7 @@ import ConditionAction from "./condition";
|
||||
import DelayAction from "./delay";
|
||||
import DeviceAction from "./device";
|
||||
import EventAction from "./event";
|
||||
import SceneAction from "./scene";
|
||||
import WaitAction from "./wait";
|
||||
|
||||
const TYPES = {
|
||||
@ -17,6 +18,7 @@ const TYPES = {
|
||||
condition: ConditionAction,
|
||||
event: EventAction,
|
||||
device_id: DeviceAction,
|
||||
scene: SceneAction,
|
||||
};
|
||||
|
||||
const OPTIONS = Object.keys(TYPES).sort();
|
||||
|
38
src/panels/config/js/script/scene.tsx
Normal file
38
src/panels/config/js/script/scene.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import { h, Component } from "preact";
|
||||
import "../../../../components/entity/ha-entity-picker";
|
||||
|
||||
export default class SceneAction extends Component<any> {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.sceneChanged = this.sceneChanged.bind(this);
|
||||
}
|
||||
|
||||
public sceneChanged(ev: any) {
|
||||
this.props.onChange(this.props.index, {
|
||||
...this.props.action,
|
||||
scene: ev.target.value,
|
||||
});
|
||||
}
|
||||
|
||||
public render({ action, hass }) {
|
||||
const { scene } = action;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ha-entity-picker
|
||||
value={scene}
|
||||
onChange={this.sceneChanged}
|
||||
hass={hass}
|
||||
domainFilter="scene"
|
||||
allowCustomEntity
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
(SceneAction as any).defaultConfig = {
|
||||
alias: "",
|
||||
scene: "",
|
||||
};
|
@ -1,6 +1,6 @@
|
||||
import { h, Component } from "preact";
|
||||
import yaml from "js-yaml";
|
||||
import "../../../components/ha-textarea";
|
||||
import "../../../components/ha-code-editor";
|
||||
|
||||
const isEmpty = (obj: object) => {
|
||||
for (const key in obj) {
|
||||
@ -34,7 +34,7 @@ export default class YAMLTextArea extends Component<any, any> {
|
||||
}
|
||||
|
||||
public onChange(ev) {
|
||||
const value = ev.target.value;
|
||||
const value = ev.detail.value;
|
||||
let parsed;
|
||||
let isValid = true;
|
||||
|
||||
@ -64,17 +64,17 @@ export default class YAMLTextArea extends Component<any, any> {
|
||||
minWidth: 300,
|
||||
width: "100%",
|
||||
};
|
||||
if (!isValid) {
|
||||
style.border = "1px solid red";
|
||||
}
|
||||
return (
|
||||
<ha-textarea
|
||||
label={label}
|
||||
value={value}
|
||||
style={style}
|
||||
onvalue-changed={this.onChange}
|
||||
dir="ltr"
|
||||
/>
|
||||
<div>
|
||||
<p>{label}</p>
|
||||
<ha-code-editor
|
||||
mode="yaml"
|
||||
style={style}
|
||||
value={value}
|
||||
error={isValid === false}
|
||||
onvalue-changed={this.onChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -9,9 +9,9 @@ import {
|
||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@material/mwc-button";
|
||||
import memoizeOne from "memoize-one";
|
||||
|
||||
import "../../../components/dialog/ha-paper-dialog";
|
||||
|
||||
import "../../../components/entity/ha-entities-picker";
|
||||
import "../../../components/user/ha-user-picker";
|
||||
import { PersonDetailDialogParams } from "./show-dialog-person-detail";
|
||||
@ -29,6 +29,13 @@ class DialogPersonDetail extends LitElement {
|
||||
@property() private _params?: PersonDetailDialogParams;
|
||||
@property() private _submitting: boolean = false;
|
||||
|
||||
private _deviceTrackersAvailable = memoizeOne((hass) => {
|
||||
return Object.keys(hass.states).some(
|
||||
(entityId) =>
|
||||
entityId.substr(0, entityId.indexOf(".")) === "device_tracker"
|
||||
);
|
||||
});
|
||||
|
||||
public async showDialog(params: PersonDetailDialogParams): Promise<void> {
|
||||
this._params = params;
|
||||
this._error = undefined;
|
||||
@ -55,7 +62,11 @@ class DialogPersonDetail extends LitElement {
|
||||
opened
|
||||
@opened-changed="${this._openedChanged}"
|
||||
>
|
||||
<h2>${this._params.entry ? this._params.entry.name : "New Person"}</h2>
|
||||
<h2>
|
||||
${this._params.entry
|
||||
? this._params.entry.name
|
||||
: this.hass!.localize("ui.panel.config.person.detail.new_person")}
|
||||
</h2>
|
||||
<paper-dialog-scrollable>
|
||||
${this._error
|
||||
? html`
|
||||
@ -66,34 +77,72 @@ class DialogPersonDetail extends LitElement {
|
||||
<paper-input
|
||||
.value=${this._name}
|
||||
@value-changed=${this._nameChanged}
|
||||
label="Name"
|
||||
error-message="Name is required"
|
||||
label="${this.hass!.localize(
|
||||
"ui.panel.config.person.detail.name"
|
||||
)}"
|
||||
error-message="${this.hass!.localize(
|
||||
"ui.panel.config.person.detail.name_error_msg"
|
||||
)}"
|
||||
.invalid=${nameInvalid}
|
||||
></paper-input>
|
||||
<ha-user-picker
|
||||
label="Linked User"
|
||||
label="${this.hass!.localize(
|
||||
"ui.panel.config.person.detail.linked_user"
|
||||
)}"
|
||||
.hass=${this.hass}
|
||||
.value=${this._userId}
|
||||
.users=${this._params.users}
|
||||
@value-changed=${this._userChanged}
|
||||
></ha-user-picker>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.person.detail.device_tracker_intro"
|
||||
)}
|
||||
</p>
|
||||
<ha-entities-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this._deviceTrackers}
|
||||
domain-filter="device_tracker"
|
||||
.pickedEntityLabel=${this.hass.localize(
|
||||
"ui.panel.config.person.detail.device_tracker_picked"
|
||||
)}
|
||||
.pickEntityLabel=${this.hass.localize(
|
||||
"ui.panel.config.person.detail.device_tracker_pick"
|
||||
)}
|
||||
@value-changed=${this._deviceTrackersChanged}
|
||||
></ha-entities-picker>
|
||||
${this._deviceTrackersAvailable(this.hass)
|
||||
? html`
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.person.detail.device_tracker_intro"
|
||||
)}
|
||||
</p>
|
||||
<ha-entities-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this._deviceTrackers}
|
||||
domain-filter="device_tracker"
|
||||
.pickedEntityLabel=${this.hass.localize(
|
||||
"ui.panel.config.person.detail.device_tracker_picked"
|
||||
)}
|
||||
.pickEntityLabel=${this.hass.localize(
|
||||
"ui.panel.config.person.detail.device_tracker_pick"
|
||||
)}
|
||||
@value-changed=${this._deviceTrackersChanged}
|
||||
>
|
||||
</ha-entities-picker>
|
||||
`
|
||||
: html`
|
||||
<p>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.person.detail.no_device_tracker_available_intro"
|
||||
)}
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
href="https://www.home-assistant.io/integrations/#presence-detection"
|
||||
target="_blank"
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.person.detail.link_presence_detection_integrations"
|
||||
)}</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
@click="${this._closeDialog}"
|
||||
href="/config/integrations"
|
||||
>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.person.detail.link_integrations_page"
|
||||
)}</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
`}
|
||||
</div>
|
||||
</paper-dialog-scrollable>
|
||||
<div class="paper-dialog-buttons">
|
||||
@ -104,7 +153,7 @@ class DialogPersonDetail extends LitElement {
|
||||
@click="${this._deleteEntry}"
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
DELETE
|
||||
${this.hass!.localize("ui.panel.config.person.detail.delete")}
|
||||
</mwc-button>
|
||||
`
|
||||
: html``}
|
||||
@ -112,13 +161,19 @@ class DialogPersonDetail extends LitElement {
|
||||
@click="${this._updateEntry}"
|
||||
.disabled=${nameInvalid || this._submitting}
|
||||
>
|
||||
${this._params.entry ? "UPDATE" : "CREATE"}
|
||||
${this._params.entry
|
||||
? this.hass!.localize("ui.panel.config.person.detail.update")
|
||||
: this.hass!.localize("ui.panel.config.person.detail.create")}
|
||||
</mwc-button>
|
||||
</div>
|
||||
</ha-paper-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _closeDialog() {
|
||||
this._params = undefined;
|
||||
}
|
||||
|
||||
private _nameChanged(ev: PolymerChangedEvent<string>) {
|
||||
this._error = undefined;
|
||||
this._name = ev.detail.value;
|
||||
|
@ -55,17 +55,21 @@ class HaConfigPerson extends LitElement {
|
||||
<hass-loading-screen></hass-loading-screen>
|
||||
`;
|
||||
}
|
||||
const hass = this.hass;
|
||||
return html`
|
||||
<hass-subpage header="Persons">
|
||||
<hass-subpage header=${hass.localize("ui.panel.config.person.caption")}>
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<span slot="header">Persons</span>
|
||||
<span slot="header"
|
||||
>${hass.localize("ui.panel.config.person.caption")}</span
|
||||
>
|
||||
<span slot="introduction">
|
||||
Here you can define each person of interest in Home Assistant.
|
||||
${hass.localize("ui.panel.config.person.introduction")}
|
||||
${this._configItems.length > 0
|
||||
? html`
|
||||
<p>
|
||||
Note: persons configured via configuration.yaml cannot be
|
||||
edited via the UI.
|
||||
${hass.localize(
|
||||
"ui.panel.config.person.note_about_persons_configured_in_yaml"
|
||||
)}
|
||||
</p>
|
||||
`
|
||||
: ""}
|
||||
@ -83,9 +87,13 @@ class HaConfigPerson extends LitElement {
|
||||
${this._storageItems.length === 0
|
||||
? html`
|
||||
<div class="empty">
|
||||
Looks like you have not created any persons yet.
|
||||
${hass.localize(
|
||||
"ui.panel.config.person.no_persons_created_yet"
|
||||
)}
|
||||
<mwc-button @click=${this._createPerson}>
|
||||
CREATE PERSON</mwc-button
|
||||
${hass.localize(
|
||||
"ui.panel.config.person.create_person"
|
||||
)}</mwc-button
|
||||
>
|
||||
</div>
|
||||
`
|
||||
@ -112,7 +120,7 @@ class HaConfigPerson extends LitElement {
|
||||
<ha-fab
|
||||
?is-wide=${this.isWide}
|
||||
icon="hass:plus"
|
||||
title="Add Person"
|
||||
title="${hass.localize("ui.panel.config.person.add_person")}"
|
||||
@click=${this._createPerson}
|
||||
></ha-fab>
|
||||
`;
|
||||
@ -180,9 +188,11 @@ class HaConfigPerson extends LitElement {
|
||||
},
|
||||
removeEntry: async () => {
|
||||
if (
|
||||
!confirm(`Are you sure you want to delete this person?
|
||||
!confirm(`${this.hass!.localize(
|
||||
"ui.panel.config.person.confirm_delete"
|
||||
)}
|
||||
|
||||
All devices belonging to this person will become unassigned.`)
|
||||
${this.hass!.localize("ui.panel.config.person.confirm_delete2")}`)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ class HaScriptEditor extends LocalizeMixin(NavigateMixin(PolymerElement)) {
|
||||
<ha-paper-icon-button-arrow-prev
|
||||
on-click="backTapped"
|
||||
></ha-paper-icon-button-arrow-prev>
|
||||
<div main-title>Script [[computeName(script)]]</div>
|
||||
<div main-title>[[computeHeader(script)]]</div>
|
||||
<template is="dom-if" if="[[!creatingNew]]">
|
||||
<paper-icon-button
|
||||
icon="hass:delete"
|
||||
@ -120,7 +120,7 @@ class HaScriptEditor extends LocalizeMixin(NavigateMixin(PolymerElement)) {
|
||||
is-wide$="[[isWide]]"
|
||||
dirty$="[[dirty]]"
|
||||
icon="hass:content-save"
|
||||
title="Save"
|
||||
title="[[localize('ui.common.save')]]"
|
||||
on-click="saveScript"
|
||||
rtl$="[[rtl]]"
|
||||
></ha-fab>
|
||||
@ -232,7 +232,11 @@ class HaScriptEditor extends LocalizeMixin(NavigateMixin(PolymerElement)) {
|
||||
this._updateComponent();
|
||||
},
|
||||
() => {
|
||||
alert("Only scripts inside scripts.yaml are editable.");
|
||||
alert(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.script.editor.load_error_not_editable"
|
||||
)
|
||||
);
|
||||
history.back();
|
||||
}
|
||||
);
|
||||
@ -244,7 +248,7 @@ class HaScriptEditor extends LocalizeMixin(NavigateMixin(PolymerElement)) {
|
||||
}
|
||||
this.dirty = false;
|
||||
this.config = {
|
||||
alias: "New Script",
|
||||
alias: this.hass.localize("ui.panel.config.script.editor.default_name"),
|
||||
sequence: [{ service: "", data: {} }],
|
||||
};
|
||||
this._updateComponent();
|
||||
@ -254,7 +258,9 @@ class HaScriptEditor extends LocalizeMixin(NavigateMixin(PolymerElement)) {
|
||||
if (
|
||||
this.dirty &&
|
||||
// eslint-disable-next-line
|
||||
!confirm("You have unsaved changes. Are you sure you want to leave?")
|
||||
!confirm(
|
||||
this.hass.localize("ui.panel.config.common.editor.confirm_unsaved")
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@ -281,7 +287,11 @@ class HaScriptEditor extends LocalizeMixin(NavigateMixin(PolymerElement)) {
|
||||
}
|
||||
|
||||
async _delete() {
|
||||
if (!confirm("Are you sure you want to delete this script?")) {
|
||||
if (
|
||||
!confirm(
|
||||
this.hass.localize("ui.panel.config.script.editor.delete_confirm")
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
await deleteScript(this.hass, computeObjectId(this.script.entity_id));
|
||||
@ -307,8 +317,14 @@ class HaScriptEditor extends LocalizeMixin(NavigateMixin(PolymerElement)) {
|
||||
);
|
||||
}
|
||||
|
||||
computeName(script) {
|
||||
return script && computeStateName(script);
|
||||
computeHeader(script) {
|
||||
return script
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.script.editor.header",
|
||||
"name",
|
||||
computeStateName(script)
|
||||
)
|
||||
: this.hass.localize("ui.panel.config.script.editor.default_name");
|
||||
}
|
||||
|
||||
_computeRTL(hass) {
|
||||
|
@ -38,22 +38,32 @@ class HaScriptPicker extends LitElement {
|
||||
.header=${this.hass.localize("ui.panel.config.script.caption")}
|
||||
>
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<div slot="header">Script Editor</div>
|
||||
<div slot="header">
|
||||
${this.hass.localize("ui.panel.config.script.picker.header")}
|
||||
</div>
|
||||
<div slot="introduction">
|
||||
The script editor allows you to create and edit scripts. Please read
|
||||
<a
|
||||
href="https://home-assistant.io/docs/scripts/editor/"
|
||||
target="_blank"
|
||||
>the instructions</a
|
||||
>
|
||||
to make sure that you have configured Home Assistant correctly.
|
||||
${this.hass.localize("ui.panel.config.script.picker.introduction")}
|
||||
<p>
|
||||
<a
|
||||
href="https://home-assistant.io/docs/scripts/editor/"
|
||||
target="_blank"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.picker.learn_more"
|
||||
)}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ha-card header="Pick script to edit">
|
||||
<ha-card>
|
||||
${this.scripts.length === 0
|
||||
? html`
|
||||
<div class="card-content">
|
||||
<p>We couldn't find any scripts.</p>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.picker.no_scripts"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
`
|
||||
: this.scripts.map(
|
||||
@ -85,7 +95,9 @@ class HaScriptPicker extends LitElement {
|
||||
slot="fab"
|
||||
?is-wide=${this.isWide}
|
||||
icon="hass:plus"
|
||||
title="Add Script"
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.script.picker.add_script"
|
||||
)}"
|
||||
?rtl=${computeRTL(this.hass)}
|
||||
></ha-fab>
|
||||
</a>
|
||||
@ -97,7 +109,11 @@ class HaScriptPicker extends LitElement {
|
||||
const script = ev.currentTarget.script as HassEntity;
|
||||
await triggerScript(this.hass, script.entity_id);
|
||||
showToast(this, {
|
||||
message: `Triggered ${computeStateName(script)}`,
|
||||
message: this.hass.localize(
|
||||
"ui.notification_toast.triggered",
|
||||
"name",
|
||||
computeStateName(script)
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -68,7 +68,8 @@ class HaUserPicker extends EventsMixin(
|
||||
<div secondary="">
|
||||
[[_computeGroup(localize, user)]]
|
||||
<template is="dom-if" if="[[user.system_generated]]">
|
||||
- System Generated
|
||||
-
|
||||
[[localize('ui.panel.config.users.picker.system_generated')]]
|
||||
</template>
|
||||
</div>
|
||||
</paper-item-body>
|
||||
|
@ -52,15 +52,15 @@ class HaUserEditor extends LitElement {
|
||||
<ha-card .header=${this._name}>
|
||||
<table class="card-content">
|
||||
<tr>
|
||||
<td>ID</td>
|
||||
<td>${hass.localize("ui.panel.config.users.editor.id")}</td>
|
||||
<td>${user.id}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Owner</td>
|
||||
<td>${hass.localize("ui.panel.config.users.editor.owner")}</td>
|
||||
<td>${user.is_owner}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Group</td>
|
||||
<td>${hass.localize("ui.panel.config.users.editor.group")}</td>
|
||||
<td>
|
||||
<select
|
||||
@change=${this._handleGroupChange}
|
||||
@ -92,11 +92,15 @@ class HaUserEditor extends LitElement {
|
||||
: ""}
|
||||
|
||||
<tr>
|
||||
<td>Active</td>
|
||||
<td>${hass.localize("ui.panel.config.users.editor.active")}</td>
|
||||
<td>${user.is_active}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>System generated</td>
|
||||
<td>
|
||||
${hass.localize(
|
||||
"ui.panel.config.users.editor.system_generated"
|
||||
)}
|
||||
</td>
|
||||
<td>${user.system_generated}</td>
|
||||
</tr>
|
||||
</table>
|
||||
@ -114,7 +118,9 @@ class HaUserEditor extends LitElement {
|
||||
</mwc-button>
|
||||
${user.system_generated
|
||||
? html`
|
||||
Unable to remove system generated users.
|
||||
${hass.localize(
|
||||
"ui.panel.config.users.editor.system_generated_users_not_removable"
|
||||
)}
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
@ -124,12 +130,19 @@ class HaUserEditor extends LitElement {
|
||||
}
|
||||
|
||||
private get _name() {
|
||||
return this.user && (this.user.name || "Unnamed user");
|
||||
return (
|
||||
this.user &&
|
||||
(this.user.name ||
|
||||
this.hass!.localize("ui.panel.config.users.editor.unnamed_user"))
|
||||
);
|
||||
}
|
||||
|
||||
private async _handleRenameUser(ev): Promise<void> {
|
||||
ev.currentTarget.blur();
|
||||
const newName = prompt("New name?", this.user!.name);
|
||||
const newName = prompt(
|
||||
this.hass!.localize("ui.panel.config.users.editor.enter_new_name"),
|
||||
this.user!.name
|
||||
);
|
||||
if (newName === null || newName === this.user!.name) {
|
||||
return;
|
||||
}
|
||||
@ -140,7 +153,11 @@ class HaUserEditor extends LitElement {
|
||||
});
|
||||
fireEvent(this, "reload-users");
|
||||
} catch (err) {
|
||||
alert(`User rename failed: ${err.message}`);
|
||||
alert(
|
||||
`${this.hass!.localize(
|
||||
"ui.panel.config.users.editor.user_rename_failed"
|
||||
)} ${err.message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,13 +171,25 @@ class HaUserEditor extends LitElement {
|
||||
showSaveSuccessToast(this, this.hass!);
|
||||
fireEvent(this, "reload-users");
|
||||
} catch (err) {
|
||||
alert(`Group update failed: ${err.message}`);
|
||||
alert(
|
||||
`${this.hass!.localize(
|
||||
"ui.panel.config.users.editor.group_update_failed"
|
||||
)} ${err.message}`
|
||||
);
|
||||
selectEl.value = this.user!.group_ids[0];
|
||||
}
|
||||
}
|
||||
|
||||
private async _deleteUser(ev): Promise<void> {
|
||||
if (!confirm(`Are you sure you want to delete ${this._name}`)) {
|
||||
if (
|
||||
!confirm(
|
||||
this.hass!.localize(
|
||||
"ui.panel.config.users.editor.confirm_user_deletion",
|
||||
"name",
|
||||
this._name
|
||||
)
|
||||
)
|
||||
) {
|
||||
ev.target.blur();
|
||||
return;
|
||||
}
|
||||
|
@ -72,7 +72,9 @@ class ZHAAddDevicesPage extends LitElement {
|
||||
: html`
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._subscribe} class="search-button">
|
||||
Search again
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.add_device_page.search_again"
|
||||
)}
|
||||
</mwc-button>
|
||||
<paper-icon-button
|
||||
class="toggle-help-icon"
|
||||
|
@ -85,7 +85,11 @@ export class ZHAClusterAttributes extends LitElement {
|
||||
return html`
|
||||
<ha-config-section .isWide="${this.isWide}">
|
||||
<div style="position: relative" slot="header">
|
||||
<span>Cluster Attributes</span>
|
||||
<span>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.cluster_attributes.header"
|
||||
)}
|
||||
</span>
|
||||
<paper-icon-button
|
||||
class="toggle-help-icon"
|
||||
@click="${this._onHelpTap}"
|
||||
@ -93,12 +97,18 @@ export class ZHAClusterAttributes extends LitElement {
|
||||
>
|
||||
</paper-icon-button>
|
||||
</div>
|
||||
<span slot="introduction">View and edit cluster attributes.</span>
|
||||
<span slot="introduction">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.cluster_attributes.introduction"
|
||||
)}
|
||||
</span>
|
||||
|
||||
<ha-card class="content">
|
||||
<div class="attribute-picker">
|
||||
<paper-dropdown-menu
|
||||
label="Attributes of the selected cluster"
|
||||
label="${this.hass!.localize(
|
||||
"ui.panel.config.zha.cluster_attributes.attributes_of_cluster"
|
||||
)}"
|
||||
class="flex"
|
||||
>
|
||||
<paper-listbox
|
||||
@ -122,7 +132,9 @@ export class ZHAClusterAttributes extends LitElement {
|
||||
${this.showHelp
|
||||
? html`
|
||||
<div class="help-text">
|
||||
Select an attribute to view or set its value
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.cluster_attributes.help_attribute_dropdown"
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
@ -138,30 +150,40 @@ export class ZHAClusterAttributes extends LitElement {
|
||||
return html`
|
||||
<div class="input-text">
|
||||
<paper-input
|
||||
label="Value"
|
||||
label="${this.hass!.localize("ui.panel.config.zha.common.value")}"
|
||||
type="string"
|
||||
.value="${this._attributeValue}"
|
||||
@value-changed="${this._onAttributeValueChanged}"
|
||||
placeholder="Value"
|
||||
placeholder="${this.hass!.localize(
|
||||
"ui.panel.config.zha.common.value"
|
||||
)}"
|
||||
></paper-input>
|
||||
</div>
|
||||
<div class="input-text">
|
||||
<paper-input
|
||||
label="Manufacturer code override"
|
||||
label="${this.hass!.localize(
|
||||
"ui.panel.config.zha.common.manufacturer_code_override"
|
||||
)}"
|
||||
type="number"
|
||||
.value="${this._manufacturerCodeOverride}"
|
||||
@value-changed="${this._onManufacturerCodeOverrideChanged}"
|
||||
placeholder="Value"
|
||||
placeholder="${this.hass!.localize(
|
||||
"ui.panel.config.zha.common.value"
|
||||
)}"
|
||||
></paper-input>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<mwc-button @click="${this._onGetZigbeeAttributeClick}"
|
||||
>Get Zigbee Attribute</mwc-button
|
||||
>
|
||||
<mwc-button @click="${this._onGetZigbeeAttributeClick}">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.cluster_attributes.get_zigbee_attribute"
|
||||
)}
|
||||
</mwc-button>
|
||||
${this.showHelp
|
||||
? html`
|
||||
<div class="help-text2">
|
||||
Get the value for the selected attribute
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.cluster_attributes.help_get_zigbee_attribute"
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
@ -170,8 +192,11 @@ export class ZHAClusterAttributes extends LitElement {
|
||||
domain="zha"
|
||||
service="set_zigbee_cluster_attribute"
|
||||
.serviceData="${this._setAttributeServiceData}"
|
||||
>Set Zigbee Attribute</ha-call-service-button
|
||||
>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.cluster_attributes.set_zigbee_attribute"
|
||||
)}
|
||||
</ha-call-service-button>
|
||||
${this.showHelp
|
||||
? html`
|
||||
<ha-service-description
|
||||
|
@ -78,7 +78,11 @@ export class ZHAClusterCommands extends LitElement {
|
||||
return html`
|
||||
<ha-config-section .isWide="${this.isWide}">
|
||||
<div class="sectionHeader" slot="header">
|
||||
<span>Cluster Commands</span>
|
||||
<span>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.cluster_commands.header"
|
||||
)}
|
||||
</span>
|
||||
<paper-icon-button
|
||||
class="toggle-help-icon"
|
||||
@click="${this._onHelpTap}"
|
||||
@ -86,12 +90,18 @@ export class ZHAClusterCommands extends LitElement {
|
||||
>
|
||||
</paper-icon-button>
|
||||
</div>
|
||||
<span slot="introduction">View and issue cluster commands.</span>
|
||||
<span slot="introduction">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.cluster_commands.introduction"
|
||||
)}
|
||||
</span>
|
||||
|
||||
<ha-card class="content">
|
||||
<div class="command-picker">
|
||||
<paper-dropdown-menu
|
||||
label="Commands of the selected cluster"
|
||||
label="${this.hass!.localize(
|
||||
"ui.panel.config.zha.cluster_commands.commands_of_cluster"
|
||||
)}"
|
||||
class="flex"
|
||||
>
|
||||
<paper-listbox
|
||||
@ -114,18 +124,26 @@ export class ZHAClusterCommands extends LitElement {
|
||||
</div>
|
||||
${this._showHelp
|
||||
? html`
|
||||
<div class="help-text">Select a command to interact with</div>
|
||||
<div class="help-text">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.cluster_commands.help_command_dropdown"
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${this._selectedCommandIndex !== -1
|
||||
? html`
|
||||
<div class="input-text">
|
||||
<paper-input
|
||||
label="Manufacturer code override"
|
||||
label="${this.hass!.localize(
|
||||
"ui.panel.config.zha.common.manufacturer_code_override"
|
||||
)}"
|
||||
type="number"
|
||||
.value="${this._manufacturerCodeOverride}"
|
||||
@value-changed="${this._onManufacturerCodeOverrideChanged}"
|
||||
placeholder="Value"
|
||||
placeholder="${this.hass!.localize(
|
||||
"ui.panel.config.zha.common.value"
|
||||
)}"
|
||||
></paper-input>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
@ -134,8 +152,11 @@ export class ZHAClusterCommands extends LitElement {
|
||||
domain="zha"
|
||||
service="issue_zigbee_cluster_command"
|
||||
.serviceData="${this._issueClusterCommandServiceData}"
|
||||
>Issue Zigbee Command</ha-call-service-button
|
||||
>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.cluster_commands.issue_zigbee_command"
|
||||
)}
|
||||
</ha-call-service-button>
|
||||
${this._showHelp
|
||||
? html`
|
||||
<ha-service-description
|
||||
|
@ -79,7 +79,10 @@ export class ZHAClusters extends LitElement {
|
||||
protected render(): TemplateResult | void {
|
||||
return html`
|
||||
<div class="node-picker">
|
||||
<paper-dropdown-menu label="Clusters" class="flex">
|
||||
<paper-dropdown-menu
|
||||
label="${this.hass!.localize("ui.panel.config.zha.common.clusters")}"
|
||||
class="flex"
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected="${this._selectedClusterIndex}"
|
||||
@ -96,7 +99,9 @@ export class ZHAClusters extends LitElement {
|
||||
${this.showHelp
|
||||
? html`
|
||||
<div class="help-text">
|
||||
Select cluster to view attributes and commands
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.clusters.help_cluster_dropdown"
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
|
@ -155,17 +155,29 @@ class ZHADeviceCard extends LitElement {
|
||||
<dt>Nwk:</dt>
|
||||
<dd class="zha-info">${formatAsPaddedHex(this.device!.nwk)}</dd>
|
||||
<dt>LQI:</dt>
|
||||
<dd class="zha-info">${this.device!.lqi || "Unknown"}</dd>
|
||||
<dd class="zha-info">${this.device!.lqi ||
|
||||
this.hass!.localize("ui.dialogs.zha_device_info.unknown")}</dd>
|
||||
<dt>RSSI:</dt>
|
||||
<dd class="zha-info">${this.device!.rssi || "Unknown"}</dd>
|
||||
<dt>Last Seen:</dt>
|
||||
<dd class="zha-info">${this.device!.last_seen || "Unknown"}</dd>
|
||||
<dt>Power Source:</dt>
|
||||
<dd class="zha-info">${this.device!.power_source || "Unknown"}</dd>
|
||||
<dd class="zha-info">${this.device!.rssi ||
|
||||
this.hass!.localize("ui.dialogs.zha_device_info.unknown")}</dd>
|
||||
<dt>${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.last_seen"
|
||||
)}:</dt>
|
||||
<dd class="zha-info">${this.device!.last_seen ||
|
||||
this.hass!.localize("ui.dialogs.zha_device_info.unknown")}</dd>
|
||||
<dt>${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.power_source"
|
||||
)}:</dt>
|
||||
<dd class="zha-info">${this.device!.power_source ||
|
||||
this.hass!.localize("ui.dialogs.zha_device_info.unknown")}</dd>
|
||||
${
|
||||
this.device!.quirk_applied
|
||||
? html`
|
||||
<dt>Quirk:</dt>
|
||||
<dt>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.quirk"
|
||||
)}:
|
||||
</dt>
|
||||
<dd class="zha-info">${this.device!.quirk_class}</dd>
|
||||
`
|
||||
: ""
|
||||
@ -238,9 +250,11 @@ class ZHADeviceCard extends LitElement {
|
||||
this.showActions
|
||||
? html`
|
||||
<div class="card-actions">
|
||||
<mwc-button @click="${this._onReconfigureNodeClick}"
|
||||
>Reconfigure Device</mwc-button
|
||||
>
|
||||
<mwc-button @click="${this._onReconfigureNodeClick}">
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.reconfigure"
|
||||
)}
|
||||
</mwc-button>
|
||||
${this.showHelp
|
||||
? html`
|
||||
<div class="help-text">
|
||||
@ -256,8 +270,11 @@ class ZHADeviceCard extends LitElement {
|
||||
domain="zha"
|
||||
service="remove"
|
||||
.serviceData="${this._serviceData}"
|
||||
>Remove Device</ha-call-service-button
|
||||
>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.remove"
|
||||
)}
|
||||
</ha-call-service-button>
|
||||
${this.showHelp
|
||||
? html`
|
||||
<div class="help-text">
|
||||
@ -270,7 +287,9 @@ class ZHADeviceCard extends LitElement {
|
||||
${this.device!.power_source === "Mains"
|
||||
? html`
|
||||
<mwc-button @click=${this._onAddDevicesClick}>
|
||||
Add Devices
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.common.add_devices"
|
||||
)}
|
||||
</mwc-button>
|
||||
${this.showHelp
|
||||
? html`
|
||||
|
@ -41,19 +41,27 @@ export class ZHANetwork extends LitElement {
|
||||
return html`
|
||||
<ha-config-section .isWide="${this.isWide}">
|
||||
<div style="position: relative" slot="header">
|
||||
<span>Network Management</span>
|
||||
<span>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.network_management.header"
|
||||
)}
|
||||
</span>
|
||||
<paper-icon-button
|
||||
class="toggle-help-icon"
|
||||
@click="${this._onHelpTap}"
|
||||
icon="hass:help-circle"
|
||||
></paper-icon-button>
|
||||
</div>
|
||||
<span slot="introduction">Commands that affect entire network</span>
|
||||
<span slot="introduction">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.network_management.introduction"
|
||||
)}
|
||||
</span>
|
||||
|
||||
<ha-card class="content">
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._onAddDevicesClick}>
|
||||
Add Devices
|
||||
${this.hass!.localize("ui.panel.config.zha.common.add_devices")}
|
||||
</mwc-button>
|
||||
${this._showHelp
|
||||
? html`
|
||||
|
@ -54,7 +54,11 @@ export class ZHANode extends LitElement {
|
||||
return html`
|
||||
<ha-config-section .isWide="${this.isWide}">
|
||||
<div class="sectionHeader" slot="header">
|
||||
<span>Device Management</span>
|
||||
<span
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.zha.node_management.header"
|
||||
)}</span
|
||||
>
|
||||
<paper-icon-button
|
||||
class="toggle-help-icon"
|
||||
@click="${this._onHelpTap}"
|
||||
@ -62,18 +66,24 @@ export class ZHANode extends LitElement {
|
||||
></paper-icon-button>
|
||||
</div>
|
||||
<span slot="introduction">
|
||||
Run ZHA commands that affect a single device. Pick a device to see a
|
||||
list of available commands. <br /><br />Note: Sleepy (battery powered)
|
||||
devices need to be awake when executing commands against them. You can
|
||||
generally wake a sleepy device by triggering it. <br /><br />Some
|
||||
devices such as Xiaomi sensors have a wake up button that you can
|
||||
press at ~5 second intervals that keep devices awake while you
|
||||
interact with them.
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.node_management.introduction"
|
||||
)}
|
||||
<br /><br />
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.node_management.hint_battery_devices"
|
||||
)}
|
||||
<br /><br />
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.node_management.hint_wakeup"
|
||||
)}
|
||||
</span>
|
||||
<ha-card class="content">
|
||||
<div class="node-picker">
|
||||
<paper-dropdown-menu
|
||||
label="Devices"
|
||||
label="${this.hass!.localize(
|
||||
"ui.panel.config.zha.common.devices"
|
||||
)}"
|
||||
class="flex"
|
||||
id="zha-device-selector"
|
||||
>
|
||||
@ -97,7 +107,9 @@ export class ZHANode extends LitElement {
|
||||
${this._showHelp
|
||||
? html`
|
||||
<div class="help-text">
|
||||
Select device to view per-device options
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.node_management.help_node_dropdown"
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
|
@ -44,6 +44,11 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.sectionHeader {
|
||||
position: relative;
|
||||
padding-right: 40px;
|
||||
}
|
||||
|
||||
.node-info {
|
||||
margin-left: 16px;
|
||||
}
|
||||
@ -77,7 +82,7 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
|
||||
.toggle-help-icon {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
top: -6px;
|
||||
right: 0;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
@ -102,7 +107,7 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
|
||||
<!-- Node card -->
|
||||
<ha-config-section is-wide="[[isWide]]">
|
||||
<div style="position: relative" slot="header">
|
||||
<div class="sectionHeader" slot="header">
|
||||
<span>Z-Wave Node Management</span>
|
||||
<paper-icon-button
|
||||
class="toggle-help-icon"
|
||||
|
12
src/panels/config/zwave/zwave-log.js
Normal file → Executable file
12
src/panels/config/zwave/zwave-log.js
Normal file → Executable file
@ -3,6 +3,7 @@ import "@polymer/paper-checkbox/paper-checkbox";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
import { EventsMixin } from "../../../mixins/events-mixin";
|
||||
import isPwa from "../../../common/config/is_pwa";
|
||||
|
||||
@ -11,7 +12,7 @@ import "../../../components/ha-card";
|
||||
|
||||
let registeredDialog = false;
|
||||
|
||||
class OzwLog extends EventsMixin(PolymerElement) {
|
||||
class OzwLog extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="iron-flex ha-style">
|
||||
@ -32,8 +33,13 @@ class OzwLog extends EventsMixin(PolymerElement) {
|
||||
|
||||
</style>
|
||||
<ha-config-section is-wide="[[isWide]]">
|
||||
<span slot="header">OZW Log</span>
|
||||
<ha-card>
|
||||
<span slot="header">
|
||||
[[localize('ui.panel.config.zwave.ozw_log.header')]]
|
||||
</span>
|
||||
<span slot="introduction">
|
||||
[[localize('ui.panel.config.zwave.ozw_log.introduction')]]
|
||||
</span>
|
||||
<ha-card class="content">
|
||||
<div class="device-picker">
|
||||
<paper-input label="Number of last log lines." type="number" min="0" max="1000" step="10" value="{{numLogLines}}">
|
||||
</paper-input>
|
||||
|
@ -51,7 +51,7 @@ export class ZwaveNetwork extends LitElement {
|
||||
protected render(): TemplateResult | void {
|
||||
return html`
|
||||
<ha-config-section .isWide="${this.isWide}">
|
||||
<div style="position: relative" slot="header">
|
||||
<div class="sectionHeader" slot="header">
|
||||
<span>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zwave.network_management.header"
|
||||
@ -63,11 +63,19 @@ export class ZwaveNetwork extends LitElement {
|
||||
icon="hass:help-circle"
|
||||
></paper-icon-button>
|
||||
</div>
|
||||
<span slot="introduction">
|
||||
<div slot="introduction">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zwave.network_management.introduction"
|
||||
)}
|
||||
</span>
|
||||
<p>
|
||||
<a
|
||||
href="https://www.home-assistant.io/docs/z-wave/control-panel/"
|
||||
target="_blank"
|
||||
>
|
||||
${this.hass!.localize("ui.panel.config.zwave.learn_more")}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
${this._networkStatus
|
||||
? html`
|
||||
@ -234,6 +242,11 @@ export class ZwaveNetwork extends LitElement {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.sectionHeader {
|
||||
position: relative;
|
||||
padding-right: 40px;
|
||||
}
|
||||
|
||||
.network-status {
|
||||
text-align: center;
|
||||
}
|
||||
|
@ -1,17 +1,18 @@
|
||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import yaml from "js-yaml";
|
||||
|
||||
import "../../../components/ha-code-editor";
|
||||
import "../../../resources/ha-style";
|
||||
import "./events-list";
|
||||
import "./event-subscribe-card";
|
||||
import { EventsMixin } from "../../../mixins/events-mixin";
|
||||
|
||||
const ERROR_SENTINEL = {};
|
||||
/*
|
||||
* @appliesMixin EventsMixin
|
||||
*/
|
||||
@ -32,6 +33,11 @@ class HaPanelDevEvent extends EventsMixin(PolymerElement) {
|
||||
|
||||
.ha-form {
|
||||
margin-right: 16px;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
mwc-button {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.header {
|
||||
@ -62,11 +68,16 @@ class HaPanelDevEvent extends EventsMixin(PolymerElement) {
|
||||
required
|
||||
value="{{eventType}}"
|
||||
></paper-input>
|
||||
<paper-textarea
|
||||
label="Event Data (YAML, optional)"
|
||||
value="{{eventData}}"
|
||||
></paper-textarea>
|
||||
<mwc-button on-click="fireEvent" raised>Fire Event</mwc-button>
|
||||
<p>Event Data (YAML, optional)</p>
|
||||
<ha-code-editor
|
||||
mode="yaml"
|
||||
value="[[eventData]]"
|
||||
error="[[!validJSON]]"
|
||||
on-value-changed="_yamlChanged"
|
||||
></ha-code-editor>
|
||||
<mwc-button on-click="fireEvent" raised disabled="[[!validJSON]]"
|
||||
>Fire Event</mwc-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -97,6 +108,16 @@ class HaPanelDevEvent extends EventsMixin(PolymerElement) {
|
||||
type: String,
|
||||
value: "",
|
||||
},
|
||||
|
||||
parsedJSON: {
|
||||
type: Object,
|
||||
computed: "_computeParsedEventData(eventData)",
|
||||
},
|
||||
|
||||
validJSON: {
|
||||
type: Boolean,
|
||||
computed: "_computeValidJSON(parsedJSON)",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -104,19 +125,28 @@ class HaPanelDevEvent extends EventsMixin(PolymerElement) {
|
||||
this.eventType = ev.detail.eventType;
|
||||
}
|
||||
|
||||
fireEvent() {
|
||||
var eventData;
|
||||
|
||||
_computeParsedEventData(eventData) {
|
||||
try {
|
||||
eventData = this.eventData ? yaml.safeLoad(this.eventData) : {};
|
||||
return eventData.trim() ? yaml.safeLoad(eventData) : {};
|
||||
} catch (err) {
|
||||
/* eslint-disable no-alert */
|
||||
alert("Error parsing YAML: " + err);
|
||||
/* eslint-enable no-alert */
|
||||
return ERROR_SENTINEL;
|
||||
}
|
||||
}
|
||||
|
||||
_computeValidJSON(parsedJSON) {
|
||||
return parsedJSON !== ERROR_SENTINEL;
|
||||
}
|
||||
|
||||
_yamlChanged(ev) {
|
||||
this.eventData = ev.detail.value;
|
||||
}
|
||||
|
||||
fireEvent() {
|
||||
if (!this.eventType) {
|
||||
alert("Event type is a mandatory field");
|
||||
return;
|
||||
}
|
||||
|
||||
this.hass.callApi("POST", "events/" + this.eventType, eventData).then(
|
||||
this.hass.callApi("POST", "events/" + this.eventType, this.parsedJSON).then(
|
||||
function() {
|
||||
this.fire("hass-notification", {
|
||||
message: "Event " + this.eventType + " successful fired!",
|
||||
|
@ -110,9 +110,8 @@ class HaPanelDevInfo extends LitElement {
|
||||
</p>
|
||||
<p>
|
||||
<a href="${nonDefaultLink}">${nonDefaultLinkText}</a><br />
|
||||
<mwc-button @click="${this._toggleDefaultPage}" raised>
|
||||
${defaultPageText}
|
||||
</mwc-button>
|
||||
<a href="#" @click="${this._toggleDefaultPage}">${defaultPageText}</a
|
||||
><br />
|
||||
</p>
|
||||
</div>
|
||||
<div class="content">
|
||||
|
@ -9,12 +9,12 @@ import {
|
||||
} from "lit-element";
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
|
||||
import { HomeAssistant } from "../../../types";
|
||||
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-code-editor";
|
||||
import "./mqtt-subscribe-card";
|
||||
|
||||
@customElement("developer-tools-mqtt")
|
||||
@ -48,12 +48,12 @@ class HaPanelDevMqtt extends LitElement {
|
||||
@value-changed=${this._handleTopic}
|
||||
></paper-input>
|
||||
|
||||
<paper-textarea
|
||||
always-float-label
|
||||
label="Payload (template allowed)"
|
||||
<p>Payload (template allowed)</p>
|
||||
<ha-code-editor
|
||||
mode="jinja2"
|
||||
.value="${this.payload}"
|
||||
@value-changed=${this._handlePayload}
|
||||
></paper-textarea>
|
||||
></ha-code-editor>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._publish}>Publish</mwc-button>
|
||||
|
@ -1,5 +1,4 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
@ -7,6 +6,7 @@ import yaml from "js-yaml";
|
||||
|
||||
import { ENTITY_COMPONENT_DOMAINS } from "../../../data/entity";
|
||||
import "../../../components/entity/ha-entity-picker";
|
||||
import "../../../components/ha-code-editor";
|
||||
import "../../../components/ha-service-picker";
|
||||
import "../../../resources/ha-style";
|
||||
import "../../../util/app-localstorage-document";
|
||||
@ -30,6 +30,10 @@ class HaPanelDevService extends PolymerElement {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
mwc-button {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-top: 24px;
|
||||
white-space: pre-wrap;
|
||||
@ -109,20 +113,16 @@ class HaPanelDevService extends PolymerElement {
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
</template>
|
||||
<paper-textarea
|
||||
always-float-label
|
||||
label="Service Data (YAML, optional)"
|
||||
value="{{serviceData}}"
|
||||
autocapitalize="none"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
></paper-textarea>
|
||||
<p>Service Data (YAML, optional)</p>
|
||||
<ha-code-editor
|
||||
mode="yaml"
|
||||
value="[[serviceData]]"
|
||||
error="[[!validJSON]]"
|
||||
on-value-changed="_yamlChanged"
|
||||
></ha-code-editor>
|
||||
<mwc-button on-click="_callService" raised disabled="[[!validJSON]]">
|
||||
Call Service
|
||||
</mwc-button>
|
||||
<template is="dom-if" if="[[!validJSON]]">
|
||||
<span class="error">Invalid YAML</span>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<template is="dom-if" if="[[!domainService]]">
|
||||
@ -305,6 +305,10 @@ class HaPanelDevService extends PolymerElement {
|
||||
entity_id: ev.target.value,
|
||||
});
|
||||
}
|
||||
|
||||
_yamlChanged(ev) {
|
||||
this.serviceData = ev.detail.value;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("developer-tools-service", HaPanelDevService);
|
||||
|
@ -1,16 +1,17 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-checkbox/paper-checkbox";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import yaml from "js-yaml";
|
||||
|
||||
import "../../../components/entity/ha-entity-picker";
|
||||
import "../../../components/ha-code-editor";
|
||||
import "../../../resources/ha-style";
|
||||
import { EventsMixin } from "../../../mixins/events-mixin";
|
||||
|
||||
const ERROR_SENTINEL = {};
|
||||
/*
|
||||
* @appliesMixin EventsMixin
|
||||
*/
|
||||
@ -27,13 +28,14 @@ class HaPanelDevState extends EventsMixin(PolymerElement) {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
ha-entity-picker,
|
||||
.state-input,
|
||||
paper-textarea {
|
||||
display: block;
|
||||
.inputs {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
mwc-button {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.entities th {
|
||||
text-align: left;
|
||||
}
|
||||
@ -66,7 +68,7 @@ class HaPanelDevState extends EventsMixin(PolymerElement) {
|
||||
}
|
||||
</style>
|
||||
|
||||
<div>
|
||||
<div class="inputs">
|
||||
<p>
|
||||
Set the representation of a device within Home Assistant.<br />
|
||||
This will not communicate with the actual device.
|
||||
@ -89,14 +91,16 @@ class HaPanelDevState extends EventsMixin(PolymerElement) {
|
||||
value="{{_state}}"
|
||||
class="state-input"
|
||||
></paper-input>
|
||||
<paper-textarea
|
||||
label="State attributes (YAML, optional)"
|
||||
autocapitalize="none"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
value="{{_stateAttributes}}"
|
||||
></paper-textarea>
|
||||
<mwc-button on-click="handleSetState" raised>Set State</mwc-button>
|
||||
<p>State attributes (YAML, optional)</p>
|
||||
<ha-code-editor
|
||||
mode="yaml"
|
||||
value="[[_stateAttributes]]"
|
||||
error="[[!validJSON]]"
|
||||
on-value-changed="_yamlChanged"
|
||||
></ha-code-editor>
|
||||
<mwc-button on-click="handleSetState" disabled="[[!validJSON]]" raised
|
||||
>Set State</mwc-button
|
||||
>
|
||||
</div>
|
||||
|
||||
<h1>Current entities</h1>
|
||||
@ -166,6 +170,16 @@ class HaPanelDevState extends EventsMixin(PolymerElement) {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
parsedJSON: {
|
||||
type: Object,
|
||||
computed: "_computeParsedStateAttributes(_stateAttributes)",
|
||||
},
|
||||
|
||||
validJSON: {
|
||||
type: Boolean,
|
||||
computed: "_computeValidJSON(parsedJSON)",
|
||||
},
|
||||
|
||||
_entityId: {
|
||||
type: String,
|
||||
value: "",
|
||||
@ -229,20 +243,13 @@ class HaPanelDevState extends EventsMixin(PolymerElement) {
|
||||
}
|
||||
|
||||
handleSetState() {
|
||||
var attr;
|
||||
|
||||
try {
|
||||
attr = this._stateAttributes ? yaml.safeLoad(this._stateAttributes) : {};
|
||||
} catch (err) {
|
||||
/* eslint-disable no-alert */
|
||||
alert("Error parsing YAML: " + err);
|
||||
/* eslint-enable no-alert */
|
||||
if (!this._entityId) {
|
||||
alert("Entity is a mandatory field");
|
||||
return;
|
||||
}
|
||||
|
||||
this.hass.callApi("POST", "states/" + this._entityId, {
|
||||
state: this._state,
|
||||
attributes: attr,
|
||||
attributes: this.parsedJSON,
|
||||
});
|
||||
}
|
||||
|
||||
@ -341,6 +348,22 @@ class HaPanelDevState extends EventsMixin(PolymerElement) {
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
_computeParsedStateAttributes(stateAttributes) {
|
||||
try {
|
||||
return stateAttributes.trim() ? yaml.safeLoad(stateAttributes) : {};
|
||||
} catch (err) {
|
||||
return ERROR_SENTINEL;
|
||||
}
|
||||
}
|
||||
|
||||
_computeValidJSON(parsedJSON) {
|
||||
return parsedJSON !== ERROR_SENTINEL;
|
||||
}
|
||||
|
||||
_yamlChanged(ev) {
|
||||
this._stateAttributes = ev.detail.value;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("developer-tools-state", HaPanelDevState);
|
||||
|
@ -1,9 +1,9 @@
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import "@polymer/paper-spinner/paper-spinner";
|
||||
import { timeOut } from "@polymer/polymer/lib/utils/async";
|
||||
import { Debouncer } from "@polymer/polymer/lib/utils/debounce";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "../../../components/ha-code-editor";
|
||||
|
||||
import "../../../resources/ha-style";
|
||||
|
||||
@ -46,12 +46,6 @@ class HaPanelDevTemplate extends PolymerElement {
|
||||
right: 8px;
|
||||
}
|
||||
|
||||
paper-textarea {
|
||||
--paper-input-container-input: {
|
||||
@apply --paper-font-code1;
|
||||
}
|
||||
}
|
||||
|
||||
.rendered {
|
||||
@apply --paper-font-code1;
|
||||
clear: both;
|
||||
@ -85,11 +79,14 @@ class HaPanelDevTemplate extends PolymerElement {
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
<paper-textarea
|
||||
label="Template editor"
|
||||
value="{{template}}"
|
||||
<p>Template editor</p>
|
||||
<ha-code-editor
|
||||
mode="jinja2"
|
||||
value="[[template]]"
|
||||
error="[[error]]"
|
||||
autofocus
|
||||
></paper-textarea>
|
||||
on-value-changed="templateChanged"
|
||||
></ha-code-editor>
|
||||
</div>
|
||||
|
||||
<div class="render-pane">
|
||||
@ -144,7 +141,6 @@ For loop example:
|
||||
{{ state.name | lower }} is {{state.state_with_unit}}
|
||||
{%- endfor %}.`,
|
||||
/* eslint-enable max-len */
|
||||
observer: "templateChanged",
|
||||
},
|
||||
|
||||
processed: {
|
||||
@ -154,6 +150,11 @@ For loop example:
|
||||
};
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
this.renderTemplate();
|
||||
}
|
||||
|
||||
computeFormClasses(narrow) {
|
||||
return narrow ? "content fit" : "content fit layout horizontal";
|
||||
}
|
||||
@ -162,7 +163,8 @@ For loop example:
|
||||
return error ? "error rendered" : "rendered";
|
||||
}
|
||||
|
||||
templateChanged() {
|
||||
templateChanged(ev) {
|
||||
this.template = ev.detail.value;
|
||||
if (this.error) {
|
||||
this.error = false;
|
||||
}
|
||||
|
@ -26,7 +26,10 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
public static getStubConfig(): object {
|
||||
return { content: " " };
|
||||
return {
|
||||
content:
|
||||
"The **Markdown** card allows you to write any text. You can style it **bold**, *italicized*, ~strikethrough~ etc. You can do images, links, and more.\n\nFor more information see the [Markdown Cheatsheet](https://commonmark.org/help).",
|
||||
};
|
||||
}
|
||||
|
||||
@property() private _config?: MarkdownCardConfig;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { html, LitElement, TemplateResult } from "lit-element";
|
||||
import { html, LitElement, TemplateResult, CSSResult, css } from "lit-element";
|
||||
|
||||
import { createCardElement } from "../common/create-card-element";
|
||||
import { LovelaceCard } from "../types";
|
||||
@ -48,12 +48,31 @@ export abstract class HuiStackCard extends LitElement implements LovelaceCard {
|
||||
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
${this._config.title
|
||||
? html`
|
||||
<div class="card-header">${this._config.title}</div>
|
||||
`
|
||||
: ""}
|
||||
<div id="root">${this._cards}</div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected abstract renderStyle(): TemplateResult;
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
.card-header {
|
||||
color: var(--ha-card-header-color, --primary-text-color);
|
||||
font-family: var(--ha-card-header-font-family, inherit);
|
||||
font-size: var(--ha-card-header-font-size, 24px);
|
||||
letter-spacing: -0.012em;
|
||||
line-height: 32px;
|
||||
display: block;
|
||||
padding: 24px 16px 16px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
private _createCardElement(cardConfig: LovelaceCardConfig) {
|
||||
const element = createCardElement(cardConfig) as LovelaceCard;
|
||||
if (this._hass) {
|
||||
|
2
src/panels/lovelace/common/compute-unused-entities.ts
Normal file → Executable file
2
src/panels/lovelace/common/compute-unused-entities.ts
Normal file → Executable file
@ -1,7 +1,7 @@
|
||||
import { LovelaceConfig, ActionConfig } from "../../../data/lovelace";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
|
||||
const EXCLUDED_DOMAINS = ["zone"];
|
||||
const EXCLUDED_DOMAINS = ["zone", "persistent_notification"];
|
||||
|
||||
const addFromAction = (entities: Set<string>, actionConfig: ActionConfig) => {
|
||||
if (
|
||||
|
@ -1,137 +0,0 @@
|
||||
// @ts-ignore
|
||||
import CodeMirror from "codemirror";
|
||||
import "codemirror/mode/yaml/yaml";
|
||||
// @ts-ignore
|
||||
import codeMirrorCSS from "codemirror/lib/codemirror.css";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import { customElement } from "lit-element";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"yaml-changed": {
|
||||
value: string;
|
||||
};
|
||||
"yaml-save": undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("hui-yaml-editor")
|
||||
export class HuiYamlEditor extends HTMLElement {
|
||||
public _hass?: HomeAssistant;
|
||||
|
||||
public codemirror!: any;
|
||||
|
||||
private _value: string;
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
CodeMirror.commands.save = (cm: CodeMirror) => {
|
||||
fireEvent(cm.getWrapperElement(), "yaml-save");
|
||||
};
|
||||
this._value = "";
|
||||
const shadowRoot = this.attachShadow({ mode: "open" });
|
||||
shadowRoot.innerHTML = `
|
||||
<style>
|
||||
${codeMirrorCSS}
|
||||
.CodeMirror {
|
||||
height: var(--code-mirror-height, auto);
|
||||
direction: var(--code-mirror-direction, ltr);
|
||||
}
|
||||
.CodeMirror-scroll {
|
||||
max-height: var(--code-mirror-max-height, --code-mirror-height);
|
||||
}
|
||||
.CodeMirror-gutters {
|
||||
border-right: 1px solid var(--paper-input-container-color, var(--secondary-text-color));
|
||||
background-color: var(--paper-dialog-background-color, var(--primary-background-color));
|
||||
transition: 0.2s ease border-right;
|
||||
}
|
||||
.CodeMirror-focused .CodeMirror-gutters {
|
||||
border-right: 2px solid var(--paper-input-container-focus-color, var(--primary-color));;
|
||||
}
|
||||
.CodeMirror-linenumber {
|
||||
color: var(--paper-dialog-color, var(--primary-text-color));
|
||||
}
|
||||
.rtl .CodeMirror-vscrollbar {
|
||||
right: auto;
|
||||
left: 0px;
|
||||
}
|
||||
.rtl-gutter {
|
||||
width: 20px;
|
||||
}
|
||||
</style>`;
|
||||
}
|
||||
|
||||
set hass(hass: HomeAssistant) {
|
||||
this._hass = hass;
|
||||
if (this._hass) {
|
||||
this.setScrollBarDirection();
|
||||
}
|
||||
}
|
||||
|
||||
set value(value: string) {
|
||||
if (this.codemirror) {
|
||||
if (value !== this.codemirror.getValue()) {
|
||||
this.codemirror.setValue(value);
|
||||
}
|
||||
}
|
||||
this._value = value;
|
||||
}
|
||||
|
||||
get value(): string {
|
||||
return this.codemirror.getValue();
|
||||
}
|
||||
|
||||
get hasComments(): boolean {
|
||||
return this.shadowRoot!.querySelector("span.cm-comment") ? true : false;
|
||||
}
|
||||
|
||||
public connectedCallback(): void {
|
||||
if (!this.codemirror) {
|
||||
this.codemirror = CodeMirror(
|
||||
(this.shadowRoot as unknown) as HTMLElement,
|
||||
{
|
||||
value: this._value,
|
||||
lineNumbers: true,
|
||||
mode: "yaml",
|
||||
tabSize: 2,
|
||||
autofocus: true,
|
||||
viewportMargin: Infinity,
|
||||
extraKeys: {
|
||||
Tab: "indentMore",
|
||||
"Shift-Tab": "indentLess",
|
||||
},
|
||||
gutters:
|
||||
this._hass && computeRTL(this._hass!)
|
||||
? ["rtl-gutter", "CodeMirror-linenumbers"]
|
||||
: [],
|
||||
}
|
||||
);
|
||||
this.setScrollBarDirection();
|
||||
this.codemirror.on("changes", () => this._onChange());
|
||||
} else {
|
||||
this.codemirror.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private _onChange(): void {
|
||||
fireEvent(this, "yaml-changed", { value: this.codemirror.getValue() });
|
||||
}
|
||||
|
||||
private setScrollBarDirection(): void {
|
||||
if (!this.codemirror) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.codemirror
|
||||
.getWrapperElement()
|
||||
.classList.toggle("rtl", computeRTL(this._hass!));
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-yaml-editor": HuiYamlEditor;
|
||||
}
|
||||
}
|
@ -15,11 +15,12 @@ import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceCardConfig } from "../../../../data/lovelace";
|
||||
import { LovelaceCardEditor } from "../../types";
|
||||
import { getCardElementTag } from "../../common/get-card-element-tag";
|
||||
import { computeRTL } from "../../../../common/util/compute_rtl";
|
||||
|
||||
import "../../components/hui-yaml-editor";
|
||||
import "../../../../components/ha-code-editor";
|
||||
// This is not a duplicate import, one is for types, one is for element.
|
||||
// tslint:disable-next-line
|
||||
import { HuiYamlEditor } from "../../components/hui-yaml-editor";
|
||||
import { HaCodeEditor } from "../../../../components/ha-code-editor";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { EntityConfig } from "../../entity-rows/types";
|
||||
|
||||
@ -43,7 +44,7 @@ export interface UIConfigChangedEvent extends Event {
|
||||
|
||||
@customElement("hui-card-editor")
|
||||
export class HuiCardEditor extends LitElement {
|
||||
@property() public hass?: HomeAssistant;
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() private _yaml?: string;
|
||||
@property() private _config?: LovelaceCardConfig;
|
||||
@ -64,12 +65,6 @@ export class HuiCardEditor extends LitElement {
|
||||
try {
|
||||
this._config = yaml.safeLoad(this.yaml);
|
||||
this._updateConfigElement();
|
||||
setTimeout(() => {
|
||||
if (this._yamlEditor) {
|
||||
this._yamlEditor.codemirror.refresh();
|
||||
}
|
||||
fireEvent(this as HTMLElement, "iron-resize");
|
||||
}, 1);
|
||||
this._error = undefined;
|
||||
} catch (err) {
|
||||
this._error = err.message;
|
||||
@ -93,14 +88,19 @@ export class HuiCardEditor extends LitElement {
|
||||
return this._error !== undefined;
|
||||
}
|
||||
|
||||
private get _yamlEditor(): HuiYamlEditor {
|
||||
return this.shadowRoot!.querySelector("hui-yaml-editor")!;
|
||||
private get _yamlEditor(): HaCodeEditor {
|
||||
return this.shadowRoot!.querySelector("ha-code-editor")! as HaCodeEditor;
|
||||
}
|
||||
|
||||
public toggleMode() {
|
||||
this._GUImode = !this._GUImode;
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._refreshYamlEditor();
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div class="wrapper">
|
||||
@ -120,11 +120,14 @@ export class HuiCardEditor extends LitElement {
|
||||
`
|
||||
: html`
|
||||
<div class="yaml-editor">
|
||||
<hui-yaml-editor
|
||||
.hass=${this.hass}
|
||||
<ha-code-editor
|
||||
mode="yaml"
|
||||
autofocus
|
||||
.value=${this.yaml}
|
||||
@yaml-changed=${this._handleYAMLChanged}
|
||||
></hui-yaml-editor>
|
||||
.error=${this._error}
|
||||
.rtl=${computeRTL(this.hass)}
|
||||
@value-changed=${this._handleYAMLChanged}
|
||||
></ha-code-editor>
|
||||
</div>
|
||||
`}
|
||||
${this._error
|
||||
@ -145,9 +148,12 @@ export class HuiCardEditor extends LitElement {
|
||||
<mwc-button
|
||||
@click=${this.toggleMode}
|
||||
?disabled=${this._warning || this._error}
|
||||
?unelevated=${this._GUImode === false}
|
||||
>
|
||||
<ha-icon icon="mdi:code-braces"></ha-icon>
|
||||
${this.hass!.localize(
|
||||
this._GUImode
|
||||
? "ui.panel.lovelace.editor.edit_card.show_code_editor"
|
||||
: "ui.panel.lovelace.editor.edit_card.show_visual_editor"
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
</div>
|
||||
@ -160,13 +166,25 @@ export class HuiCardEditor extends LitElement {
|
||||
if (changedProperties.has("_GUImode")) {
|
||||
if (this._GUImode === false) {
|
||||
// Refresh code editor when switching to yaml mode
|
||||
this._yamlEditor.codemirror.refresh();
|
||||
this._yamlEditor.codemirror.focus();
|
||||
this._refreshYamlEditor(true);
|
||||
}
|
||||
fireEvent(this as HTMLElement, "iron-resize");
|
||||
}
|
||||
}
|
||||
|
||||
private _refreshYamlEditor(focus = false) {
|
||||
// wait on render
|
||||
setTimeout(() => {
|
||||
if (this._yamlEditor && this._yamlEditor.codemirror) {
|
||||
this._yamlEditor.codemirror.refresh();
|
||||
if (focus) {
|
||||
this._yamlEditor.codemirror.focus();
|
||||
}
|
||||
}
|
||||
fireEvent(this as HTMLElement, "iron-resize");
|
||||
}, 1);
|
||||
}
|
||||
|
||||
private _handleUIConfigChanged(ev: UIConfigChangedEvent) {
|
||||
ev.stopPropagation();
|
||||
const config = ev.detail.config;
|
||||
|
@ -14,31 +14,31 @@ import { getCardElementTag } from "../../common/get-card-element-tag";
|
||||
import { CardPickTarget } from "../types";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
|
||||
const cards = [
|
||||
{ name: "Alarm panel", type: "alarm-panel" },
|
||||
{ name: "Conditional", type: "conditional" },
|
||||
{ name: "Entities", type: "entities" },
|
||||
{ name: "Entity Button", type: "entity-button" },
|
||||
{ name: "Entity Filter", type: "entity-filter" },
|
||||
{ name: "Gauge", type: "gauge" },
|
||||
{ name: "Glance", type: "glance" },
|
||||
{ name: "History Graph", type: "history-graph" },
|
||||
{ name: "Horizontal Stack", type: "horizontal-stack" },
|
||||
{ name: "iFrame", type: "iframe" },
|
||||
{ name: "Light", type: "light" },
|
||||
{ name: "Map", type: "map" },
|
||||
{ name: "Markdown", type: "markdown" },
|
||||
{ name: "Media Control", type: "media-control" },
|
||||
{ name: "Picture", type: "picture" },
|
||||
{ name: "Picture Elements", type: "picture-elements" },
|
||||
{ name: "Picture Entity", type: "picture-entity" },
|
||||
{ name: "Picture Glance", type: "picture-glance" },
|
||||
{ name: "Plant Status", type: "plant-status" },
|
||||
{ name: "Sensor", type: "sensor" },
|
||||
{ name: "Shopping List", type: "shopping-list" },
|
||||
{ name: "Thermostat", type: "thermostat" },
|
||||
{ name: "Vertical Stack", type: "vertical-stack" },
|
||||
{ name: "Weather Forecast", type: "weather-forecast" },
|
||||
const cards: string[] = [
|
||||
"alarm-panel",
|
||||
"conditional",
|
||||
"entities",
|
||||
"entity-button",
|
||||
"entity-filter",
|
||||
"gauge",
|
||||
"glance",
|
||||
"history-graph",
|
||||
"horizontal-stack",
|
||||
"iframe",
|
||||
"light",
|
||||
"map",
|
||||
"markdown",
|
||||
"media-control",
|
||||
"picture",
|
||||
"picture-elements",
|
||||
"picture-entity",
|
||||
"picture-glance",
|
||||
"plant-status",
|
||||
"sensor",
|
||||
"shopping-list",
|
||||
"thermostat",
|
||||
"vertical-stack",
|
||||
"weather-forecast",
|
||||
];
|
||||
|
||||
@customElement("hui-card-picker")
|
||||
@ -53,10 +53,12 @@ export class HuiCardPicker extends LitElement {
|
||||
${this.hass!.localize("ui.panel.lovelace.editor.edit_card.pick_card")}
|
||||
</h3>
|
||||
<div class="cards-container">
|
||||
${cards.map((card) => {
|
||||
${cards.map((card: string) => {
|
||||
return html`
|
||||
<mwc-button @click="${this._cardPicked}" .type="${card.type}">
|
||||
${card.name}
|
||||
<mwc-button @click="${this._cardPicked}" .type="${card}">
|
||||
${this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.${card}.name`
|
||||
)}
|
||||
</mwc-button>
|
||||
`;
|
||||
})}
|
||||
|
@ -62,10 +62,21 @@ export class HuiDialogEditCard extends LitElement {
|
||||
return html``;
|
||||
}
|
||||
|
||||
let heading: string;
|
||||
if (this._cardConfig && this._cardConfig.type) {
|
||||
heading = `${this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.${this._cardConfig.type}.name`
|
||||
)} ${this.hass!.localize("ui.panel.lovelace.editor.edit_card.header")}`;
|
||||
} else {
|
||||
heading = this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_card.header"
|
||||
);
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-paper-dialog with-backdrop opened modal>
|
||||
<h2>
|
||||
${this.hass!.localize("ui.panel.lovelace.editor.edit_card.header")}
|
||||
${heading}
|
||||
</h2>
|
||||
<paper-dialog-scrollable>
|
||||
${this._cardConfig === undefined
|
||||
|
@ -101,7 +101,7 @@ export class HuiAlarmPanelCardEditor extends LitElement
|
||||
})}
|
||||
<paper-dropdown-menu
|
||||
.label="${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.alarm_panel.available_states"
|
||||
"ui.panel.lovelace.editor.card.alarm-panel.available_states"
|
||||
)}"
|
||||
@value-changed="${this._stateAdded}"
|
||||
>
|
||||
|
@ -22,7 +22,7 @@ import "../../../../components/data-table/ha-data-table";
|
||||
// tslint:disable-next-line
|
||||
import {
|
||||
SelectionChangedEvent,
|
||||
DataTabelColumnContainer,
|
||||
DataTableColumnContainer,
|
||||
} from "../../../../components/data-table/ha-data-table";
|
||||
|
||||
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
||||
@ -55,7 +55,7 @@ export class HuiUnusedEntities extends LitElement {
|
||||
}
|
||||
|
||||
private _columns = memoizeOne((narrow: boolean) => {
|
||||
const columns: DataTabelColumnContainer = {
|
||||
const columns: DataTableColumnContainer = {
|
||||
entity: {
|
||||
title: "Entity",
|
||||
sortable: true,
|
||||
|
@ -66,7 +66,7 @@ class HuiMediaPlayerEntityRow extends LitElement implements EntityRow {
|
||||
<hui-generic-entity-row
|
||||
.hass="${this.hass}"
|
||||
.config="${this._config}"
|
||||
.showSecondary="false"
|
||||
.showSecondary=${false}
|
||||
>
|
||||
${OFF_STATES.includes(stateObj.state)
|
||||
? html`
|
||||
|
@ -14,11 +14,12 @@ import { Lovelace } from "./types";
|
||||
|
||||
import "../../components/ha-icon";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import "./components/hui-yaml-editor";
|
||||
import "../../components/ha-code-editor";
|
||||
// This is not a duplicate import, one is for types, one is for element.
|
||||
// tslint:disable-next-line
|
||||
import { HuiYamlEditor } from "./components/hui-yaml-editor";
|
||||
import { HaCodeEditor } from "../../components/ha-code-editor";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { computeRTL } from "../../common/util/compute_rtl";
|
||||
|
||||
const lovelaceStruct = struct.interface({
|
||||
title: "string?",
|
||||
@ -27,12 +28,12 @@ const lovelaceStruct = struct.interface({
|
||||
});
|
||||
|
||||
class LovelaceFullConfigEditor extends LitElement {
|
||||
public hass?: HomeAssistant;
|
||||
public hass!: HomeAssistant;
|
||||
public lovelace?: Lovelace;
|
||||
public closeEditor?: () => void;
|
||||
private _saving?: boolean;
|
||||
private _changed?: boolean;
|
||||
private _generation?: number;
|
||||
private _generation = 1;
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
@ -80,12 +81,15 @@ class LovelaceFullConfigEditor extends LitElement {
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
<div class="content">
|
||||
<hui-yaml-editor
|
||||
<ha-code-editor
|
||||
mode="yaml"
|
||||
autofocus
|
||||
.rtl=${computeRTL(this.hass)}
|
||||
.hass="${this.hass}"
|
||||
@yaml-changed="${this._yamlChanged}"
|
||||
@yaml-save="${this._handleSave}"
|
||||
@value-changed="${this._yamlChanged}"
|
||||
@editor-save="${this._handleSave}"
|
||||
>
|
||||
</hui-yaml-editor>
|
||||
</ha-code-editor>
|
||||
</div>
|
||||
</app-header-layout>
|
||||
`;
|
||||
@ -93,8 +97,6 @@ class LovelaceFullConfigEditor extends LitElement {
|
||||
|
||||
protected firstUpdated() {
|
||||
this.yamlEditor.value = yaml.safeDump(this.lovelace!.config);
|
||||
this.yamlEditor.codemirror.clearHistory();
|
||||
this._generation = this.yamlEditor.codemirror.changeGeneration(true);
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
@ -140,10 +142,9 @@ class LovelaceFullConfigEditor extends LitElement {
|
||||
}
|
||||
|
||||
private _yamlChanged() {
|
||||
if (!this._generation) {
|
||||
return;
|
||||
}
|
||||
this._changed = !this.yamlEditor.codemirror.isClean(this._generation);
|
||||
this._changed = !this.yamlEditor
|
||||
.codemirror!.getDoc()
|
||||
.isClean(this._generation);
|
||||
if (this._changed && !window.onbeforeunload) {
|
||||
window.onbeforeunload = () => {
|
||||
return true;
|
||||
@ -199,14 +200,16 @@ class LovelaceFullConfigEditor extends LitElement {
|
||||
} catch (err) {
|
||||
alert(`Unable to save YAML: ${err}`);
|
||||
}
|
||||
this._generation = this.yamlEditor.codemirror.changeGeneration(true);
|
||||
this._generation = this.yamlEditor
|
||||
.codemirror!.getDoc()
|
||||
.changeGeneration(true);
|
||||
window.onbeforeunload = null;
|
||||
this._saving = false;
|
||||
this._changed = false;
|
||||
}
|
||||
|
||||
private get yamlEditor(): HuiYamlEditor {
|
||||
return this.shadowRoot!.querySelector("hui-yaml-editor")!;
|
||||
private get yamlEditor(): HaCodeEditor {
|
||||
return this.shadowRoot!.querySelector("ha-code-editor")! as HaCodeEditor;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -236,7 +236,20 @@ class HUIRoot extends LitElement {
|
||||
>
|
||||
${this.lovelace!.config.views.map(
|
||||
(view) => html`
|
||||
<paper-tab aria-label="${view.title}">
|
||||
<paper-tab
|
||||
aria-label="${view.title}"
|
||||
class="${classMap({
|
||||
"hide-tab": Boolean(
|
||||
!this._editMode &&
|
||||
view.visible !== undefined &&
|
||||
((Array.isArray(view.visible) &&
|
||||
!view.visible.some(
|
||||
(e) => e.user === this.hass!.user!.id
|
||||
)) ||
|
||||
view.visible === false)
|
||||
),
|
||||
})}"
|
||||
>
|
||||
${this._editMode
|
||||
? html`
|
||||
<ha-paper-icon-button-arrow-prev
|
||||
@ -392,6 +405,9 @@ class HUIRoot extends LitElement {
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
.hide-tab {
|
||||
display: none;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -138,7 +138,6 @@ export class HUIView extends LitElement {
|
||||
flex-basis: 0;
|
||||
flex-grow: 1;
|
||||
max-width: 500px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.column > * {
|
||||
|
@ -23,7 +23,9 @@ class AdvancedModeCard extends LitElement {
|
||||
return html`
|
||||
<ha-card>
|
||||
<div class="card-header">
|
||||
<div class="title">Advanced mode</div>
|
||||
<div class="title">
|
||||
${this.hass.localize("ui.panel.profile.advanced_mode.title")}
|
||||
</div>
|
||||
<ha-switch
|
||||
.checked=${this.coreUserData && this.coreUserData.showAdvanced}
|
||||
.disabled=${this.coreUserData === undefined}
|
||||
@ -31,10 +33,7 @@ class AdvancedModeCard extends LitElement {
|
||||
></ha-switch>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
Home Assistant hides advanced features and options by default. You can
|
||||
make these features accessible by checking this toggle. This is a
|
||||
user-specific setting and does not impact other users using Home
|
||||
Assistant.
|
||||
${this.hass.localize("ui.panel.profile.advanced_mode.description")}
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
|
@ -28,7 +28,7 @@ class HaPushNotificationsRow extends LocalizeMixin(PolymerElement) {
|
||||
<span slot="description">
|
||||
[[_description(_platformLoaded, _pushSupported)]]
|
||||
<a
|
||||
href="https://www.home-assistant.io/integrations/notify.html5/"
|
||||
href="https://www.home-assistant.io/integrations/html5"
|
||||
target="_blank"
|
||||
>[[localize('ui.panel.profile.push_notifications.link_promo')]]</a
|
||||
>
|
||||
|
13
src/resources/codemirror.ondemand.ts
Normal file
13
src/resources/codemirror.ondemand.ts
Normal file
@ -0,0 +1,13 @@
|
||||
interface LoadedCodeMirror {
|
||||
codeMirror: any;
|
||||
codeMirrorCss: any;
|
||||
}
|
||||
|
||||
let loaded: Promise<LoadedCodeMirror>;
|
||||
|
||||
export const loadCodeMirror = async (): Promise<LoadedCodeMirror> => {
|
||||
if (!loaded) {
|
||||
loaded = import(/* webpackChunkName: "codemirror" */ "./codemirror");
|
||||
}
|
||||
return loaded;
|
||||
};
|
13
src/resources/codemirror.ts
Normal file
13
src/resources/codemirror.ts
Normal file
@ -0,0 +1,13 @@
|
||||
// @ts-ignore
|
||||
import _CodeMirror, { Editor } from "codemirror";
|
||||
// @ts-ignore
|
||||
import _codeMirrorCss from "codemirror/lib/codemirror.css";
|
||||
import "codemirror/mode/yaml/yaml";
|
||||
import "codemirror/mode/jinja2/jinja2";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
|
||||
_CodeMirror.commands.save = (cm: Editor) => {
|
||||
fireEvent(cm.getWrapperElement(), "editor-save");
|
||||
};
|
||||
export const codeMirror: any = _CodeMirror;
|
||||
export const codeMirrorCss: any = _codeMirrorCss;
|
@ -33,6 +33,8 @@ documentContainer.innerHTML = `<custom-style>
|
||||
|
||||
--scrollbar-thumb-color: rgb(194, 194, 194);
|
||||
|
||||
--error-state-color: #db4437;
|
||||
|
||||
/* states and badges */
|
||||
--state-icon-color: #44739e;
|
||||
--state-icon-active-color: #FDD835;
|
||||
|
352
src/translations/en.json
Normal file → Executable file
352
src/translations/en.json
Normal file → Executable file
@ -566,11 +566,20 @@
|
||||
"zha_device_info": {
|
||||
"manuf": "by {manufacturer}",
|
||||
"no_area": "No Area",
|
||||
"buttons": {
|
||||
"add": "Add Devices",
|
||||
"remove": "Remove Device",
|
||||
"reconfigure": "Reconfigure Device"
|
||||
},
|
||||
"services": {
|
||||
"reconfigure": "Reconfigure ZHA device (heal device). Use this if you are having issues with the device. If the device in question is a battery powered device please ensure it is awake and accepting commands when you use this service.",
|
||||
"updateDeviceName": "Set a custom name for this device in the device registry.",
|
||||
"remove": "Remove a device from the ZigBee network."
|
||||
"remove": "Remove a device from the Zigbee network."
|
||||
},
|
||||
"quirk": "Quirk",
|
||||
"last_seen": "Last Seen",
|
||||
"power_source": "Power Source",
|
||||
"unknown": "Unknown",
|
||||
"zha_device_card": {
|
||||
"device_name_placeholder": "User given name",
|
||||
"area_picker_label": "Area",
|
||||
@ -597,7 +606,8 @@
|
||||
},
|
||||
"notification_toast": {
|
||||
"service_call_failed": "Failed to call service {service}.",
|
||||
"connection_lost": "Connection lost. Reconnecting…"
|
||||
"connection_lost": "Connection lost. Reconnecting…",
|
||||
"triggered": "Triggered {name}"
|
||||
},
|
||||
"sidebar": {
|
||||
"external_app_configuration": "App Configuration"
|
||||
@ -606,6 +616,11 @@
|
||||
"config": {
|
||||
"header": "Configure Home Assistant",
|
||||
"introduction": "Here it is possible to configure your components and Home Assistant. Not everything is possible to configure from the UI yet, but we're working on it.",
|
||||
"common": {
|
||||
"editor": {
|
||||
"confirm_unsaved": "You have unsaved changes. Are you sure you want to leave?"
|
||||
}
|
||||
},
|
||||
"area_registry": {
|
||||
"caption": "Area Registry",
|
||||
"description": "Overview of all areas in your home.",
|
||||
@ -815,7 +830,12 @@
|
||||
"label": "And"
|
||||
},
|
||||
"device": {
|
||||
"label": "Device"
|
||||
"label": "Device",
|
||||
"extra_fields": {
|
||||
"above": "Above",
|
||||
"below": "Below",
|
||||
"for": "Duration"
|
||||
}
|
||||
},
|
||||
"numeric_state": {
|
||||
"label": "[%key:ui::panel::config::automation::editor::triggers::type::numeric_state::label%]",
|
||||
@ -889,6 +909,9 @@
|
||||
},
|
||||
"device_id": {
|
||||
"label": "Device"
|
||||
},
|
||||
"scene": {
|
||||
"label": "Activate scene"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -896,13 +919,175 @@
|
||||
},
|
||||
"script": {
|
||||
"caption": "Script",
|
||||
"description": "Create and edit scripts"
|
||||
"description": "Create and edit scripts",
|
||||
"picker": {
|
||||
"header": "Script Editor",
|
||||
"introduction": "The script editor allows you to create and edit scripts. Please follow the link below to read the instructions to make sure that you have configured Home Assistant correctly.",
|
||||
"learn_more": "Learn more about scripts",
|
||||
"no_scripts": "We couldn’t find any editable scripts",
|
||||
"add_script": "Add script"
|
||||
},
|
||||
"editor": {
|
||||
"header": "Script: {name}",
|
||||
"default_name": "New Script",
|
||||
"load_error_not_editable": "Only scripts inside scripts.yaml are editable.",
|
||||
"delete_confirm": "Are you sure you want to delete this script?"
|
||||
}
|
||||
},
|
||||
"cloud": {
|
||||
"caption": "Home Assistant Cloud",
|
||||
"description_login": "Logged in as {email}",
|
||||
"description_not_login": "Not logged in",
|
||||
"description_features": "Control away from home, integrate with Alexa and Google Assistant."
|
||||
"description_features": "Control away from home, integrate with Alexa and Google Assistant.",
|
||||
"login": {
|
||||
"title": "Cloud Login",
|
||||
"introduction": "Home Assistant Cloud provides you with a secure remote connection to your instance while away from home. It also allows you to connect with cloud-only services: Amazon Alexa and Google Assistant.",
|
||||
"introduction2": "This service is run by our partner ",
|
||||
"introduction2a": ", a company founded by the founders of Home Assistant and Hass.io.",
|
||||
"introduction3": "Home Assistant Cloud is a subscription service with a free one month trial. No payment information necessary.",
|
||||
"learn_more_link": "Learn more about Home Assistant Cloud",
|
||||
"dismiss": "Dismiss",
|
||||
"sign_in": "Sign in",
|
||||
"email": "Email",
|
||||
"email_error_msg": "Invalid email",
|
||||
"password": "Password",
|
||||
"password_error_msg": "Passwords are at least 8 characters",
|
||||
"forgot_password": "forgot password?",
|
||||
"start_trial": "Start your free 1 month trial",
|
||||
"trial_info": "No payment information necessary",
|
||||
"alert_password_change_required": "You need to change your password before logging in.",
|
||||
"alert_email_confirm_necessary": "You need to confirm your email before logging in."
|
||||
},
|
||||
"forgot_password": {
|
||||
"title": "Forgot password",
|
||||
"subtitle": "Forgot your password",
|
||||
"instructions": "Enter your email address and we will send you a link to reset your password.",
|
||||
"email": "Email",
|
||||
"email_error_msg": "Invalid email",
|
||||
"send_reset_email": "Send reset email",
|
||||
"check_your_email": "Check your email for instructions on how to reset your password."
|
||||
},
|
||||
"register": {
|
||||
"title": "Register Account",
|
||||
"headline": "Start your free trial",
|
||||
"information": "Create an account to start your free one month trial with Home Assistant Cloud. No payment information necessary.",
|
||||
"information2": "The trial will give you access to all the benefits of Home Assistant Cloud, including:",
|
||||
"feature_remote_control": "Control of Home Assistant away from home",
|
||||
"feature_google_home": "Integration with Google Assistant",
|
||||
"feature_amazon_alexa": "Integration with Amazon Alexa",
|
||||
"feature_webhook_apps": "Easy integration with webhook-based apps like OwnTracks",
|
||||
"information3": "This service is run by our partner ",
|
||||
"information3a": ", a company founded by the founders of Home Assistant and Hass.io.",
|
||||
"information4": "By registering an account you agree to the following terms and conditions.",
|
||||
"link_terms_conditions": "Terms and Conditions",
|
||||
"link_privacy_policy": "Privacy Policy",
|
||||
"create_account": "Create Account",
|
||||
"email_address": "Email address",
|
||||
"email_error_msg": "Invalid email",
|
||||
"password": "Password",
|
||||
"password_error_msg": "Passwords are at least 8 characters",
|
||||
"start_trial": "Start Trial",
|
||||
"resend_confirm_email": "Resend confirmation email",
|
||||
"account_created": "Account created! Check your email for instructions on how to activate your account."
|
||||
},
|
||||
"account": {
|
||||
"thank_you_note": "Thank you for being part of Home Assistant Cloud. It's because of people like you that we are able to make a great home automation experience for everyone. Thank you!",
|
||||
"nabu_casa_account": "Nabu Casa Account",
|
||||
"connection_status": "Cloud connection status",
|
||||
"manage_account": "Manage Account",
|
||||
"sign_out": "Sign out",
|
||||
"integrations": "Integrations",
|
||||
"integrations_introduction": "Integrations for Home Assistant Cloud allow you to connect with services in the cloud without having to expose your Home Assistant instance publicly on the internet.",
|
||||
"integrations_introduction2": "Check the website for ",
|
||||
"integrations_link_all_features": " all available features",
|
||||
"connected": "Connected",
|
||||
"not_connected": "Not Connected",
|
||||
"fetching_subscription": "Fetching subscription…",
|
||||
"remote": {
|
||||
"title": "Remote Control",
|
||||
"access_is_being_prepared": "Remote access is being prepared. We will notify you when it's ready.",
|
||||
"info": "Home Assistant Cloud provides a secure remote connection to your instance while away from home.",
|
||||
"instance_is_available": "Your instance is available at",
|
||||
"instance_will_be_available": "Your instance will be available at",
|
||||
"link_learn_how_it_works": "Learn how it works",
|
||||
"certificate_info": "Certificate Info"
|
||||
},
|
||||
"alexa": {
|
||||
"title": "Alexa",
|
||||
"info": "With the Alexa integration for Home Assistant Cloud you'll be able to control all your Home Assistant devices via any Alexa-enabled device.",
|
||||
"enable_ha_skill": "Enable the Home Assistant skill for Alexa",
|
||||
"config_documentation": "Config documentation",
|
||||
"enable_state_reporting": "Enable State Reporting",
|
||||
"info_state_reporting": "If you enable state reporting, Home Assistant will send all state changes of exposed entities to Amazon. This allows you to always see the latest states in the Alexa app and use the state changes to create routines.",
|
||||
"sync_entities": "Sync Entities",
|
||||
"manage_entities": "Manage Entities",
|
||||
"sync_entities_error": "Failed to sync entities:",
|
||||
"state_reporting_error": "Unable to {enable_disable} report state.",
|
||||
"enable": "enable",
|
||||
"disable": "disable"
|
||||
},
|
||||
"google": {
|
||||
"title": "Google Assistant",
|
||||
"info": "With the Google Assistant integration for Home Assistant Cloud you'll be able to control all your Home Assistant devices via any Google Assistant-enabled device.",
|
||||
"enable_ha_skill": "Activate the Home Assistant skill for Google Assistant",
|
||||
"config_documentation": "Config documentation",
|
||||
"enable_state_reporting": "Enable State Reporting",
|
||||
"info_state_reporting": "If you enable state reporting, Home Assistant will send all state changes of exposed entities to Google. This allows you to always see the latest states in the Google app.",
|
||||
"security_devices": "Security Devices",
|
||||
"enter_pin_info": "Please enter a pin to interact with security devices. Security devices are doors, garage doors and locks. You will be asked to say/enter this pin when interacting with such devices via Google Assistant.",
|
||||
"devices_pin": "Security Devices Pin",
|
||||
"enter_pin_hint": "Enter a PIN to use security devices",
|
||||
"sync_entities": "Sync Entities to Google",
|
||||
"manage_entities": "Manage Entities",
|
||||
"enter_pin_error": "Unable to store pin:"
|
||||
},
|
||||
"webhooks": {
|
||||
"title": "Webhooks",
|
||||
"info": "Anything that is configured to be triggered by a webhook can be given a publicly accessible URL to allow you to send data back to Home Assistant from anywhere, without exposing your instance to the internet.",
|
||||
"no_hooks_yet": "Looks like you have no webhooks yet. Get started by configuring a ",
|
||||
"no_hooks_yet_link_integration": "webhook-based integration",
|
||||
"no_hooks_yet2": " or by creating a ",
|
||||
"no_hooks_yet_link_automation": "webhook automation",
|
||||
"link_learn_more": "Learn more about creating webhook-powered automations.",
|
||||
"loading": "Loading ...",
|
||||
"manage": "Manage",
|
||||
"disable_hook_error_msg": "Failed to disable webhook:"
|
||||
}
|
||||
},
|
||||
"alexa": {
|
||||
"title": "Alexa",
|
||||
"banner": "Editing which entities are exposed via this UI is disabled because you have configured entity filters in configuration.yaml.",
|
||||
"exposed_entities": "Exposed entities",
|
||||
"not_exposed_entities": "Not Exposed entities",
|
||||
"expose": "Expose to Alexa"
|
||||
},
|
||||
"dialog_certificate": {
|
||||
"certificate_information": "Certificate Information",
|
||||
"certificate_expiration_date": "Certificate expiration date",
|
||||
"will_be_auto_renewed": "Will be automatically renewed",
|
||||
"fingerprint": "Certificate fingerprint:",
|
||||
"close": "Close"
|
||||
},
|
||||
"google": {
|
||||
"title": "Google Assistant",
|
||||
"expose": "Expose to Google Assistant",
|
||||
"disable_2FA": "Disable two factor authentication",
|
||||
"banner": "Editing which entities are exposed via this UI is disabled because you have configured entity filters in configuration.yaml.",
|
||||
"exposed_entities": "Exposed entities",
|
||||
"not_exposed_entities": "Not Exposed entities",
|
||||
"sync_to_google": "Synchronizing changes to Google."
|
||||
},
|
||||
"dialog_cloudhook": {
|
||||
"webhook_for": "Webhook for {name}",
|
||||
"available_at": "The webhook is available at the following url:",
|
||||
"managed_by_integration": "This webhook is managed by an integration and cannot be disabled.",
|
||||
"info_disable_webhook": "If you no longer want to use this webhook, you can",
|
||||
"link_disable_webhook": "disable it",
|
||||
"view_documentation": "View documentation",
|
||||
"close": "Close",
|
||||
"confirm_disable": "Are you sure you want to disable this webhook?",
|
||||
"copied_to_clipboard": "Copied to clipboard"
|
||||
}
|
||||
},
|
||||
"devices": {
|
||||
"caption": "Devices",
|
||||
@ -927,7 +1112,13 @@
|
||||
"introduction": "Home Assistant keeps a registry of every entity it has ever seen that can be uniquely identified. Each of these entities will have an entity ID assigned which will be reserved for just this entity.",
|
||||
"introduction2": "Use the entity registry to override the name, change the entity ID or remove the entry from Home Assistant. Note, removing the entity registry entry won't remove the entity. To do that, follow the link below and remove it from the integrations page.",
|
||||
"integrations_page": "Integrations page",
|
||||
"show_disabled": "Show disabled entities"
|
||||
"show_disabled": "Show disabled entities",
|
||||
"headers": {
|
||||
"name": "Name",
|
||||
"entity_id": "Entity ID",
|
||||
"integration": "Integration",
|
||||
"enabled": "Enabled"
|
||||
}
|
||||
},
|
||||
"editor": {
|
||||
"unavailable": "This entity is not currently available.",
|
||||
@ -944,11 +1135,27 @@
|
||||
"person": {
|
||||
"caption": "Persons",
|
||||
"description": "Manage the persons that Home Assistant tracks.",
|
||||
"introduction": "Here you can define each person of interest in Home Assistant.",
|
||||
"note_about_persons_configured_in_yaml": "Note: persons configured via configuration.yaml cannot be edited via the UI.",
|
||||
"no_persons_created_yet": "Looks like you have not created any persons yet.",
|
||||
"create_person": "Create Person",
|
||||
"add_person": "Add Person",
|
||||
"confirm_delete": "Are you sure you want to delete this person?",
|
||||
"confirm_delete2": "All devices belonging to this person will become unassigned.",
|
||||
"detail": {
|
||||
"new_person": "New Person",
|
||||
"name": "Name",
|
||||
"name_error_msg": "Name is required",
|
||||
"linked_user": "Linked User",
|
||||
"device_tracker_intro": "Select the devices that belong to this person.",
|
||||
"no_device_tracker_available_intro": "When you have devices that indicate the presence of a person, you will be able to assign them to a person here. You can add your first device by adding a presence-detection integration from the integrations page.",
|
||||
"link_presence_detection_integrations": "Presence Detection Integrations",
|
||||
"link_integrations_page": "Integrations page",
|
||||
"device_tracker_picked": "Track Device",
|
||||
"device_tracker_pick": "Pick device to track"
|
||||
"device_tracker_pick": "Pick device to track",
|
||||
"delete": "Delete",
|
||||
"create": "Create",
|
||||
"update": "Update"
|
||||
}
|
||||
},
|
||||
"integrations": {
|
||||
@ -957,6 +1164,9 @@
|
||||
"discovered": "Discovered",
|
||||
"configured": "Configured",
|
||||
"new": "Set up a new integration",
|
||||
"note_about_integrations": "Not all integrations can be configured via the UI yet.",
|
||||
"note_about_website_reference": "More are available on the ",
|
||||
"home_assistant_website": "Home Assistant website",
|
||||
"configure": "Configure",
|
||||
"none": "Nothing configured yet",
|
||||
"config_entry": {
|
||||
@ -982,7 +1192,8 @@
|
||||
"caption": "Users",
|
||||
"description": "Manage users",
|
||||
"picker": {
|
||||
"title": "Users"
|
||||
"title": "Users",
|
||||
"system_generated": "System generated"
|
||||
},
|
||||
"editor": {
|
||||
"caption": "View user",
|
||||
@ -990,7 +1201,18 @@
|
||||
"change_password": "Change password",
|
||||
"activate_user": "Activate user",
|
||||
"deactivate_user": "Deactivate user",
|
||||
"delete_user": "Delete user"
|
||||
"delete_user": "Delete user",
|
||||
"id": "ID",
|
||||
"owner": "Owner",
|
||||
"group": "Group",
|
||||
"active": "Active",
|
||||
"system_generated": "System generated",
|
||||
"system_generated_users_not_removable": "Unable to remove system generated users.",
|
||||
"unnamed_user": "Unnamed User",
|
||||
"enter_new_name": "Enter new name",
|
||||
"user_rename_failed": "User rename failed:",
|
||||
"group_update_failed": "Group update failed:",
|
||||
"confirm_user_deletion": "Are you sure you want to delete {name}?"
|
||||
},
|
||||
"add_user": {
|
||||
"caption": "Add user",
|
||||
@ -1003,15 +1225,55 @@
|
||||
"zha": {
|
||||
"caption": "ZHA",
|
||||
"description": "Zigbee Home Automation network management",
|
||||
"common": {
|
||||
"add_devices": "Add Devices",
|
||||
"clusters": "Clusters",
|
||||
"devices": "Devices",
|
||||
"manufacturer_code_override": "Manufacturer Code Override",
|
||||
"value": "Value"
|
||||
},
|
||||
"add_device_page": {
|
||||
"header": "Zigbee Home Automation - Add Devices",
|
||||
"spinner": "Searching for ZHA Zigbee devices...",
|
||||
"discovery_text": "Discovered devices will show up here. Follow the instructions for your device(s) and place the device(s) in pairing mode."
|
||||
"discovery_text": "Discovered devices will show up here. Follow the instructions for your device(s) and place the device(s) in pairing mode.",
|
||||
"search_again": "Search Again"
|
||||
},
|
||||
"network_management": {
|
||||
"header": "Network Management",
|
||||
"introduction": "Commands that affect the entire network"
|
||||
},
|
||||
"node_management": {
|
||||
"header": "Device Management",
|
||||
"introduction": "Run ZHA commands that affect a single device. Pick a device to see a list of available commands.",
|
||||
"hint_battery_devices": "Note: Sleepy (battery powered) devices need to be awake when executing commands against them. You can generally wake a sleepy device by triggering it.",
|
||||
"hint_wakeup": "Some devices such as Xiaomi sensors have a wake up button that you can press at ~5 second intervals that keep devices awake while you interact with them.",
|
||||
"help_node_dropdown": "Select a device to view per-device options."
|
||||
},
|
||||
"clusters": {
|
||||
"help_cluster_dropdown": "Select a cluster to view attributes and commands."
|
||||
},
|
||||
"cluster_attributes": {
|
||||
"header": "Cluster Attributes",
|
||||
"introduction": "View and edit cluster attributes.",
|
||||
"attributes_of_cluster": "Attributes of the selected cluster",
|
||||
"get_zigbee_attribute": "Get Zigbee Attribute",
|
||||
"set_zigbee_attribute": "Set Zigbee Attribute",
|
||||
"help_attribute_dropdown": "Select an attribute to view or set its value.",
|
||||
"help_get_zigbee_attribute": "Get the value for the selected attribute.",
|
||||
"help_set_zigbee_attribute": "Set attribute value for the specified cluster on the specified entity."
|
||||
},
|
||||
"cluster_commands": {
|
||||
"header": "Cluster Commands",
|
||||
"introduction": "View and issue cluster commands.",
|
||||
"commands_of_cluster": "Commands of the selected cluster",
|
||||
"issue_zigbee_command": "Issue Zigbee Command",
|
||||
"help_command_dropdown": "Select a command to interact with."
|
||||
}
|
||||
},
|
||||
"zwave": {
|
||||
"caption": "Z-Wave",
|
||||
"description": "Manage your Z-Wave network",
|
||||
"learn_more": "Learn more about Z-Wave",
|
||||
"common": {
|
||||
"value": "Value",
|
||||
"instance": "Instance",
|
||||
@ -1023,6 +1285,10 @@
|
||||
"header": "Z-Wave Network Management",
|
||||
"introduction": "Run commands that affect the Z-Wave network. You won't get feedback on whether most commands succeeded, but you can check the OZW Log to try to find out."
|
||||
},
|
||||
"ozw_log": {
|
||||
"header": "OZW Log",
|
||||
"introduction": "View the log. 0 is the minimum (loads entire log) and 1000 is the maximum. Load will show a static log and tail will auto update with the last specified number of lines of the log."
|
||||
},
|
||||
"network_status": {
|
||||
"network_stopped": "Z-Wave Network Stopped",
|
||||
"network_starting": "Starting Z-Wave Network...",
|
||||
@ -1119,6 +1385,8 @@
|
||||
"header": "Card Configuration",
|
||||
"pick_card": "Pick the card you want to add.",
|
||||
"toggle_editor": "Toggle Editor",
|
||||
"show_visual_editor": "Show Visual Editor",
|
||||
"show_code_editor": "Show Code Editor",
|
||||
"add": "Add Card",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
@ -1138,17 +1406,29 @@
|
||||
"migrate": "Migrate config"
|
||||
},
|
||||
"card": {
|
||||
"alarm_panel": {
|
||||
"alarm-panel": {
|
||||
"name": "Alarm Panel",
|
||||
"available_states": "Available States"
|
||||
},
|
||||
"conditional": {
|
||||
"name": "Conditional"
|
||||
},
|
||||
"config": {
|
||||
"required": "Required",
|
||||
"optional": "Optional"
|
||||
},
|
||||
"entities": {
|
||||
"name": "Entities",
|
||||
"show_header_toggle": "Show Header Toggle?"
|
||||
},
|
||||
"entity-button": {
|
||||
"name": "Entity Button"
|
||||
},
|
||||
"entity-filter": {
|
||||
"name": "Entity Filter"
|
||||
},
|
||||
"gauge": {
|
||||
"name": "Gauge",
|
||||
"severity": {
|
||||
"define": "Define Severity?",
|
||||
"green": "Green",
|
||||
@ -1157,8 +1437,21 @@
|
||||
}
|
||||
},
|
||||
"glance": {
|
||||
"name": "Glance",
|
||||
"columns": "Columns"
|
||||
},
|
||||
"history-graph": {
|
||||
"name": "History Graph"
|
||||
},
|
||||
"horizontal-stack": {
|
||||
"name": "Horizontal Stack"
|
||||
},
|
||||
"iframe": {
|
||||
"name": "iFrame"
|
||||
},
|
||||
"light": {
|
||||
"name": "Light"
|
||||
},
|
||||
"generic": {
|
||||
"aspect_ratio": "Aspect Ratio",
|
||||
"camera_image": "Camera Entity",
|
||||
@ -1184,17 +1477,50 @@
|
||||
"url": "Url"
|
||||
},
|
||||
"map": {
|
||||
"name": "Map",
|
||||
"geo_location_sources": "Geolocation Sources",
|
||||
"dark_mode": "Dark Mode?",
|
||||
"default_zoom": "Default Zoom",
|
||||
"source": "Source"
|
||||
},
|
||||
"markdown": {
|
||||
"name": "Markdown",
|
||||
"content": "Content"
|
||||
},
|
||||
"media-control": {
|
||||
"name": "Media Control"
|
||||
},
|
||||
"picture": {
|
||||
"name": "Picture"
|
||||
},
|
||||
"picture-elements": {
|
||||
"name": "Picture Elements"
|
||||
},
|
||||
"picture-entity": {
|
||||
"name": "Picture Entity"
|
||||
},
|
||||
"picture-glance": {
|
||||
"name": "Picture Glance"
|
||||
},
|
||||
"plant-status": {
|
||||
"name": "Plant Status"
|
||||
},
|
||||
"sensor": {
|
||||
"name": "Sensor",
|
||||
"graph_detail": "Graph Detail",
|
||||
"graph_type": "Graph Type"
|
||||
},
|
||||
"shopping-list": {
|
||||
"name": "Shopping List"
|
||||
},
|
||||
"thermostat": {
|
||||
"name": "Thermostat"
|
||||
},
|
||||
"vertical-stack": {
|
||||
"name": "Vertical Stack"
|
||||
},
|
||||
"weather-forecast": {
|
||||
"name": "Weather Forecast"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -1266,6 +1592,10 @@
|
||||
"close": "Close",
|
||||
"submit": "Submit"
|
||||
},
|
||||
"advanced_mode": {
|
||||
"title": "Advanced Mode",
|
||||
"description": "Home Assistant hides advanced features and options by default. You can make these features accessible by checking this toggle. This is a user-specific setting and does not impact other users using Home Assistant."
|
||||
},
|
||||
"refresh_tokens": {
|
||||
"header": "Refresh Tokens",
|
||||
"description": "Each refresh token represents a login session. Refresh tokens will be automatically removed when you click log out. The following refresh tokens are currently active for your account.",
|
||||
|
@ -46,7 +46,8 @@
|
||||
"nativeName": "Euskara"
|
||||
},
|
||||
"fa": {
|
||||
"nativeName": "فارسی"
|
||||
"nativeName": "فارسی",
|
||||
"isRTL": true
|
||||
},
|
||||
"fi": {
|
||||
"nativeName": "Suomi"
|
||||
|
@ -504,7 +504,12 @@
|
||||
"leave": "Sortir"
|
||||
},
|
||||
"device": {
|
||||
"label": "Dispositiu"
|
||||
"label": "Dispositiu",
|
||||
"extra_fields": {
|
||||
"above": "A sobre",
|
||||
"below": "A sota",
|
||||
"for": "Durada"
|
||||
}
|
||||
}
|
||||
},
|
||||
"learn_more": "Més informació sobre els activadors"
|
||||
@ -1065,10 +1070,10 @@
|
||||
},
|
||||
"editor": {
|
||||
"edit_card": {
|
||||
"header": "Targeta de Configuració",
|
||||
"header": "Configuració de la targeta",
|
||||
"save": "Desa",
|
||||
"toggle_editor": "Commutar l'editor",
|
||||
"pick_card": "Tria la targeta que vols afegir.",
|
||||
"pick_card": "Tria una targeta que vulguis afegir.",
|
||||
"add": "Afegir targeta",
|
||||
"edit": "Editar",
|
||||
"delete": "Elimina",
|
||||
|
@ -160,7 +160,7 @@
|
||||
"not_home": "Ude"
|
||||
},
|
||||
"fan": {
|
||||
"off": "Slukket",
|
||||
"off": "Fra",
|
||||
"on": "Tændt"
|
||||
},
|
||||
"group": {
|
||||
@ -504,7 +504,12 @@
|
||||
"leave": "Forlad"
|
||||
},
|
||||
"device": {
|
||||
"label": "Enhed"
|
||||
"label": "Enhed",
|
||||
"extra_fields": {
|
||||
"above": "Over",
|
||||
"below": "Under",
|
||||
"for": "Varighed"
|
||||
}
|
||||
}
|
||||
},
|
||||
"learn_more": "Lær om udløsere"
|
||||
@ -775,7 +780,8 @@
|
||||
"enabled_label": "Aktivér enhed",
|
||||
"enabled_cause": "Deaktiveret af {cause}.",
|
||||
"enabled_description": "Deaktiverede enheder tilføjes ikke til Home Assistant.",
|
||||
"confirm_delete": "Er du sikker på, at du vil slette denne enhed?"
|
||||
"confirm_delete": "Er du sikker på, at du vil slette denne indtastning?",
|
||||
"confirm_delete2": "Hvis du sletter en post, fjernes enheden ikke fra hjemme assistenten. Hvis du vil gøre dette, skal du fjerne integrationen ' {platform} ' fra Home Assistant."
|
||||
}
|
||||
},
|
||||
"person": {
|
||||
@ -829,7 +835,7 @@
|
||||
"caption": "Gør kun noget, hvis ..."
|
||||
},
|
||||
"actions": {
|
||||
"caption": "Når noget udløses ..."
|
||||
"caption": "Når noget påvirkes ..."
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -909,7 +915,7 @@
|
||||
"description": "Dette vil skjule sidepanelet som standard, svarende til den mobile oplevelse."
|
||||
},
|
||||
"vibrate": {
|
||||
"header": "Vibrere",
|
||||
"header": "Vibrer",
|
||||
"description": "Aktivér eller deaktiver vibrationer på denne enhed, når du styrer enheder."
|
||||
}
|
||||
},
|
||||
@ -1058,7 +1064,8 @@
|
||||
"navigate_to": "Naviger til {location}",
|
||||
"toggle": "Skift {name}",
|
||||
"call_service": "Kald service {name}",
|
||||
"more_info": "Vis mere-info: {name}"
|
||||
"more_info": "Vis mere-info: {name}",
|
||||
"url": "Åbn vindue til {url_path}"
|
||||
}
|
||||
},
|
||||
"editor": {
|
||||
@ -1113,6 +1120,9 @@
|
||||
"required": "Påkrævet",
|
||||
"optional": "Valgfri"
|
||||
},
|
||||
"entities": {
|
||||
"show_header_toggle": "Vis omskifter ved overskrift?"
|
||||
},
|
||||
"gauge": {
|
||||
"severity": {
|
||||
"define": "Definer alvorlighed?",
|
||||
@ -1131,6 +1141,7 @@
|
||||
"entities": "Enheder",
|
||||
"entity": "Enhed",
|
||||
"hold_action": "Hold handling",
|
||||
"hours_to_show": "Vis i antal timer",
|
||||
"icon": "Ikon",
|
||||
"icon_height": "Ikonhøjde",
|
||||
"image": "Sti til billede",
|
||||
@ -1432,12 +1443,12 @@
|
||||
"manuf": "af {manufacturer}",
|
||||
"no_area": "Intet område",
|
||||
"services": {
|
||||
"reconfigure": "Genkonfigurer ZHA-enhed (helbred enhed). Brug dette hvis du har problemer med enheden. Hvis den pågældende enhed er en batteridrevet enhed skal du sørge for at den er vågen og accepterer kommandoer når du bruger denne service.",
|
||||
"reconfigure": "Rekonfigurer ZHA-enhed (helbred enhed). Brug dette hvis du har problemer med enheden. Hvis den pågældende enhed er en batteridrevet enhed skal du sørge for at den er vågen og accepterer kommandoer når du bruger denne service.",
|
||||
"updateDeviceName": "Angiv et brugerdefineret navn til denne enhed i enhedsopsætningen",
|
||||
"remove": "Fjern en enhed fra Zigbee-netværket."
|
||||
},
|
||||
"zha_device_card": {
|
||||
"device_name_placeholder": "Navn",
|
||||
"device_name_placeholder": "Bruger tildelt navn",
|
||||
"area_picker_label": "Område",
|
||||
"update_name_button": "Opdater navn"
|
||||
}
|
||||
|
@ -504,7 +504,12 @@
|
||||
"leave": "Verlassen"
|
||||
},
|
||||
"device": {
|
||||
"label": "Gerät"
|
||||
"label": "Gerät",
|
||||
"extra_fields": {
|
||||
"above": "Über",
|
||||
"below": "Unter",
|
||||
"for": "Dauer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"learn_more": "Erfahre mehr über Auslöser"
|
||||
@ -554,6 +559,12 @@
|
||||
},
|
||||
"device": {
|
||||
"label": "Gerät"
|
||||
},
|
||||
"and": {
|
||||
"label": "Und"
|
||||
},
|
||||
"or": {
|
||||
"label": "Oder"
|
||||
}
|
||||
},
|
||||
"learn_more": "Erfahre mehr über Bedingungen"
|
||||
@ -596,7 +607,11 @@
|
||||
"learn_more": "Erfahre mehr über Aktionen"
|
||||
},
|
||||
"load_error_not_editable": "Nur Automatisierungen in automations.yaml sind editierbar.",
|
||||
"load_error_unknown": "Fehler beim Laden der Automatisierung ({err_no})."
|
||||
"load_error_unknown": "Fehler beim Laden der Automatisierung ({err_no}).",
|
||||
"description": {
|
||||
"label": "Beschreibung",
|
||||
"placeholder": "Optionale Beschreibung"
|
||||
}
|
||||
}
|
||||
},
|
||||
"script": {
|
||||
@ -754,7 +769,8 @@
|
||||
"unavailable": "(nicht verfügbar)",
|
||||
"introduction": "Der Home Assistant führt eine Registrierung aller Entitäten, die er je gesehen hat und die eindeutig identifiziert werden können. Jeder dieser Entitäten wird eine Entitäts-ID zugewiesen, die nur für diese Entität reserviert ist.",
|
||||
"introduction2": "Verwende die Entitätsregistrierung, um den Namen zu überschreiben, die Entität-ID zu ändern oder den Eintrag aus dem Home Assistant zu entfernen. Beachte, dass das Entfernen des Entitätsregistrierungseintrags die Entität nicht löscht. Folge dazu dem Link unten und entferne ihn in der Integrationsseite.",
|
||||
"integrations_page": "Integrationsseite"
|
||||
"integrations_page": "Integrationsseite",
|
||||
"show_disabled": "Anzeigen deaktivierter Entitäten"
|
||||
},
|
||||
"editor": {
|
||||
"unavailable": "Diese Entität ist derzeit nicht verfügbar.",
|
||||
@ -763,7 +779,9 @@
|
||||
"update": "UPDATE",
|
||||
"enabled_label": "Entität aktivieren",
|
||||
"enabled_cause": "Deaktiviert durch {cause}.",
|
||||
"enabled_description": "Deaktivierte Entitäten werden in Home Assistant nicht hinzugefügt."
|
||||
"enabled_description": "Deaktivierte Entitäten werden in Home Assistant nicht hinzugefügt.",
|
||||
"confirm_delete": "Möchten Sie diesen Eintrag wirklich löschen?",
|
||||
"confirm_delete2": "Durch das Löschen eines Eintrags wird die Entität nicht aus Home Assistant entfernt. Dazu müssen Sie die Integration '{platform}' von Home Assistant entfernen."
|
||||
}
|
||||
},
|
||||
"person": {
|
||||
@ -808,7 +826,18 @@
|
||||
},
|
||||
"devices": {
|
||||
"caption": "Geräte",
|
||||
"description": "Verwalte verbundene Geräte"
|
||||
"description": "Verwalte verbundene Geräte",
|
||||
"automation": {
|
||||
"triggers": {
|
||||
"caption": "Tu nur etwas, wenn..."
|
||||
},
|
||||
"conditions": {
|
||||
"caption": "Tu nur etwas, wenn..."
|
||||
},
|
||||
"actions": {
|
||||
"caption": "Wenn etwas ausgelöst wird ..."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
@ -886,7 +915,8 @@
|
||||
"description": "Dies blendet die Seitenleiste standardmäßig aus, ähnlich der Nutzung auf Mobilgeräten."
|
||||
},
|
||||
"vibrate": {
|
||||
"header": "Vibrieren"
|
||||
"header": "Vibrieren",
|
||||
"description": "Aktivieren oder deaktivieren Sie die Vibration an diesem Gerät, wenn Sie Geräte steuern."
|
||||
}
|
||||
},
|
||||
"page-authorize": {
|
||||
@ -1034,7 +1064,8 @@
|
||||
"navigate_to": "Navigiere zu {location}",
|
||||
"toggle": "{name} umschalten",
|
||||
"call_service": "Dienst {name} ausführen",
|
||||
"more_info": "Zeige weitere Informationen: {name}"
|
||||
"more_info": "Zeige weitere Informationen: {name}",
|
||||
"url": "Fenster zu {url_path} öffnen"
|
||||
}
|
||||
},
|
||||
"editor": {
|
||||
@ -1080,6 +1111,66 @@
|
||||
"edit_lovelace": {
|
||||
"header": "Titel deiner Lovelace UI",
|
||||
"explanation": "Dieser Titel wird überhalb aller deiner Lovelace Ansichten gezeigt."
|
||||
},
|
||||
"card": {
|
||||
"alarm_panel": {
|
||||
"available_states": "Verfügbare Zustände"
|
||||
},
|
||||
"config": {
|
||||
"required": "Benötigt",
|
||||
"optional": "Optional"
|
||||
},
|
||||
"entities": {
|
||||
"show_header_toggle": "Namen anzeigen?"
|
||||
},
|
||||
"gauge": {
|
||||
"severity": {
|
||||
"define": "Schweregrad definieren?",
|
||||
"green": "Grün",
|
||||
"red": "Rot",
|
||||
"yellow": "Gelb"
|
||||
}
|
||||
},
|
||||
"glance": {
|
||||
"columns": "Spalten"
|
||||
},
|
||||
"generic": {
|
||||
"aspect_ratio": "Seitenverhältnis",
|
||||
"camera_image": "Kamera-Entität",
|
||||
"camera_view": "Kameraansicht",
|
||||
"entities": "Ungenutzte Elemente",
|
||||
"entity": "Entität",
|
||||
"hold_action": "Halte-Aktion",
|
||||
"hours_to_show": "Stunden",
|
||||
"icon": "Symbol",
|
||||
"icon_height": "Symbol Höhe",
|
||||
"image": "Bildpfad",
|
||||
"maximum": "Maximum",
|
||||
"minimum": "Minimum",
|
||||
"name": "Name",
|
||||
"refresh_interval": "Aktualisierungsintervall",
|
||||
"show_icon": "Symbol anzeigen?",
|
||||
"show_name": "Namen anzeigen?",
|
||||
"show_state": "Status anzeigen?",
|
||||
"tap_action": "Tipp-Aktion",
|
||||
"title": "Titel",
|
||||
"theme": "Aussehen",
|
||||
"unit": "Einheit",
|
||||
"url": "Url"
|
||||
},
|
||||
"map": {
|
||||
"geo_location_sources": "Geolocation-Quellen",
|
||||
"dark_mode": "Dunkler Modus?",
|
||||
"default_zoom": "Standard-Zoom",
|
||||
"source": "Quelle"
|
||||
},
|
||||
"markdown": {
|
||||
"content": "Inhalt"
|
||||
},
|
||||
"sensor": {
|
||||
"graph_detail": "Diagrammdetail",
|
||||
"graph_type": "Typ"
|
||||
}
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
@ -1352,6 +1443,7 @@
|
||||
"manuf": "von {manufacturer}",
|
||||
"no_area": "Kein Bereich",
|
||||
"services": {
|
||||
"reconfigure": "Konfigurieren Sie das ZHA-Gerät neu (Gerät heilen). Verwenden Sie diese Option, wenn Sie Probleme mit dem Gerät haben. Wenn es sich bei dem fraglichen Gerät um ein batteriebetriebenes Gerät handelt, vergewissern Sie sich, dass es wach ist und Befehle akzeptiert, wenn Sie diesen Dienst nutzen.",
|
||||
"updateDeviceName": "Lege einen benutzerdefinierten Namen für dieses Gerät in der Geräteregistrierung fest.",
|
||||
"remove": "Entfernen Sie ein Gerät aus dem ZigBee-Netzwerk."
|
||||
},
|
||||
@ -1437,7 +1529,7 @@
|
||||
"away": "Abwesend",
|
||||
"boost": "Maximal",
|
||||
"comfort": "Komfort",
|
||||
"home": "Zu Hause",
|
||||
"home": "Zuhause",
|
||||
"sleep": "Schlafen",
|
||||
"activity": "Aktivität"
|
||||
},
|
||||
|
@ -763,7 +763,8 @@
|
||||
"update": "ΕΝΗΜΕΡΩΣΗ",
|
||||
"enabled_label": "Ενεργοποίηση οντότητας",
|
||||
"enabled_cause": "Απενεργοποιήθηκε από τo {cause}.",
|
||||
"enabled_description": "Απενεργοποιημένες οντότητες δεν θα προστεθούν στον Home Assistant"
|
||||
"enabled_description": "Απενεργοποιημένες οντότητες δεν θα προστεθούν στον Home Assistant",
|
||||
"confirm_delete2": "Η διαγραφή μιας καταχώρησης δεν θα καταργήσει την οντότητα από το Home Assistant. Για να γίνει αυτό, θα πρέπει να καταργήσετε την ενσωμάτωση '{platform}' από το Home Assistant."
|
||||
}
|
||||
},
|
||||
"person": {
|
||||
|
@ -504,7 +504,12 @@
|
||||
"leave": "Leave"
|
||||
},
|
||||
"device": {
|
||||
"label": "Device"
|
||||
"label": "Device",
|
||||
"extra_fields": {
|
||||
"above": "Above",
|
||||
"below": "Below",
|
||||
"for": "Duration"
|
||||
}
|
||||
}
|
||||
},
|
||||
"learn_more": "Learn more about triggers"
|
||||
|
@ -160,7 +160,7 @@
|
||||
"not_home": "Fuera de Casa"
|
||||
},
|
||||
"fan": {
|
||||
"off": "Apagado",
|
||||
"off": "Desactivado",
|
||||
"on": "Encendido"
|
||||
},
|
||||
"group": {
|
||||
|
@ -504,7 +504,12 @@
|
||||
"leave": "Salir"
|
||||
},
|
||||
"device": {
|
||||
"label": "Dispositivo"
|
||||
"label": "Dispositivo",
|
||||
"extra_fields": {
|
||||
"above": "Por encima de",
|
||||
"below": "Por debajo de",
|
||||
"for": "Duración"
|
||||
}
|
||||
}
|
||||
},
|
||||
"learn_more": "Aprende más sobre los desencadenantes"
|
||||
|
@ -691,7 +691,10 @@
|
||||
},
|
||||
"profile": {
|
||||
"push_notifications": {
|
||||
"description": "ارسال اعلان ها به این دستگاه"
|
||||
"header": "ارسال اعلانها",
|
||||
"description": "ارسال اعلانها به این دستگاه",
|
||||
"push_notifications": "ارسال اعلانها",
|
||||
"link_promo": "بیشتر بدانید"
|
||||
},
|
||||
"refresh_tokens": {
|
||||
"header": "تازه کردن نشانه",
|
||||
@ -713,7 +716,10 @@
|
||||
},
|
||||
"current_user": "شما در حال حاضر به عنوان {fullName} وارد شده اید.",
|
||||
"change_password": {
|
||||
"header": "تغییر رمز عبور"
|
||||
"header": "تغییر رمز عبور",
|
||||
"current_password": "رمز فعلی",
|
||||
"new_password": "رمز جدید",
|
||||
"confirm_new_password": "تائید رمز جدید"
|
||||
},
|
||||
"mfa": {
|
||||
"confirm_disable": "آیا مطمئن هستید که میخواهید {name} غیرفعال کنید؟"
|
||||
@ -813,7 +819,7 @@
|
||||
},
|
||||
"core-config": {
|
||||
"intro": "سلام {name} ، به دستیار خانگی خوش آمدید چگونه می خواهید خانه خود را نام ببرید؟",
|
||||
"intro_location": "ما می خواهیم بدانیم کجا زندگی می کنیم این اطلاعات برای نمایش اطلاعات و تنظیم خودکار اتوماسیون مبتنی بر آفتاب کمک خواهد کرد. این اطلاعات در خارج از شبکه شما به اشتراک گذاشته نمیشود .",
|
||||
"intro_location": "ما می خواهیم بدانیم کجا زندگی می کنید این اطلاعات برای نمایش اطلاعات و تنظیم خودکار اتوماسیون مبتنی بر آخورشید کمک خواهد کرد. این اطلاعات در خارج از شبکه شما به اشتراک گذاشته نمیشود .",
|
||||
"intro_location_detect": "ما می تواند کمک به شما در پر کردن این اطلاعات با ساخت یک درخواست به یک سرویس خارجی.",
|
||||
"location_name_default": "خانه",
|
||||
"button_detect": "تشخیص",
|
||||
@ -1078,7 +1084,9 @@
|
||||
"confirm": "ذخیره ورود به سیستم"
|
||||
},
|
||||
"notification_drawer": {
|
||||
"click_to_configure": "برای پیکربندی {entity} روی دکمه کلیک کنید"
|
||||
"click_to_configure": "برای پیکربندی {entity} روی دکمه کلیک کنید",
|
||||
"empty": "بدون اعلان",
|
||||
"title": "اعلانها"
|
||||
},
|
||||
"common": {
|
||||
"save": "ذخیره"
|
||||
|
@ -1001,7 +1001,7 @@
|
||||
},
|
||||
"core-config": {
|
||||
"intro": "Hei {name}, tervetuloa Home Assistant -käyttäjäksi. Kuinka haluaisit nimetä uuden kotisi?",
|
||||
"intro_location": "Haluaisimme tietää, missä asut. Nämä tiedot auttavat näyttämään tietoja ja perustamaan aurinkopohjaisia automaatioita. Nämä tiedot eivät koskaan jaeta oman verkkosi ulkopuolella.",
|
||||
"intro_location": "Haluaisimme tietää, missä asut. Nämä tiedot auttavat näyttämään tietoja ja perustamaan aurinkoon perustuvia automaatioita. Näitä tietoja ei koskaan jaeta oman verkkosi ulkopuolelle.",
|
||||
"intro_location_detect": "Voimme auttaa sinua täyttämään nämä tiedot tekemällä kertaluonteisen pyynnön ulkoiselle palvelulle.",
|
||||
"location_name_default": "Koti",
|
||||
"button_detect": "Havaitse",
|
||||
|
@ -33,12 +33,12 @@
|
||||
},
|
||||
"automation": {
|
||||
"off": "Off",
|
||||
"on": "On"
|
||||
"on": "[%key_id:state::default::on%]"
|
||||
},
|
||||
"binary_sensor": {
|
||||
"default": {
|
||||
"off": "Off",
|
||||
"on": "On"
|
||||
"on": "[%key_id:state::default::on%]"
|
||||
},
|
||||
"moisture": {
|
||||
"off": "Sec",
|
||||
@ -118,8 +118,8 @@
|
||||
}
|
||||
},
|
||||
"calendar": {
|
||||
"off": "Off",
|
||||
"on": "On"
|
||||
"off": "[%key_id:state::default::off%]",
|
||||
"on": "[%key_id:state::default::on%]"
|
||||
},
|
||||
"camera": {
|
||||
"recording": "Enregistrement",
|
||||
@ -165,7 +165,7 @@
|
||||
},
|
||||
"group": {
|
||||
"off": "Off",
|
||||
"on": "On",
|
||||
"on": "[%key_id:state::default::on%]",
|
||||
"home": "Présent",
|
||||
"not_home": "Absent",
|
||||
"open": "Ouvert",
|
||||
@ -210,19 +210,19 @@
|
||||
"scening": "Scénario"
|
||||
},
|
||||
"script": {
|
||||
"off": "Off",
|
||||
"on": "On"
|
||||
"off": "[%key_id:state::default::off%]",
|
||||
"on": "[%key_id:state::default::on%]"
|
||||
},
|
||||
"sensor": {
|
||||
"off": "Off",
|
||||
"on": "On"
|
||||
"off": "[%key_id:state::default::off%]",
|
||||
"on": "[%key_id:state::default::on%]"
|
||||
},
|
||||
"sun": {
|
||||
"above_horizon": "Au-dessus de l'horizon",
|
||||
"below_horizon": "Sous l’horizon"
|
||||
},
|
||||
"switch": {
|
||||
"off": "Off",
|
||||
"off": "[%key_id:state::default::off%]",
|
||||
"on": "On"
|
||||
},
|
||||
"zwave": {
|
||||
@ -284,7 +284,7 @@
|
||||
"alarm_control_panel": {
|
||||
"armed": "Activé",
|
||||
"disarmed": "Désactivé",
|
||||
"armed_home": "Armé",
|
||||
"armed_home": "Activé",
|
||||
"armed_away": "Activé",
|
||||
"armed_night": "Activé",
|
||||
"pending": "En cours",
|
||||
@ -294,7 +294,7 @@
|
||||
"armed_custom_bypass": "Activé"
|
||||
},
|
||||
"device_tracker": {
|
||||
"home": "Maison",
|
||||
"home": "Présent",
|
||||
"not_home": "Absent"
|
||||
},
|
||||
"person": {
|
||||
@ -504,7 +504,12 @@
|
||||
"leave": "Quitte"
|
||||
},
|
||||
"device": {
|
||||
"label": "Équipements"
|
||||
"label": "Équipements",
|
||||
"extra_fields": {
|
||||
"above": "Au-dessus de",
|
||||
"below": "Au-dessous de",
|
||||
"for": "Durée"
|
||||
}
|
||||
}
|
||||
},
|
||||
"learn_more": "En savoir plus sur les déclencheurs"
|
||||
@ -1524,7 +1529,7 @@
|
||||
"away": "Absent",
|
||||
"boost": "Renforcer",
|
||||
"comfort": "Confort",
|
||||
"home": "Accueil",
|
||||
"home": "Présent",
|
||||
"sleep": "Veille",
|
||||
"activity": "Activité"
|
||||
},
|
||||
|
@ -78,6 +78,7 @@
|
||||
"home": "घर"
|
||||
},
|
||||
"fan": {
|
||||
"off": "बंद",
|
||||
"on": "चालू"
|
||||
},
|
||||
"group": {
|
||||
|
@ -504,7 +504,12 @@
|
||||
"leave": "Távozás"
|
||||
},
|
||||
"device": {
|
||||
"label": "Eszköz"
|
||||
"label": "Eszköz",
|
||||
"extra_fields": {
|
||||
"above": "Felett",
|
||||
"below": "Alatt",
|
||||
"for": "Időtartam"
|
||||
}
|
||||
}
|
||||
},
|
||||
"learn_more": "Tudj meg többet a triggerekről"
|
||||
|
@ -454,7 +454,11 @@
|
||||
"leave": "Brottför"
|
||||
},
|
||||
"device": {
|
||||
"label": "Tæki"
|
||||
"label": "Tæki",
|
||||
"extra_fields": {
|
||||
"above": "Yfir",
|
||||
"below": "Undir"
|
||||
}
|
||||
}
|
||||
},
|
||||
"learn_more": "Læra meira um kveikjur"
|
||||
|
@ -504,7 +504,12 @@
|
||||
"leave": "Uscita"
|
||||
},
|
||||
"device": {
|
||||
"label": "Dispositivo"
|
||||
"label": "Dispositivo",
|
||||
"extra_fields": {
|
||||
"above": "Sopra",
|
||||
"below": "Sotto",
|
||||
"for": "Durata"
|
||||
}
|
||||
}
|
||||
},
|
||||
"learn_more": "Per saperne di più sui trigger"
|
||||
|
@ -118,6 +118,7 @@
|
||||
"off": "オフ",
|
||||
"on": "オン",
|
||||
"home": "在宅",
|
||||
"not_home": "外出",
|
||||
"ok": "OK"
|
||||
},
|
||||
"input_boolean": {
|
||||
|
@ -504,7 +504,12 @@
|
||||
"leave": "퇴장"
|
||||
},
|
||||
"device": {
|
||||
"label": "기기"
|
||||
"label": "기기",
|
||||
"extra_fields": {
|
||||
"above": "이상",
|
||||
"below": "이하",
|
||||
"for": "동안"
|
||||
}
|
||||
}
|
||||
},
|
||||
"learn_more": "트리거에 대해 더 알아보기"
|
||||
@ -554,6 +559,12 @@
|
||||
},
|
||||
"device": {
|
||||
"label": "기기"
|
||||
},
|
||||
"and": {
|
||||
"label": "다중조건 (그리고)"
|
||||
},
|
||||
"or": {
|
||||
"label": "다중조건 (또는)"
|
||||
}
|
||||
},
|
||||
"learn_more": "조건에 대해 더 알아보기"
|
||||
@ -596,7 +607,11 @@
|
||||
"learn_more": "동작에 대해 더 알아보기"
|
||||
},
|
||||
"load_error_not_editable": "automations.yaml 의 자동화만 편집할 수 있습니다.",
|
||||
"load_error_unknown": "자동화를 읽어오는 도중 오류가 발생했습니다 ({err_no})."
|
||||
"load_error_unknown": "자동화를 읽어오는 도중 오류가 발생했습니다 ({err_no}).",
|
||||
"description": {
|
||||
"label": "설명",
|
||||
"placeholder": "부가설명"
|
||||
}
|
||||
}
|
||||
},
|
||||
"script": {
|
||||
@ -711,7 +726,7 @@
|
||||
"caption": "ZHA",
|
||||
"description": "Zigbee 홈 자동화 네트워크 관리",
|
||||
"services": {
|
||||
"reconfigure": "ZHA 기기를 다시 구성 합니다. (장치 복구). 기기에 문제가 있는 경우 사용해주세요. 기기가 배터리로 작동하는 경우, 이 서비스를 사용할 때 기기가 켜져있고 통신이 가능한 상태인지 확인해주세요.",
|
||||
"reconfigure": "ZHA 기기를 다시 구성 합니다. (기기 복구). 기기에 문제가 있는 경우 사용해주세요. 기기가 배터리로 작동하는 경우, 이 서비스를 사용할 때 기기가 켜져있고 통신이 가능한 상태인지 확인해주세요.",
|
||||
"updateDeviceName": "이 기기의 사용자 정의 이름을 기기 레지스트리에 설정합니다.",
|
||||
"remove": "Zigbee 네트워크에서 기기 제거"
|
||||
},
|
||||
@ -754,7 +769,8 @@
|
||||
"unavailable": "(사용불가)",
|
||||
"introduction": "Home Assistant 는 구성요소의 식별을 위해 모든 구성요소에 고유한 레지스트리를 부여합니다. 각각의 구성요소들은 자신만의 고유한 구성요소 ID 를 가집니다.",
|
||||
"introduction2": "구성요소를 편집하여 이름을 대체하거나 구성요소 ID를 변경하고 Home Assistant 에서 항목을 제거할 수 있습니다. 단, 구성요소 편집창에서 구성요소를 삭제해도 구성요소가 완전히 제거되는 것은 아닙니다. 완전히 제거하려면, 아래 링크를 따라 통합 구성요소 페이지에서 제거해주세요.",
|
||||
"integrations_page": "통합 구성요소 페이지"
|
||||
"integrations_page": "통합 구성요소 페이지",
|
||||
"show_disabled": "비활성화 된 구성요소 표시"
|
||||
},
|
||||
"editor": {
|
||||
"unavailable": "이 구성요소는 현재 사용할 수 없습니다.",
|
||||
@ -763,7 +779,9 @@
|
||||
"update": "업데이트",
|
||||
"enabled_label": "구성요소 활성화",
|
||||
"enabled_cause": "{cause} 에 의해 비활성화 되었습니다.",
|
||||
"enabled_description": "비활성화 된 구성요소는 Home Assistant 에 추가되지 않습니다."
|
||||
"enabled_description": "비활성화 된 구성요소는 Home Assistant 에 추가되지 않습니다.",
|
||||
"confirm_delete": "이 구성요소를 제거 하시겠습니까?",
|
||||
"confirm_delete2": "구성요소 항목을 제거해도 Home Assistant 에서 실제로 구성요소가 제거되는것은 아닙니다. 완전히 제거하려면, Home Assistant 에서 '{platform}' 통합 구성요소를 제거해주세요."
|
||||
}
|
||||
},
|
||||
"person": {
|
||||
@ -771,9 +789,9 @@
|
||||
"description": "Home Assistant 가 추적하는 구성원을 관리합니다",
|
||||
"detail": {
|
||||
"name": "이름",
|
||||
"device_tracker_intro": "이 구성원에게 속한 장치를 선택해주세요.",
|
||||
"device_tracker_picked": "장치 추적 대상",
|
||||
"device_tracker_pick": "추적 할 장치 선택"
|
||||
"device_tracker_intro": "이 구성원에게 속한 기기를 선택해주세요.",
|
||||
"device_tracker_picked": "추적 대상 기기",
|
||||
"device_tracker_pick": "추적 할 기기 선택"
|
||||
}
|
||||
},
|
||||
"server_control": {
|
||||
@ -808,7 +826,18 @@
|
||||
},
|
||||
"devices": {
|
||||
"caption": "기기",
|
||||
"description": "연결된 기기 관리"
|
||||
"description": "연결된 기기 관리",
|
||||
"automation": {
|
||||
"triggers": {
|
||||
"caption": "...일 때 뭔가를 실행"
|
||||
},
|
||||
"conditions": {
|
||||
"caption": "...인 경우 뭔가를 실행"
|
||||
},
|
||||
"actions": {
|
||||
"caption": "뭔가 트리거 되었을 때...."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
@ -884,6 +913,10 @@
|
||||
"force_narrow": {
|
||||
"header": "항상 사이드바 숨기기",
|
||||
"description": "모바일 환경과 마찬가지로 기본적으로 사이드 바가 숨겨집니다."
|
||||
},
|
||||
"vibrate": {
|
||||
"header": "진동효과",
|
||||
"description": "기기를 제어 할 때 이 기기에서 진동을 활성화 또는 비활성화합니다."
|
||||
}
|
||||
},
|
||||
"page-authorize": {
|
||||
@ -1031,7 +1064,8 @@
|
||||
"navigate_to": "{location} 로(으로) 이동",
|
||||
"toggle": "{name} 토글",
|
||||
"call_service": "{name} 서비스 호출",
|
||||
"more_info": "추가 정보 표시: {name}"
|
||||
"more_info": "추가 정보 표시: {name}",
|
||||
"url": "{url_path} 창 열기"
|
||||
}
|
||||
},
|
||||
"editor": {
|
||||
@ -1077,6 +1111,66 @@
|
||||
"edit_lovelace": {
|
||||
"header": "Lovelace UI 의 제목",
|
||||
"explanation": "이 제목은 Lovelace 의 모든 화면의 상단에 표시됩니다."
|
||||
},
|
||||
"card": {
|
||||
"alarm_panel": {
|
||||
"available_states": "사용 가능한 상태요소"
|
||||
},
|
||||
"config": {
|
||||
"required": "필수 요소",
|
||||
"optional": "선택사항"
|
||||
},
|
||||
"entities": {
|
||||
"show_header_toggle": "헤더 토글 표시"
|
||||
},
|
||||
"gauge": {
|
||||
"severity": {
|
||||
"define": "게이지 구간 정의하기",
|
||||
"green": "초록",
|
||||
"red": "빨강",
|
||||
"yellow": "노랑"
|
||||
}
|
||||
},
|
||||
"glance": {
|
||||
"columns": "열"
|
||||
},
|
||||
"generic": {
|
||||
"aspect_ratio": "종횡비",
|
||||
"camera_image": "카메라 구성요소",
|
||||
"camera_view": "카메라 뷰",
|
||||
"entities": "구성요소",
|
||||
"entity": "구성요소",
|
||||
"hold_action": "길게 누르기 동작",
|
||||
"hours_to_show": "표시 시간",
|
||||
"icon": "아이콘",
|
||||
"icon_height": "아이콘 높이",
|
||||
"image": "이미지 경로",
|
||||
"maximum": "최대",
|
||||
"minimum": "최소",
|
||||
"name": "이름",
|
||||
"refresh_interval": "새로 고침 간격",
|
||||
"show_icon": "아이콘 표시",
|
||||
"show_name": "이름 표시",
|
||||
"show_state": "상태 표시",
|
||||
"tap_action": "탭 동작",
|
||||
"title": "제목",
|
||||
"theme": "테마",
|
||||
"unit": "단위",
|
||||
"url": "Url"
|
||||
},
|
||||
"map": {
|
||||
"geo_location_sources": "위치정보 소스",
|
||||
"dark_mode": "어둡게 표시",
|
||||
"default_zoom": "기본 확대",
|
||||
"source": "소스"
|
||||
},
|
||||
"markdown": {
|
||||
"content": "내용"
|
||||
},
|
||||
"sensor": {
|
||||
"graph_detail": "그래프 세부묘사 정도",
|
||||
"graph_type": "그래프 유형"
|
||||
}
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
@ -1349,7 +1443,7 @@
|
||||
"manuf": "{manufacturer} 제조",
|
||||
"no_area": "영역 없음",
|
||||
"services": {
|
||||
"reconfigure": "ZHA 장치를 다시 구성 합니다. (장치 복구). 장치에 문제가 있는 경우 사용해주세요. 장치가 배터리로 작동하는 경우, 이 서비스를 사용할 때 장치가 켜져있고 통신이 가능한 상태여야 합니다.",
|
||||
"reconfigure": "ZHA 기기를 다시 구성 합니다. (기기 복구). 기기에 문제가 있는 경우 사용해주세요. 기기가 배터리로 작동하는 경우, 이 서비스를 사용할 때 기기가 켜져있고 통신이 가능한 상태인지 확인해주세요.",
|
||||
"updateDeviceName": "이 기기의 사용자 정의 이름을 기기 레지스트리에 설정합니다.",
|
||||
"remove": "Zigbee 네트워크에서 기기를 제거해주세요."
|
||||
},
|
||||
@ -1367,7 +1461,7 @@
|
||||
},
|
||||
"notification_drawer": {
|
||||
"click_to_configure": "버튼을 클릭하여 {entity} 을(를) 구성",
|
||||
"empty": "알림 없음",
|
||||
"empty": "알림 내용이 없습니다.",
|
||||
"title": "알림"
|
||||
}
|
||||
},
|
||||
@ -1381,7 +1475,7 @@
|
||||
"configurator": "구성",
|
||||
"conversation": "대화",
|
||||
"cover": "여닫이",
|
||||
"device_tracker": "추적 장치",
|
||||
"device_tracker": "추적 기기",
|
||||
"fan": "송풍기",
|
||||
"history_graph": "기록그래프",
|
||||
"group": "그룹",
|
||||
|
@ -504,7 +504,12 @@
|
||||
"leave": "Verloossen"
|
||||
},
|
||||
"device": {
|
||||
"label": "Apparat"
|
||||
"label": "Apparat",
|
||||
"extra_fields": {
|
||||
"above": "Iwwert",
|
||||
"below": "Ënnert",
|
||||
"for": "Dauer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"learn_more": "Méi iwwert Ausléiser liesen"
|
||||
|
@ -97,6 +97,10 @@
|
||||
"on": "Įjungta",
|
||||
"manual": "Rankinis"
|
||||
},
|
||||
"fan": {
|
||||
"off": "Išjungta",
|
||||
"on": "Įjungta"
|
||||
},
|
||||
"group": {
|
||||
"off": "Išjungta",
|
||||
"on": "Įjungta",
|
||||
|
@ -753,7 +753,7 @@
|
||||
"header": "Vienību Reģistrs",
|
||||
"unavailable": "(nav pieejams)",
|
||||
"introduction": "Home Assistant uztur katras vienības reģistru, kuru var unikāli identificēt. Katrai no šīm vienībām tiks piešķirts vienības ID, kas tiks rezervēts tikai šai vienībai.",
|
||||
"introduction2": "Izmantojiet vienību reģistru, lai ignorētu vārdu, mainītu vienību ID vai noņemtu ierakstu no Home Assistant. Ņemiet vērā: noņemot vienības reģistra ierakstu, vienība netiks noņemta. Lai to izdarītu, sekojiet zemāk esošajai saitei un noņemiet to no integrācijas lapas.",
|
||||
"introduction2": "Izmantojiet vienību reģistru, lai piešķirtu vārdu, mainītu vienību ID vai noņemtu ierakstu no Home Assistant. Ņemiet vērā: noņemot vienības reģistra ierakstu, vienība netiks noņemta. Lai to izdarītu, sekojiet zemāk esošajai saitei un noņemiet to no integrācijas lapas.",
|
||||
"integrations_page": "Integrāciju lapa"
|
||||
},
|
||||
"editor": {
|
||||
@ -1098,7 +1098,7 @@
|
||||
"page-demo": {
|
||||
"cards": {
|
||||
"demo": {
|
||||
"demo_by": "{name} ",
|
||||
"demo_by": "{name}",
|
||||
"next_demo": "Nākamā demonstrācija",
|
||||
"introduction": "Laipni lūgts mājās! Jūs esat sasniedzis Home Assistant demonstrāciju, kurā mēs parādām labākos lietotāja kopienas izveidotos lietotāja interfeisus.",
|
||||
"learn_more": "Uzziniet vairāk par Home Assistant"
|
||||
@ -1316,7 +1316,7 @@
|
||||
"dialogs": {
|
||||
"more_info_settings": {
|
||||
"save": "Saglabāt",
|
||||
"name": "Nosaukuma ignorēšana",
|
||||
"name": "Piesķirt nosaukumu",
|
||||
"entity_id": "Vienības ID"
|
||||
},
|
||||
"more_info_control": {
|
||||
|
@ -504,7 +504,12 @@
|
||||
"leave": "Forlater"
|
||||
},
|
||||
"device": {
|
||||
"label": "Enhet"
|
||||
"label": "Enhet",
|
||||
"extra_fields": {
|
||||
"above": "Over",
|
||||
"below": "Under",
|
||||
"for": "Varighet"
|
||||
}
|
||||
}
|
||||
},
|
||||
"learn_more": "Lær mer om utløsere"
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user