mirror of
https://github.com/home-assistant/frontend.git
synced 2026-06-04 07:21:42 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6b2754c4cc |
+5
-1
@@ -66,7 +66,11 @@
|
||||
"import/prefer-default-export": 0,
|
||||
"import/no-unresolved": 0,
|
||||
"import/no-cycle": 0,
|
||||
"import/extensions": 0,
|
||||
"import/extensions": [
|
||||
2,
|
||||
"ignorePackages",
|
||||
{ "ts": "never", "js": "never" }
|
||||
],
|
||||
"no-restricted-syntax": ["error", "LabeledStatement", "WithStatement"],
|
||||
"object-curly-newline": 0,
|
||||
"default-case": 0,
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import "~app/resources/ha-style";
|
||||
import "~app/resources/roboto";
|
||||
import "../../../src/resources/ha-style";
|
||||
import "../../../src/resources/roboto";
|
||||
import "./layout/hc-connect";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable no-undef */
|
||||
import { CAST_NS } from "~app/cast/const";
|
||||
import { HassMessage } from "~app/cast/receiver_messages";
|
||||
import "~app/resources/custom-card-support";
|
||||
import { CAST_NS } from "../../../src/cast/const";
|
||||
import { HassMessage } from "../../../src/cast/receiver_messages";
|
||||
import "../../../src/resources/custom-card-support";
|
||||
import { castContext } from "./cast_context";
|
||||
import { HcMain } from "./layout/hc-main";
|
||||
import { ReceivedMessage } from "./types";
|
||||
|
||||
@@ -192,8 +192,6 @@ export class HcMain extends HassElement {
|
||||
this._handleNewLovelaceConfig(lovelaceConfig)
|
||||
);
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
console.log("Error fetching Lovelace configuration", err, msg);
|
||||
// Generate a Lovelace config.
|
||||
this._unsubLovelace = () => undefined;
|
||||
await this._generateLovelaceConfig();
|
||||
|
||||
@@ -2,8 +2,8 @@ import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "~app/components/ha-switch";
|
||||
import "~app/components/ha-formfield";
|
||||
import "../../../src/components/ha-switch";
|
||||
import "../../../src/components/ha-formfield";
|
||||
import "./demo-card";
|
||||
|
||||
class DemoCards extends PolymerElement {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import "~app/resources/compatibility";
|
||||
import "~app/resources/roboto";
|
||||
import "../../src/resources/compatibility";
|
||||
import "../../src/resources/roboto";
|
||||
import "./hassio-main";
|
||||
|
||||
const styleEl = document.createElement("style");
|
||||
|
||||
@@ -14,9 +14,7 @@ import {
|
||||
createHassioSession,
|
||||
fetchHassioHomeAssistantInfo,
|
||||
fetchHassioSupervisorInfo,
|
||||
fetchHassioInfo,
|
||||
HassioHomeAssistantInfo,
|
||||
HassioInfo,
|
||||
HassioPanelInfo,
|
||||
HassioSupervisorInfo,
|
||||
} from "../../src/data/hassio/supervisor";
|
||||
@@ -77,8 +75,6 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
|
||||
|
||||
@property() private _hostInfo: HassioHostInfo;
|
||||
|
||||
@property() private _hassioInfo?: HassioInfo;
|
||||
|
||||
@property() private _hassOsInfo?: HassioHassOSInfo;
|
||||
|
||||
@property() private _hassInfo: HassioHomeAssistantInfo;
|
||||
@@ -151,7 +147,6 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
|
||||
hass: this.hass,
|
||||
narrow: this.narrow,
|
||||
supervisorInfo: this._supervisorInfo,
|
||||
hassioInfo: this._hassioInfo,
|
||||
hostInfo: this._hostInfo,
|
||||
hassInfo: this._hassInfo,
|
||||
hassOsInfo: this._hassOsInfo,
|
||||
@@ -161,7 +156,6 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
|
||||
el.hass = this.hass;
|
||||
el.narrow = this.narrow;
|
||||
el.supervisorInfo = this._supervisorInfo;
|
||||
el.hassioInfo = this._hassioInfo;
|
||||
el.hostInfo = this._hostInfo;
|
||||
el.hassInfo = this._hassInfo;
|
||||
el.hassOsInfo = this._hassOsInfo;
|
||||
@@ -175,14 +169,12 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [supervisorInfo, hostInfo, hassInfo, hassioInfo] = await Promise.all([
|
||||
const [supervisorInfo, hostInfo, hassInfo] = await Promise.all([
|
||||
fetchHassioSupervisorInfo(this.hass),
|
||||
fetchHassioHostInfo(this.hass),
|
||||
fetchHassioHomeAssistantInfo(this.hass),
|
||||
fetchHassioInfo(this.hass),
|
||||
]);
|
||||
this._supervisorInfo = supervisorInfo;
|
||||
this._hassioInfo = hassioInfo;
|
||||
this._hostInfo = hostInfo;
|
||||
this._hassInfo = hassInfo;
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import { HassioHassOSInfo, HassioHostInfo } from "../../src/data/hassio/host";
|
||||
import {
|
||||
HassioHomeAssistantInfo,
|
||||
HassioSupervisorInfo,
|
||||
HassioInfo,
|
||||
} from "../../src/data/hassio/supervisor";
|
||||
import {
|
||||
HassRouterPage,
|
||||
@@ -27,8 +26,6 @@ class HassioPanelRouter extends HassRouterPage {
|
||||
|
||||
@property({ attribute: false }) public supervisorInfo: HassioSupervisorInfo;
|
||||
|
||||
@property({ attribute: false }) public hassioInfo!: HassioInfo;
|
||||
|
||||
@property({ attribute: false }) public hostInfo: HassioHostInfo;
|
||||
|
||||
@property({ attribute: false }) public hassInfo: HassioHomeAssistantInfo;
|
||||
@@ -57,7 +54,6 @@ class HassioPanelRouter extends HassRouterPage {
|
||||
el.route = this.route;
|
||||
el.narrow = this.narrow;
|
||||
el.supervisorInfo = this.supervisorInfo;
|
||||
el.hassioInfo = this.hassioInfo;
|
||||
el.hostInfo = this.hostInfo;
|
||||
el.hassInfo = this.hassInfo;
|
||||
el.hassOsInfo = this.hassOsInfo;
|
||||
|
||||
@@ -10,7 +10,6 @@ import { HassioHassOSInfo, HassioHostInfo } from "../../src/data/hassio/host";
|
||||
import {
|
||||
HassioHomeAssistantInfo,
|
||||
HassioSupervisorInfo,
|
||||
HassioInfo,
|
||||
} from "../../src/data/hassio/supervisor";
|
||||
import type { PageNavigation } from "../../src/layouts/hass-tabs-subpage";
|
||||
import { HomeAssistant, Route } from "../../src/types";
|
||||
@@ -49,8 +48,6 @@ class HassioPanel extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public supervisorInfo!: HassioSupervisorInfo;
|
||||
|
||||
@property({ attribute: false }) public hassioInfo!: HassioInfo;
|
||||
|
||||
@property({ attribute: false }) public hostInfo!: HassioHostInfo;
|
||||
|
||||
@property({ attribute: false }) public hassInfo!: HassioHomeAssistantInfo;
|
||||
@@ -64,7 +61,6 @@ class HassioPanel extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.supervisorInfo=${this.supervisorInfo}
|
||||
.hassioInfo=${this.hassioInfo}
|
||||
.hostInfo=${this.hostInfo}
|
||||
.hassInfo=${this.hassInfo}
|
||||
.hassOsInfo=${this.hassOsInfo}
|
||||
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
shutdownHost,
|
||||
updateOS,
|
||||
} from "../../../src/data/hassio/host";
|
||||
import { HassioInfo } from "../../../src/data/hassio/supervisor";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
@@ -36,8 +35,6 @@ class HassioHostInfo extends LitElement {
|
||||
|
||||
@property() public hostInfo!: HassioHostInfoType;
|
||||
|
||||
@property({ attribute: false }) public hassioInfo!: HassioInfo;
|
||||
|
||||
@property() public hassOsInfo!: HassioHassOSInfo;
|
||||
|
||||
@property() private _errors?: string;
|
||||
@@ -57,12 +54,6 @@ class HassioHostInfo extends LitElement {
|
||||
<td>System</td>
|
||||
<td>${this.hostInfo.operating_system}</td>
|
||||
</tr>
|
||||
${!this.hostInfo.features.includes("hassos")
|
||||
? html`<tr>
|
||||
<td>Docker version</td>
|
||||
<td>${this.hassioInfo.docker}</td>
|
||||
</tr>`
|
||||
: ""}
|
||||
${this.hostInfo.deployment
|
||||
? html`
|
||||
<tr>
|
||||
|
||||
@@ -11,10 +11,7 @@ import {
|
||||
HassioHassOSInfo,
|
||||
HassioHostInfo,
|
||||
} from "../../../src/data/hassio/host";
|
||||
import {
|
||||
HassioSupervisorInfo,
|
||||
HassioInfo,
|
||||
} from "../../../src/data/hassio/supervisor";
|
||||
import { HassioSupervisorInfo } from "../../../src/data/hassio/supervisor";
|
||||
import "../../../src/layouts/hass-tabs-subpage";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
@@ -34,11 +31,9 @@ class HassioSystem extends LitElement {
|
||||
|
||||
@property() public supervisorInfo!: HassioSupervisorInfo;
|
||||
|
||||
@property({ attribute: false }) public hassioInfo!: HassioInfo;
|
||||
|
||||
@property() public hostInfo!: HassioHostInfo;
|
||||
|
||||
@property({ attribute: false }) public hassOsInfo!: HassioHassOSInfo;
|
||||
@property() public hassOsInfo!: HassioHassOSInfo;
|
||||
|
||||
public render(): TemplateResult | void {
|
||||
return html`
|
||||
@@ -60,7 +55,6 @@ class HassioSystem extends LitElement {
|
||||
></hassio-supervisor-info>
|
||||
<hassio-host-info
|
||||
.hass=${this.hass}
|
||||
.hassioInfo=${this.hassioInfo}
|
||||
.hostInfo=${this.hostInfo}
|
||||
.hassOsInfo=${this.hassOsInfo}
|
||||
></hassio-host-info>
|
||||
|
||||
+2
-6
@@ -22,7 +22,6 @@
|
||||
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"~app": "file:./src",
|
||||
"@formatjs/intl-pluralrules": "^1.5.8",
|
||||
"@fullcalendar/core": "^5.0.0-beta.2",
|
||||
"@fullcalendar/daygrid": "^5.0.0-beta.2",
|
||||
@@ -75,7 +74,6 @@
|
||||
"@thomasloven/round-slider": "0.5.0",
|
||||
"@vaadin/vaadin-combo-box": "^5.0.10",
|
||||
"@vaadin/vaadin-date-picker": "^4.0.7",
|
||||
"@vue/web-component-wrapper": "^1.2.0",
|
||||
"@webcomponents/webcomponentsjs": "^2.2.7",
|
||||
"chart.js": "~2.8.0",
|
||||
"chartjs-chart-timeline": "^0.3.0",
|
||||
@@ -89,7 +87,7 @@
|
||||
"fuse.js": "^6.0.0",
|
||||
"google-timezones-json": "^1.0.2",
|
||||
"hls.js": "^0.12.4",
|
||||
"home-assistant-js-websocket": "^5.3.0",
|
||||
"home-assistant-js-websocket": "^5.2.1",
|
||||
"idb-keyval": "^3.2.0",
|
||||
"intl-messageformat": "^8.3.9",
|
||||
"js-yaml": "^3.13.1",
|
||||
@@ -108,8 +106,6 @@
|
||||
"roboto-fontface": "^0.10.0",
|
||||
"superstruct": "^0.6.1",
|
||||
"unfetch": "^4.1.0",
|
||||
"vue": "^2.6.11",
|
||||
"vue2-daterange-picker": "^0.5.1",
|
||||
"web-animations-js": "^2.3.2",
|
||||
"workbox-core": "^5.1.3",
|
||||
"workbox-precaching": "^5.1.3",
|
||||
@@ -189,7 +185,7 @@
|
||||
"sinon": "^7.3.1",
|
||||
"source-map-url": "^0.4.0",
|
||||
"systemjs": "^6.3.2",
|
||||
"terser-webpack-plugin": "^3.0.6",
|
||||
"terser-webpack-plugin": "^1.2.3",
|
||||
"ts-lit-plugin": "^1.1.10",
|
||||
"ts-mocha": "^6.0.0",
|
||||
"typescript": "^3.8.3",
|
||||
|
||||
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20200620.0",
|
||||
version="20200603.1",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
||||
@@ -6,18 +6,19 @@ import {
|
||||
property,
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
import {
|
||||
AuthProvider,
|
||||
fetchAuthProviders,
|
||||
AuthUrlSearchParams,
|
||||
} from "../data/auth";
|
||||
import { AuthProvider, fetchAuthProviders } from "../data/auth";
|
||||
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
|
||||
import { registerServiceWorker } from "../util/register-service-worker";
|
||||
import "./ha-auth-flow";
|
||||
import { extractSearchParamsObject } from "../common/url/search-params";
|
||||
|
||||
import(/* webpackChunkName: "pick-auth-provider" */ "./ha-pick-auth-provider");
|
||||
|
||||
interface QueryParams {
|
||||
client_id?: string;
|
||||
redirect_uri?: string;
|
||||
state?: string;
|
||||
}
|
||||
|
||||
class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
@property() public clientId?: string;
|
||||
|
||||
@@ -32,7 +33,14 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
constructor() {
|
||||
super();
|
||||
this.translationFragment = "page-authorize";
|
||||
const query = extractSearchParamsObject() as AuthUrlSearchParams;
|
||||
const query: QueryParams = {};
|
||||
const values = location.search.substr(1).split("&");
|
||||
for (const item of values) {
|
||||
const value = item.split("=");
|
||||
if (value.length > 1) {
|
||||
query[decodeURIComponent(value[0])] = decodeURIComponent(value[1]);
|
||||
}
|
||||
}
|
||||
if (query.client_id) {
|
||||
this.clientId = query.client_id;
|
||||
}
|
||||
@@ -137,7 +145,7 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
response.status === 400 &&
|
||||
authProviders.code === "onboarding_required"
|
||||
) {
|
||||
location.href = `/onboarding.html${location.search}`;
|
||||
location.href = "/?";
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ export const isValidEntityId = (entityId: string) =>
|
||||
export const createValidEntityId = (input: string) =>
|
||||
input
|
||||
.toLowerCase()
|
||||
.replace(/\s|'|\./g, "_") // replace spaces, points and quotes with underscore
|
||||
.replace(/\s|'/g, "_") // replace spaces and quotes with underscore
|
||||
.replace(/\W/g, "") // remove not allowed chars
|
||||
.replace(/_{2,}/g, "_") // replace multiple underscores with 1
|
||||
.replace(/_$/, ""); // remove underscores at the end
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
export const extractSearchParamsObject = (): { [key: string]: string } => {
|
||||
const query = {};
|
||||
const searchParams = new URLSearchParams(location.search);
|
||||
for (const [key, value] of searchParams.entries()) {
|
||||
query[key] = value;
|
||||
}
|
||||
return query;
|
||||
};
|
||||
@@ -8,9 +8,6 @@ class HaProgressButton extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
:host {
|
||||
outline: none;
|
||||
}
|
||||
.container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
@@ -619,11 +619,6 @@ export class HaDataTable extends LitElement {
|
||||
text-transform: inherit;
|
||||
}
|
||||
|
||||
.mdc-data-table__cell a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.mdc-data-table__cell--numeric {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@@ -1,228 +0,0 @@
|
||||
import Vue from "vue";
|
||||
import wrap from "@vue/web-component-wrapper";
|
||||
import DateRangePicker from "vue2-daterange-picker";
|
||||
// @ts-ignore
|
||||
import dateRangePickerStyles from "vue2-daterange-picker/dist/vue2-daterange-picker.css";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { Constructor } from "../types";
|
||||
import { customElement } from "lit-element/lib/decorators";
|
||||
|
||||
const Component = Vue.extend({
|
||||
props: {
|
||||
twentyfourHours: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
ranges: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
startDate: {
|
||||
type: [String, Date],
|
||||
default() {
|
||||
return new Date();
|
||||
},
|
||||
},
|
||||
endDate: {
|
||||
type: [String, Date],
|
||||
default() {
|
||||
return new Date();
|
||||
},
|
||||
},
|
||||
},
|
||||
render(createElement) {
|
||||
// @ts-ignore
|
||||
return createElement(DateRangePicker, {
|
||||
props: {
|
||||
"time-picker": true,
|
||||
"auto-apply": false,
|
||||
opens: "right",
|
||||
"show-dropdowns": false,
|
||||
"time-picker24-hour": this.twentyfourHours,
|
||||
disabled: this.disabled,
|
||||
ranges: this.ranges ? {} : false,
|
||||
},
|
||||
model: {
|
||||
value: {
|
||||
startDate: this.startDate,
|
||||
endDate: this.endDate,
|
||||
},
|
||||
callback: (value) => {
|
||||
// @ts-ignore
|
||||
fireEvent(this.$el as HTMLElement, "change", value);
|
||||
},
|
||||
expression: "dateRange",
|
||||
},
|
||||
scopedSlots: {
|
||||
input() {
|
||||
return createElement("slot", {
|
||||
domProps: { name: "input" },
|
||||
});
|
||||
},
|
||||
header() {
|
||||
return createElement("slot", {
|
||||
domProps: { name: "header" },
|
||||
});
|
||||
},
|
||||
ranges() {
|
||||
return createElement("slot", {
|
||||
domProps: { name: "ranges" },
|
||||
});
|
||||
},
|
||||
footer() {
|
||||
return createElement("slot", {
|
||||
domProps: { name: "footer" },
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const WrappedElement: Constructor<HTMLElement> = wrap(Vue, Component);
|
||||
|
||||
@customElement("date-range-picker")
|
||||
class DateRangePickerElement extends WrappedElement {
|
||||
constructor() {
|
||||
super();
|
||||
const style = document.createElement("style");
|
||||
style.innerHTML = `
|
||||
${dateRangePickerStyles}
|
||||
.calendars {
|
||||
display: flex;
|
||||
}
|
||||
.daterangepicker {
|
||||
left: 0px !important;
|
||||
top: auto;
|
||||
background-color: var(--card-background-color);
|
||||
border: none;
|
||||
border-radius: var(--ha-card-border-radius, 4px);
|
||||
box-shadow: var(
|
||||
--ha-card-box-shadow,
|
||||
0px 2px 1px -1px rgba(0, 0, 0, 0.2),
|
||||
0px 1px 1px 0px rgba(0, 0, 0, 0.14),
|
||||
0px 1px 3px 0px rgba(0, 0, 0, 0.12)
|
||||
);
|
||||
color: var(--primary-text-color);
|
||||
min-width: initial !important;
|
||||
}
|
||||
.daterangepicker:after {
|
||||
border-bottom: 6px solid var(--card-background-color);
|
||||
}
|
||||
.daterangepicker .calendar-table {
|
||||
background-color: var(--card-background-color);
|
||||
border: none;
|
||||
}
|
||||
.daterangepicker .calendar-table td,
|
||||
.daterangepicker .calendar-table th {
|
||||
background-color: transparent;
|
||||
color: var(--secondary-text-color);
|
||||
border-radius: 0;
|
||||
outline: none;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
.daterangepicker td.off,
|
||||
.daterangepicker td.off.end-date,
|
||||
.daterangepicker td.off.in-range,
|
||||
.daterangepicker td.off.start-date {
|
||||
background-color: var(--secondary-background-color);
|
||||
color: var(--disabled-text-color);
|
||||
}
|
||||
.daterangepicker td.in-range {
|
||||
background-color: var(--light-primary-color);
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.daterangepicker td.active,
|
||||
.daterangepicker td.active:hover {
|
||||
background-color: var(--primary-color);
|
||||
color: var(--text-primary-color);
|
||||
}
|
||||
.daterangepicker td.start-date.end-date {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.daterangepicker td.start-date {
|
||||
border-radius: 50% 0 0 50%;
|
||||
}
|
||||
.daterangepicker td.end-date {
|
||||
border-radius: 0 50% 50% 0;
|
||||
}
|
||||
.reportrange-text {
|
||||
background: none !important;
|
||||
padding: 0 !important;
|
||||
border: none !important;
|
||||
}
|
||||
.daterangepicker .calendar-table .next span,
|
||||
.daterangepicker .calendar-table .prev span {
|
||||
border: solid var(--primary-text-color);
|
||||
border-width: 0 2px 2px 0;
|
||||
}
|
||||
.daterangepicker .ranges li {
|
||||
outline: none;
|
||||
}
|
||||
.daterangepicker .ranges li:hover {
|
||||
background-color: var(--secondary-background-color);
|
||||
}
|
||||
.daterangepicker .ranges li.active {
|
||||
background-color: var(--primary-color);
|
||||
color: var(--text-primary-color);
|
||||
}
|
||||
.daterangepicker select.ampmselect,
|
||||
.daterangepicker select.hourselect,
|
||||
.daterangepicker select.minuteselect,
|
||||
.daterangepicker select.secondselect {
|
||||
background: transparent;
|
||||
border: 1px solid var(--divider-color);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.daterangepicker .drp-buttons .btn {
|
||||
border: 1px solid var(--primary-color);
|
||||
background-color: transparent;
|
||||
color: var(--primary-color);
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.calendars-container {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.drp-calendar.col.right .calendar-table {
|
||||
display: none;
|
||||
}
|
||||
.daterangepicker.show-ranges .drp-calendar.left {
|
||||
border-left: 0px;
|
||||
}
|
||||
.daterangepicker .drp-calendar.left {
|
||||
padding: 8px;
|
||||
}
|
||||
.daterangepicker.show-calendar .ranges {
|
||||
margin-top: 0;
|
||||
padding-top: 8px;
|
||||
border-right: 1px solid var(--divider-color);
|
||||
}
|
||||
@media only screen and (max-width: 800px) {
|
||||
.calendars {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
.calendar-table {
|
||||
padding: 0 !important;
|
||||
}
|
||||
`;
|
||||
const shadowRoot = this.shadowRoot!;
|
||||
shadowRoot.appendChild(style);
|
||||
// Stop click events from reaching the document, otherwise it will close the picker immediately.
|
||||
shadowRoot.addEventListener("click", (ev) => ev.stopPropagation());
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"date-range-picker": DateRangePickerElement;
|
||||
}
|
||||
}
|
||||
@@ -125,19 +125,23 @@ class HaCameraStream extends LitElement {
|
||||
}
|
||||
|
||||
private async _startHls(): Promise<void> {
|
||||
// eslint-disable-next-line
|
||||
const Hls = ((await import(
|
||||
/* webpackChunkName: "hls.js" */ "hls.js"
|
||||
)) as any).default as HLSModule;
|
||||
let hlsSupported = Hls.isSupported();
|
||||
const videoEl = this._videoEl;
|
||||
|
||||
if (!hlsSupported) {
|
||||
hlsSupported =
|
||||
videoEl.canPlayType("application/vnd.apple.mpegurl") !== "";
|
||||
const nativeHlsSupported =
|
||||
!!videoEl.canPlayType &&
|
||||
(videoEl.canPlayType("application/vnd.apple.mpegURL") !== "" ||
|
||||
videoEl.canPlayType("audio/mpegurl") !== "");
|
||||
|
||||
let hlsSupported = false;
|
||||
let Hls: HLSModule | undefined;
|
||||
|
||||
if (!nativeHlsSupported) {
|
||||
Hls = ((await import(/* webpackChunkName: "hls.js" */ "hls.js")) as any)
|
||||
.default as HLSModule;
|
||||
hlsSupported = Hls.isSupported();
|
||||
}
|
||||
|
||||
if (!hlsSupported) {
|
||||
if (!nativeHlsSupported && !hlsSupported) {
|
||||
this._forceMJPEG = this.stateObj!.entity_id;
|
||||
return;
|
||||
}
|
||||
@@ -148,10 +152,10 @@ class HaCameraStream extends LitElement {
|
||||
this.stateObj!.entity_id
|
||||
);
|
||||
|
||||
if (Hls.isSupported()) {
|
||||
this._renderHLSPolyfill(videoEl, Hls, url);
|
||||
} else {
|
||||
if (nativeHlsSupported) {
|
||||
this._renderHLSNative(videoEl, url);
|
||||
} else {
|
||||
this._renderHLSPolyfill(videoEl, Hls!, url);
|
||||
}
|
||||
return;
|
||||
} catch (err) {
|
||||
@@ -177,7 +181,9 @@ class HaCameraStream extends LitElement {
|
||||
url: string
|
||||
) {
|
||||
const hls = new Hls({
|
||||
liveBackBufferLength: 60,
|
||||
liveSyncDurationCount: 3,
|
||||
liveBackBufferLength: 0,
|
||||
liveDurationInfinity: true,
|
||||
});
|
||||
this._hlsPolyfillInstance = hls;
|
||||
hls.attachMedia(videoEl);
|
||||
|
||||
@@ -12,8 +12,6 @@ import {
|
||||
class HaCard extends LitElement {
|
||||
@property() public header?: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public outlined = false;
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
@@ -21,12 +19,12 @@ class HaCard extends LitElement {
|
||||
--ha-card-background,
|
||||
var(--paper-card-background-color, white)
|
||||
);
|
||||
border-radius: var(--ha-card-border-radius, 4px);
|
||||
border-radius: var(--ha-card-border-radius, 2px);
|
||||
box-shadow: var(
|
||||
--ha-card-box-shadow,
|
||||
0px 2px 1px -1px rgba(0, 0, 0, 0.2),
|
||||
0px 1px 1px 0px rgba(0, 0, 0, 0.14),
|
||||
0px 1px 3px 0px rgba(0, 0, 0, 0.12)
|
||||
0 2px 2px 0 rgba(0, 0, 0, 0.14),
|
||||
0 1px 5px 0 rgba(0, 0, 0, 0.12),
|
||||
0 3px 1px -2px rgba(0, 0, 0, 0.2)
|
||||
);
|
||||
color: var(--primary-text-color);
|
||||
display: block;
|
||||
@@ -34,16 +32,6 @@ class HaCard extends LitElement {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
:host([outlined]) {
|
||||
box-shadow: none;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: var(
|
||||
--ha-card-border-color,
|
||||
var(--divider-color, #e0e0e0)
|
||||
);
|
||||
}
|
||||
|
||||
.card-header,
|
||||
:host ::slotted(.card-header) {
|
||||
color: var(--ha-card-header-color, --primary-text-color);
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { mdiCalendar } from "@mdi/js";
|
||||
import { formatDateTime } from "../common/datetime/format_date_time";
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "./ha-svg-icon";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@material/mwc-list/mwc-list";
|
||||
import "./date-range-picker";
|
||||
|
||||
export interface DateRangePickerRanges {
|
||||
[key: string]: [Date, Date];
|
||||
}
|
||||
|
||||
@customElement("ha-date-range-picker")
|
||||
export class HaDateRangePicker extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public startDate!: Date;
|
||||
|
||||
@property() public endDate!: Date;
|
||||
|
||||
@property() public ranges?: DateRangePickerRanges;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) private _hour24format = false;
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
if (changedProps.has("hass")) {
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
if (!oldHass || oldHass.language !== this.hass.language) {
|
||||
this._hour24format = this._compute24hourFormat();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<date-range-picker
|
||||
?disabled=${this.disabled}
|
||||
twentyfour-hours=${this._hour24format}
|
||||
start-date=${this.startDate}
|
||||
end-date=${this.endDate}
|
||||
?ranges=${this.ranges !== undefined}
|
||||
>
|
||||
<div slot="input" class="date-range-inputs">
|
||||
<ha-svg-icon path=${mdiCalendar}></ha-svg-icon>
|
||||
<paper-input
|
||||
.value=${formatDateTime(this.startDate, this.hass.language)}
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.date-range-picker.start_date"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
@click=${this._handleInputClick}
|
||||
readonly
|
||||
></paper-input>
|
||||
<paper-input
|
||||
.value=${formatDateTime(this.endDate, this.hass.language)}
|
||||
label=${this.hass.localize(
|
||||
"ui.components.date-range-picker.end_date"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
@click=${this._handleInputClick}
|
||||
readonly
|
||||
></paper-input>
|
||||
</div>
|
||||
${this.ranges
|
||||
? html`<div slot="ranges" class="date-range-ranges">
|
||||
<mwc-list @click=${this._setDateRange}>
|
||||
${Object.entries(this.ranges).map(
|
||||
([name, dates]) => html`<mwc-list-item
|
||||
.activated=${this.startDate.getTime() ===
|
||||
dates[0].getTime() &&
|
||||
this.endDate.getTime() === dates[1].getTime()}
|
||||
.startDate=${dates[0]}
|
||||
.endDate=${dates[1]}
|
||||
>
|
||||
${name}
|
||||
</mwc-list-item>`
|
||||
)}
|
||||
</mwc-list>
|
||||
</div>`
|
||||
: ""}
|
||||
<div slot="footer" class="date-range-footer">
|
||||
<mwc-button @click=${this._cancelDateRange}
|
||||
>${this.hass.localize("ui.common.cancel")}</mwc-button
|
||||
>
|
||||
<mwc-button @click=${this._applyDateRange}
|
||||
>${this.hass.localize(
|
||||
"ui.components.date-range-picker.select"
|
||||
)}</mwc-button
|
||||
>
|
||||
</div>
|
||||
</date-range-picker>
|
||||
`;
|
||||
}
|
||||
|
||||
private _compute24hourFormat() {
|
||||
return (
|
||||
new Intl.DateTimeFormat(this.hass.language, {
|
||||
hour: "numeric",
|
||||
})
|
||||
.formatToParts(new Date(2020, 0, 1, 13))
|
||||
.find((part) => part.type === "hour")!.value.length === 2
|
||||
);
|
||||
}
|
||||
|
||||
private _setDateRange(ev: Event) {
|
||||
const target = ev.target as any;
|
||||
const startDate = target.startDate;
|
||||
const endDate = target.endDate;
|
||||
const dateRangePicker = this._dateRangePicker;
|
||||
dateRangePicker.clickRange([startDate, endDate]);
|
||||
dateRangePicker.clickedApply();
|
||||
}
|
||||
|
||||
private _cancelDateRange() {
|
||||
this._dateRangePicker.clickCancel();
|
||||
}
|
||||
|
||||
private _applyDateRange() {
|
||||
this._dateRangePicker.clickedApply();
|
||||
}
|
||||
|
||||
private get _dateRangePicker() {
|
||||
const dateRangePicker = this.shadowRoot!.querySelector(
|
||||
"date-range-picker"
|
||||
) as any;
|
||||
return dateRangePicker.vueComponent.$children[0];
|
||||
}
|
||||
|
||||
private _handleInputClick() {
|
||||
// close the date picker, so it will open again on the click event
|
||||
if (this._dateRangePicker.open) {
|
||||
this._dateRangePicker.open = false;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-svg-icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.date-range-inputs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.date-range-ranges {
|
||||
border-right: 1px solid var(--divider-color);
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
.date-range-ranges {
|
||||
border-right: none;
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
}
|
||||
|
||||
.date-range-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 8px;
|
||||
border-top: 1px solid var(--divider-color);
|
||||
}
|
||||
|
||||
paper-input {
|
||||
display: inline-block;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
paper-input:last-child {
|
||||
margin-left: 8px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-date-range-picker": HaDateRangePicker;
|
||||
}
|
||||
}
|
||||
@@ -25,9 +25,6 @@ export class HaDialog extends MwcDialog {
|
||||
return [
|
||||
style,
|
||||
css`
|
||||
.mdc-dialog {
|
||||
z-index: var(--dialog-z-index, 7);
|
||||
}
|
||||
.mdc-dialog__actions {
|
||||
justify-content: var(--justify-action-buttons, flex-end);
|
||||
}
|
||||
|
||||
@@ -24,21 +24,28 @@ export class HaIconButton extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<mwc-icon-button .label=${this.label} .disabled=${this.disabled}>
|
||||
<mwc-icon-button
|
||||
.label=${this.label}
|
||||
?disabled=${this.disabled}
|
||||
@click=${this._handleClick}
|
||||
>
|
||||
<ha-icon .icon=${this.icon}></ha-icon>
|
||||
</mwc-icon-button>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleClick(ev) {
|
||||
if (this.disabled) {
|
||||
ev.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: inline-block;
|
||||
outline: none;
|
||||
}
|
||||
:host([disabled]) {
|
||||
pointer-events: none;
|
||||
}
|
||||
mwc-icon-button {
|
||||
--mdc-theme-on-primary: currentColor;
|
||||
--mdc-theme-text-disabled-on-light: var(--disabled-text-color);
|
||||
|
||||
@@ -54,7 +54,7 @@ class HaMarkdown extends LitElement {
|
||||
}
|
||||
ha-markdown-element code,
|
||||
pre {
|
||||
background-color: var(--markdown-code-background-color, none);
|
||||
background-color: var(--markdown-code-background-color, #f6f8fa);
|
||||
border-radius: 3px;
|
||||
}
|
||||
ha-markdown-element code {
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
Wrapper for paper-textarea.
|
||||
|
||||
paper-textarea crashes on iOS when created programmatically. This only impacts
|
||||
our automation and script editors as they are using Preact. Polymer is using
|
||||
template elements and does not have this issue.
|
||||
|
||||
paper-textarea issue: https://github.com/PolymerElements/paper-input/issues/556
|
||||
WebKit issue: https://bugs.webkit.org/show_bug.cgi?id=174629
|
||||
*/
|
||||
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
class HaTextarea extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<paper-textarea
|
||||
label="[[label]]"
|
||||
placeholder="[[placeholder]]"
|
||||
value="{{value}}"
|
||||
></paper-textarea>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
name: String,
|
||||
label: String,
|
||||
placeholder: String,
|
||||
value: {
|
||||
type: String,
|
||||
notify: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-textarea", HaTextarea);
|
||||
@@ -1,11 +1,5 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface AuthUrlSearchParams {
|
||||
client_id?: string;
|
||||
redirect_uri?: string;
|
||||
state?: string;
|
||||
}
|
||||
|
||||
export interface AuthProvider {
|
||||
name: string;
|
||||
id: string;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { HomeAssistant } from "../types";
|
||||
import { DataEntryFlowProgress, DataEntryFlowStep } from "./data_entry_flow";
|
||||
import { domainToName } from "./integration";
|
||||
|
||||
export const DISCOVERY_SOURCES = ["unignore", "homekit", "ssdp", "zeroconf", "discovery"];
|
||||
export const DISCOVERY_SOURCES = ["unignore", "homekit", "ssdp", "zeroconf"];
|
||||
|
||||
export const createConfigFlow = (hass: HomeAssistant, handler: string) =>
|
||||
hass.callApi<DataEntryFlowStep>("POST", "config/config_entries/flow", {
|
||||
|
||||
@@ -12,11 +12,6 @@ export interface ConfigUpdateValues {
|
||||
internal_url?: string | null;
|
||||
}
|
||||
|
||||
export interface CheckConfigResult {
|
||||
result: "valid" | "invalid";
|
||||
errors: string | null;
|
||||
}
|
||||
|
||||
export const saveCoreConfig = (
|
||||
hass: HomeAssistant,
|
||||
values: Partial<ConfigUpdateValues>
|
||||
@@ -30,6 +25,3 @@ export const detectCoreConfig = (hass: HomeAssistant) =>
|
||||
hass.callWS<Partial<ConfigUpdateValues>>({
|
||||
type: "config/core/detect",
|
||||
});
|
||||
|
||||
export const checkCoreConfig = (hass: HomeAssistant) =>
|
||||
hass.callApi<CheckConfigResult>("POST", "config/core/check_config");
|
||||
|
||||
@@ -4,20 +4,6 @@ import { hassioApiResultExtractor, HassioResponse } from "./common";
|
||||
export type HassioHomeAssistantInfo = any;
|
||||
export type HassioSupervisorInfo = any;
|
||||
|
||||
export type HassioInfo = {
|
||||
arch: string;
|
||||
channel: string;
|
||||
docker: string;
|
||||
hassos?: string;
|
||||
homeassistant: string;
|
||||
hostname: string;
|
||||
logging: string;
|
||||
maching: string;
|
||||
supervisor: string;
|
||||
supported_arch: string[];
|
||||
timezone: string;
|
||||
};
|
||||
|
||||
export type HassioPanelInfo = PanelInfo<
|
||||
| undefined
|
||||
| {
|
||||
@@ -52,12 +38,6 @@ export const fetchHassioSupervisorInfo = async (hass: HomeAssistant) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const fetchHassioInfo = async (hass: HomeAssistant) => {
|
||||
return hassioApiResultExtractor(
|
||||
await hass.callApi<HassioResponse<HassioInfo>>("GET", "hassio/info")
|
||||
);
|
||||
};
|
||||
|
||||
export const fetchHassioLogs = async (
|
||||
hass: HomeAssistant,
|
||||
provider: string
|
||||
|
||||
@@ -1,67 +1,7 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface LogbookEntry {
|
||||
when: string;
|
||||
name: string;
|
||||
message: string;
|
||||
entity_id?: string;
|
||||
domain: string;
|
||||
context_user_id?: string;
|
||||
}
|
||||
|
||||
const DATA_CACHE: {
|
||||
[cacheKey: string]: { [entityId: string]: Promise<LogbookEntry[]> };
|
||||
} = {};
|
||||
|
||||
export const getLogbookData = (
|
||||
hass: HomeAssistant,
|
||||
startDate: string,
|
||||
endDate: string,
|
||||
entityId?: string
|
||||
) => {
|
||||
const ALL_ENTITIES = "*";
|
||||
|
||||
if (!entityId) {
|
||||
entityId = ALL_ENTITIES;
|
||||
}
|
||||
|
||||
const cacheKey = `${startDate}${endDate}`;
|
||||
|
||||
if (!DATA_CACHE[cacheKey]) {
|
||||
DATA_CACHE[cacheKey] = {};
|
||||
}
|
||||
|
||||
if (DATA_CACHE[cacheKey][entityId]) {
|
||||
return DATA_CACHE[cacheKey][entityId];
|
||||
}
|
||||
|
||||
if (entityId !== ALL_ENTITIES && DATA_CACHE[cacheKey][ALL_ENTITIES]) {
|
||||
return DATA_CACHE[cacheKey][ALL_ENTITIES].then((entities) =>
|
||||
entities.filter((entity) => entity.entity_id === entityId)
|
||||
);
|
||||
}
|
||||
|
||||
DATA_CACHE[cacheKey][entityId] = getLogbookDataFromServer(
|
||||
hass,
|
||||
startDate,
|
||||
endDate,
|
||||
entityId !== ALL_ENTITIES ? entityId : undefined
|
||||
).then((entries) => entries.reverse());
|
||||
return DATA_CACHE[cacheKey][entityId];
|
||||
};
|
||||
|
||||
const getLogbookDataFromServer = async (
|
||||
hass: HomeAssistant,
|
||||
startDate: string,
|
||||
endDate: string,
|
||||
entityId?: string
|
||||
) => {
|
||||
const url = `logbook/${startDate}?end_time=${endDate}${
|
||||
entityId ? `&entity=${entityId}` : ""
|
||||
}`;
|
||||
return hass.callApi<LogbookEntry[]>("GET", url);
|
||||
};
|
||||
|
||||
export const clearLogbookCache = (startDate, endDate) => {
|
||||
DATA_CACHE[`${startDate}${endDate}`] = {};
|
||||
};
|
||||
|
||||
@@ -51,7 +51,7 @@ export const onboardCoreConfigStep = (hass: HomeAssistant) =>
|
||||
|
||||
export const onboardIntegrationStep = (
|
||||
hass: HomeAssistant,
|
||||
params: { client_id: string; redirect_uri: string }
|
||||
params: { client_id: string }
|
||||
) =>
|
||||
hass.callApi<OnboardingIntegrationStepResponse>(
|
||||
"POST",
|
||||
|
||||
@@ -42,8 +42,6 @@ class StepFlowPickHandler extends LitElement {
|
||||
|
||||
private _width?: number;
|
||||
|
||||
private _height?: number;
|
||||
|
||||
private _getHandlers = memoizeOne(
|
||||
(h: string[], filter?: string, _localize?: LocalizeFunc) => {
|
||||
const handlers: HandlerObj[] = h.map((handler) => {
|
||||
@@ -84,10 +82,7 @@ class StepFlowPickHandler extends LitElement {
|
||||
@value-changed=${this._filterChanged}
|
||||
></search-input>
|
||||
<div
|
||||
style=${styleMap({
|
||||
width: `${this._width}px`,
|
||||
height: `${this._height}px`,
|
||||
})}
|
||||
style=${styleMap({ width: `${this._width}px` })}
|
||||
class=${classMap({ advanced: Boolean(this.showAdvanced) })}
|
||||
>
|
||||
${handlers.map(
|
||||
@@ -144,20 +139,13 @@ class StepFlowPickHandler extends LitElement {
|
||||
|
||||
protected updated(changedProps) {
|
||||
super.updated(changedProps);
|
||||
// Store the width and height so that when we search, box doesn't jump
|
||||
const div = this.shadowRoot!.querySelector("div")!;
|
||||
// Store the width so that when we search, box doesn't jump
|
||||
if (!this._width) {
|
||||
const width = div.clientWidth;
|
||||
const width = this.shadowRoot!.querySelector("div")!.clientWidth;
|
||||
if (width) {
|
||||
this._width = width;
|
||||
}
|
||||
}
|
||||
if (!this._height) {
|
||||
const height = div.clientHeight;
|
||||
if (height) {
|
||||
this._height = height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async _filterChanged(e) {
|
||||
@@ -178,8 +166,8 @@ class StepFlowPickHandler extends LitElement {
|
||||
configFlowContentStyles,
|
||||
css`
|
||||
img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
max-width: 40px;
|
||||
max-height: 40px;
|
||||
}
|
||||
search-input {
|
||||
display: block;
|
||||
@@ -192,12 +180,12 @@ class StepFlowPickHandler extends LitElement {
|
||||
overflow: auto;
|
||||
max-height: 600px;
|
||||
}
|
||||
@media all and (max-height: 900px) {
|
||||
@media all and (max-height: 1px) {
|
||||
div {
|
||||
max-height: calc(100vh - 134px);
|
||||
max-height: calc(100vh - 205px);
|
||||
}
|
||||
div.advanced {
|
||||
max-height: calc(100vh - 250px);
|
||||
max-height: calc(100vh - 300px);
|
||||
}
|
||||
}
|
||||
paper-icon-item {
|
||||
|
||||
@@ -66,7 +66,7 @@ class DialogDeviceRegistryDetail extends LitElement {
|
||||
<paper-input
|
||||
.value=${this._nameByUser}
|
||||
@value-changed=${this._nameChanged}
|
||||
.label=${this.hass.localize("ui.panel.config.devices.name")}
|
||||
.label=${this.hass.localize("ui.dialogs.devices.name")}
|
||||
.placeholder=${device.name || ""}
|
||||
.disabled=${this._submitting}
|
||||
></paper-input>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
css,
|
||||
@@ -10,7 +11,7 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import "../../components/ha-dialog";
|
||||
import "../../components/dialog/ha-paper-dialog";
|
||||
import "../../components/ha-switch";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
@@ -40,17 +41,21 @@ class DialogBox extends LitElement {
|
||||
const confirmPrompt = this._params.confirmation || this._params.prompt;
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
@close=${this._close}
|
||||
.heading=${this._params.title
|
||||
? this._params.title
|
||||
: this._params.confirmation &&
|
||||
this.hass.localize("ui.dialogs.generic.default_confirmation_title")}
|
||||
<ha-paper-dialog
|
||||
with-backdrop
|
||||
opened
|
||||
modal
|
||||
@opened-changed="${this._openedChanged}"
|
||||
>
|
||||
<div>
|
||||
<h2>
|
||||
${this._params.title
|
||||
? this._params.title
|
||||
: this._params.confirmation &&
|
||||
this.hass.localize(
|
||||
"ui.dialogs.generic.default_confirmation_title"
|
||||
)}
|
||||
</h2>
|
||||
<paper-dialog-scrollable>
|
||||
${this._params.text
|
||||
? html`
|
||||
<p
|
||||
@@ -78,21 +83,23 @@ class DialogBox extends LitElement {
|
||||
></paper-input>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
${confirmPrompt &&
|
||||
html`
|
||||
<mwc-button @click=${this._dismiss} slot="secondaryAction">
|
||||
${this._params.dismissText
|
||||
? this._params.dismissText
|
||||
: this.hass.localize("ui.dialogs.generic.cancel")}
|
||||
</paper-dialog-scrollable>
|
||||
<div class="paper-dialog-buttons">
|
||||
${confirmPrompt &&
|
||||
html`
|
||||
<mwc-button @click="${this._dismiss}">
|
||||
${this._params.dismissText
|
||||
? this._params.dismissText
|
||||
: this.hass.localize("ui.dialogs.generic.cancel")}
|
||||
</mwc-button>
|
||||
`}
|
||||
<mwc-button @click="${this._confirm}">
|
||||
${this._params.confirmText
|
||||
? this._params.confirmText
|
||||
: this.hass.localize("ui.dialogs.generic.ok")}
|
||||
</mwc-button>
|
||||
`}
|
||||
<mwc-button @click=${this._confirm} slot="primaryAction">
|
||||
${this._params.confirmText
|
||||
? this._params.confirmText
|
||||
: this.hass.localize("ui.dialogs.generic.ok")}
|
||||
</mwc-button>
|
||||
</ha-dialog>
|
||||
</div>
|
||||
</ha-paper-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -120,8 +127,10 @@ class DialogBox extends LitElement {
|
||||
this._dismiss();
|
||||
}
|
||||
|
||||
private _close(): void {
|
||||
this._params = undefined;
|
||||
private _openedChanged(ev: PolymerChangedEvent<boolean>): void {
|
||||
if (!(ev.detail as any).value) {
|
||||
this._params = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
@@ -132,6 +141,15 @@ class DialogBox extends LitElement {
|
||||
pointer-events: initial !important;
|
||||
cursor: initial !important;
|
||||
}
|
||||
ha-paper-dialog {
|
||||
min-width: 400px;
|
||||
max-width: 500px;
|
||||
}
|
||||
@media (max-width: 400px) {
|
||||
ha-paper-dialog {
|
||||
min-width: initial;
|
||||
}
|
||||
}
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
@@ -147,10 +165,6 @@ class DialogBox extends LitElement {
|
||||
.secondary {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
ha-dialog {
|
||||
/* Place above other dialogs */
|
||||
--dialog-z-index: 104;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ class MoreInfoControls extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
<div class="main-title" main-title="" on-click="enlarge">
|
||||
[[_computeStateName(stateObj)]]
|
||||
</div>
|
||||
<template is="dom-if" if="[[hass.user.is_admin]]">
|
||||
<template is="dom-if" if="[[_computeConfig(hass)]]">
|
||||
<ha-icon-button
|
||||
aria-label$="[[localize('ui.dialogs.more_info_control.settings')]]"
|
||||
icon="hass:settings"
|
||||
@@ -219,6 +219,10 @@ class MoreInfoControls extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
return stateObj ? computeStateName(stateObj) : "";
|
||||
}
|
||||
|
||||
_computeConfig(hass) {
|
||||
return hass.user.is_admin && isComponentLoaded(hass, "config");
|
||||
}
|
||||
|
||||
_computeEdit(hass, stateObj) {
|
||||
const domain = this._computeDomain(stateObj);
|
||||
return (
|
||||
|
||||
+22
-25
@@ -8,18 +8,15 @@ import {
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { computeStateName } from "../../../../../../common/entity/compute_state_name";
|
||||
import "../../../../../../components/ha-dialog";
|
||||
import "../../../../../../components/ha-switch";
|
||||
import "../../../../../../components/ha-formfield";
|
||||
import type { HaSwitch } from "../../../../../../components/ha-switch";
|
||||
import { computeDeviceName } from "../../../../../../data/device_registry";
|
||||
import {
|
||||
fetchMQTTDebugInfo,
|
||||
MQTTDeviceDebugInfo,
|
||||
} from "../../../../../../data/mqtt";
|
||||
import { haStyleDialog } from "../../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../../types";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import "../../components/ha-dialog";
|
||||
import "../../components/ha-switch";
|
||||
import "../../components/ha-formfield";
|
||||
import type { HaSwitch } from "../../components/ha-switch";
|
||||
import { computeDeviceName } from "../../data/device_registry";
|
||||
import { fetchMQTTDebugInfo, MQTTDeviceDebugInfo } from "../../data/mqtt";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "./mqtt-discovery-payload";
|
||||
import "./mqtt-messages";
|
||||
import { MQTTDeviceDebugInfoDialogParams } from "./show-dialog-mqtt-device-debug-info";
|
||||
@@ -190,19 +187,19 @@ class DialogMQTTDeviceDebugInfo extends LitElement {
|
||||
<li class="triggerlistitem">
|
||||
MQTT discovery data:
|
||||
<ul class="discoverydata">
|
||||
<li>
|
||||
Topic:
|
||||
<code>${trigger.discovery_data.topic}</code>
|
||||
</li>
|
||||
<li>
|
||||
<mqtt-discovery-payload
|
||||
.hass=${this.hass}
|
||||
.payload=${trigger.discovery_data.payload}
|
||||
.showAsYaml=${this._showAsYaml}
|
||||
.summary=${"Payload"}
|
||||
>
|
||||
</mqtt-discovery-payload>
|
||||
</li>
|
||||
<li>
|
||||
Topic:
|
||||
<code>${trigger.discovery_data.topic}</code>
|
||||
</li>
|
||||
<li>
|
||||
<mqtt-discovery-payload
|
||||
.hass=${this.hass}
|
||||
.payload=${trigger.discovery_data.payload}
|
||||
.showAsYaml=${this._showAsYaml}
|
||||
.summary=${"Payload"}
|
||||
>
|
||||
</li>
|
||||
</mqtt-discovery-payload>
|
||||
</ul>
|
||||
</li>
|
||||
`
|
||||
+3
-3
@@ -9,9 +9,9 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { formatTimeWithSeconds } from "../../../../../../common/datetime/format_time";
|
||||
import { HomeAssistant } from "../../../../../../types";
|
||||
import { MQTTMessage } from "../../../../../../data/mqtt";
|
||||
import { formatTimeWithSeconds } from "../../common/datetime/format_time";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { MQTTMessage } from "../../data/mqtt";
|
||||
|
||||
@customElement("mqtt-messages")
|
||||
class MQTTMessages extends LitElement {
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { fireEvent } from "../../../../../../common/dom/fire_event";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { DeviceRegistryEntry } from "../../data/device_registry";
|
||||
|
||||
export interface MQTTDeviceDebugInfoDialogParams {
|
||||
device: DeviceRegistryEntry;
|
||||
@@ -0,0 +1,113 @@
|
||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../components/dialog/ha-paper-dialog";
|
||||
import type { HaPaperDialog } from "../../components/dialog/ha-paper-dialog";
|
||||
import { fetchZHADevice, ZHADevice } from "../../data/zha";
|
||||
import "../../panels/config/zha/zha-device-card";
|
||||
import type { PolymerChangedEvent } from "../../polymer-types";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { ZHADeviceInfoDialogParams } from "./show-dialog-zha-device-info";
|
||||
|
||||
@customElement("dialog-zha-device-info")
|
||||
class DialogZHADeviceInfo extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() private _params?: ZHADeviceInfoDialogParams;
|
||||
|
||||
@property() private _error?: string;
|
||||
|
||||
@property() private _device?: ZHADevice;
|
||||
|
||||
public async showDialog(params: ZHADeviceInfoDialogParams): Promise<void> {
|
||||
this._params = params;
|
||||
this._device = await fetchZHADevice(this.hass, params.ieee);
|
||||
await this.updateComplete;
|
||||
this._dialog.open();
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._params || !this._device) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-paper-dialog
|
||||
with-backdrop
|
||||
opened
|
||||
@opened-changed=${this._openedChanged}
|
||||
>
|
||||
${this._error
|
||||
? html` <div class="error">${this._error}</div> `
|
||||
: html`
|
||||
<zha-device-card
|
||||
class="card"
|
||||
.hass=${this.hass}
|
||||
.device=${this._device}
|
||||
@zha-device-removed=${this._onDeviceRemoved}
|
||||
.showEntityDetail=${false}
|
||||
.showActions="${this._device.device_type !== "Coordinator"}"
|
||||
></zha-device-card>
|
||||
`}
|
||||
</ha-paper-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _openedChanged(ev: PolymerChangedEvent<boolean>): void {
|
||||
if (!ev.detail.value) {
|
||||
this._params = undefined;
|
||||
this._error = undefined;
|
||||
this._device = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private _onDeviceRemoved(): void {
|
||||
this._closeDialog();
|
||||
}
|
||||
|
||||
private get _dialog(): HaPaperDialog {
|
||||
return this.shadowRoot!.querySelector("ha-paper-dialog")!;
|
||||
}
|
||||
|
||||
private _closeDialog() {
|
||||
this._dialog.close();
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-paper-dialog > * {
|
||||
margin: 0;
|
||||
display: block;
|
||||
padding: 0;
|
||||
}
|
||||
.card {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex: 1 0 300px;
|
||||
min-width: 0;
|
||||
max-width: 600px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.error {
|
||||
color: var(--google-red-500);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-zha-device-info": DialogZHADeviceInfo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
|
||||
export interface ZHADeviceInfoDialogParams {
|
||||
ieee: string;
|
||||
}
|
||||
|
||||
export const loadZHADeviceInfoDialog = () =>
|
||||
import(
|
||||
/* webpackChunkName: "dialog-zha-device-info" */ "./dialog-zha-device-info"
|
||||
);
|
||||
|
||||
export const showZHADeviceInfoDialog = (
|
||||
element: HTMLElement,
|
||||
zhaDeviceInfoParams: ZHADeviceInfoDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-zha-device-info",
|
||||
dialogImport: loadZHADeviceInfoDialog,
|
||||
dialogParams: zhaDeviceInfoParams,
|
||||
});
|
||||
};
|
||||
+4
-4
@@ -6,10 +6,10 @@ import {
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../../../components/ha-code-editor";
|
||||
import { createCloseHeading } from "../../../../../components/ha-dialog";
|
||||
import { haStyleDialog } from "../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import "../../components/ha-code-editor";
|
||||
import { createCloseHeading } from "../../components/ha-dialog";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { ZHADeviceZigbeeInfoDialogParams } from "./show-dialog-zha-device-zigbee-info";
|
||||
|
||||
@customElement("dialog-zha-device-zigbee-info")
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { ZHADevice } from "../../../../../data/zha";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { ZHADevice } from "../../data/zha";
|
||||
|
||||
export interface ZHADeviceZigbeeInfoDialogParams {
|
||||
device: ZHADevice;
|
||||
@@ -2,7 +2,7 @@
|
||||
if (navigator.userAgent.indexOf("Android") === -1 &&
|
||||
navigator.userAgent.indexOf("CrOS") === -1) {
|
||||
function _pf(src, type) {
|
||||
var el = document.createElement("link");
|
||||
const el = document.createElement("link");
|
||||
el.rel = "preload";
|
||||
el.as = "font";
|
||||
el.type = "font/woff2";
|
||||
|
||||
@@ -58,13 +58,12 @@
|
||||
window.customPanelJS = "<%= latestCustomPanelJS %>";
|
||||
window.latestJS = true;
|
||||
</script>
|
||||
<script>
|
||||
{% for extra_module in extra_modules -%}
|
||||
import("{{ extra_module }}");
|
||||
<script type="module" crossorigin="use-credentials" src="{{ extra_module }}"></script>
|
||||
{% endfor -%}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
if (!window.latestJS) {
|
||||
window.customPanelJS = "<%= es5CustomPanelJS %>";
|
||||
|
||||
@@ -80,14 +79,11 @@
|
||||
_ls("<%= es5CoreJS %>");
|
||||
_ls("<%= es5AppJS %>");
|
||||
<% } %>
|
||||
{% for extra_script in extra_js_es5 -%}
|
||||
_ls("{{ extra_script }}");
|
||||
{% endfor -%}
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
if (!window.latestJS) {
|
||||
{% for extra_script in extra_js_es5 -%}
|
||||
_ls("{{ extra_script }}");
|
||||
{% endfor -%}
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
{% for extra_url in extra_urls -%}
|
||||
|
||||
@@ -91,20 +91,6 @@ class PartialPanelResolver extends HassRouterPage {
|
||||
|
||||
private _waitForStart = false;
|
||||
|
||||
private _disconnectedPanel?: ChildNode;
|
||||
|
||||
private _hiddenTimeout?: number;
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
|
||||
document.addEventListener(
|
||||
"visibilitychange",
|
||||
() => this._handleVisibilityChange(),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
|
||||
@@ -155,27 +141,6 @@ class PartialPanelResolver extends HassRouterPage {
|
||||
}
|
||||
}
|
||||
|
||||
private _handleVisibilityChange() {
|
||||
if (document.hidden) {
|
||||
this._hiddenTimeout = window.setTimeout(() => {
|
||||
this._hiddenTimeout = undefined;
|
||||
if (this.lastChild) {
|
||||
this._disconnectedPanel = this.lastChild;
|
||||
this.removeChild(this.lastChild);
|
||||
}
|
||||
}, 300000);
|
||||
} else {
|
||||
if (this._hiddenTimeout) {
|
||||
clearTimeout(this._hiddenTimeout);
|
||||
this._hiddenTimeout = undefined;
|
||||
}
|
||||
if (this._disconnectedPanel) {
|
||||
this.appendChild(this._disconnectedPanel);
|
||||
this._disconnectedPanel = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async _updateRoutes(oldPanels?: HomeAssistant["panels"]) {
|
||||
this.routerOptions = getRoutes(this.hass.panels);
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {
|
||||
Auth,
|
||||
createConnection,
|
||||
genClientId,
|
||||
getAuth,
|
||||
subscribeConfig,
|
||||
genClientId,
|
||||
} from "home-assistant-js-websocket";
|
||||
import {
|
||||
customElement,
|
||||
@@ -14,12 +14,12 @@ import {
|
||||
} from "lit-element";
|
||||
import { HASSDomEvent } from "../common/dom/fire_event";
|
||||
import { subscribeOne } from "../common/util/subscribe-one";
|
||||
import { hassUrl, AuthUrlSearchParams } from "../data/auth";
|
||||
import { hassUrl } from "../data/auth";
|
||||
import {
|
||||
fetchOnboardingOverview,
|
||||
OnboardingResponses,
|
||||
OnboardingStep,
|
||||
onboardIntegrationStep,
|
||||
ValidOnboardingStep,
|
||||
} from "../data/onboarding";
|
||||
import { subscribeUser } from "../data/ws-user";
|
||||
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
|
||||
@@ -28,28 +28,19 @@ import { HomeAssistant } from "../types";
|
||||
import { registerServiceWorker } from "../util/register-service-worker";
|
||||
import "./onboarding-create-user";
|
||||
import "./onboarding-loading";
|
||||
import { extractSearchParamsObject } from "../common/url/search-params";
|
||||
|
||||
type OnboardingEvent =
|
||||
| {
|
||||
type: "user";
|
||||
result: OnboardingResponses["user"];
|
||||
}
|
||||
| {
|
||||
type: "core_config";
|
||||
result: OnboardingResponses["core_config"];
|
||||
}
|
||||
| {
|
||||
type: "integration";
|
||||
};
|
||||
interface OnboardingEvent<T extends ValidOnboardingStep> {
|
||||
type: T;
|
||||
result: OnboardingResponses[T];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"onboarding-step": OnboardingEvent;
|
||||
"onboarding-step": OnboardingEvent<ValidOnboardingStep>;
|
||||
}
|
||||
|
||||
interface GlobalEventHandlersEventMap {
|
||||
"onboarding-step": HASSDomEvent<OnboardingEvent>;
|
||||
"onboarding-step": HASSDomEvent<OnboardingEvent<ValidOnboardingStep>>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +150,9 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
}
|
||||
}
|
||||
|
||||
private async _handleStepDone(ev: HASSDomEvent<OnboardingEvent>) {
|
||||
private async _handleStepDone(
|
||||
ev: HASSDomEvent<OnboardingEvent<ValidOnboardingStep>>
|
||||
) {
|
||||
const stepResult = ev.detail;
|
||||
this._steps = this._steps!.map((step) =>
|
||||
step.step === stepResult.type ? { ...step, done: true } : step
|
||||
@@ -183,41 +176,9 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
} else if (stepResult.type === "core_config") {
|
||||
// We do nothing
|
||||
} else if (stepResult.type === "integration") {
|
||||
const result = stepResult.result as OnboardingResponses["integration"];
|
||||
this._loading = true;
|
||||
|
||||
// Determine if oauth redirect has been provided
|
||||
const externalAuthParams = extractSearchParamsObject() as AuthUrlSearchParams;
|
||||
const authParams =
|
||||
externalAuthParams.client_id && externalAuthParams.redirect_uri
|
||||
? externalAuthParams
|
||||
: {
|
||||
client_id: genClientId(),
|
||||
redirect_uri: `${location.protocol}//${location.host}/?auth_callback=1`,
|
||||
state: btoa(
|
||||
JSON.stringify({
|
||||
hassUrl: `${location.protocol}//${location.host}`,
|
||||
clientId: genClientId(),
|
||||
})
|
||||
),
|
||||
};
|
||||
|
||||
let result: OnboardingResponses["integration"];
|
||||
|
||||
try {
|
||||
result = await onboardIntegrationStep(this.hass!, {
|
||||
client_id: authParams.client_id!,
|
||||
redirect_uri: authParams.redirect_uri!,
|
||||
});
|
||||
} catch (err) {
|
||||
this.hass!.connection.close();
|
||||
await this.hass!.auth.revoke();
|
||||
|
||||
alert(`Unable to finish onboarding: ${err.message}`);
|
||||
|
||||
document.location.assign("/?");
|
||||
return;
|
||||
}
|
||||
|
||||
// If we don't close the connection manually, the connection will be
|
||||
// closed when we navigate away from the page. Firefox allows JS to
|
||||
// continue to execute, and so HAWS will automatically reconnect once
|
||||
@@ -230,17 +191,17 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
// Revoke current auth token.
|
||||
await this.hass!.auth.revoke();
|
||||
|
||||
// Build up the url to redirect to
|
||||
let redirectUrl = authParams.redirect_uri!;
|
||||
redirectUrl +=
|
||||
(redirectUrl.includes("?") ? "&" : "?") +
|
||||
`code=${encodeURIComponent(result.auth_code)}`;
|
||||
|
||||
if (authParams.state) {
|
||||
redirectUrl += `&state=${encodeURIComponent(authParams.state)}`;
|
||||
}
|
||||
|
||||
document.location.assign(redirectUrl);
|
||||
const state = btoa(
|
||||
JSON.stringify({
|
||||
hassUrl: `${location.protocol}//${location.host}`,
|
||||
clientId: genClientId(),
|
||||
})
|
||||
);
|
||||
document.location.assign(
|
||||
`/?auth_callback=1&code=${encodeURIComponent(
|
||||
result.auth_code
|
||||
)}&state=${state}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { genClientId } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -20,6 +21,7 @@ import {
|
||||
} from "../data/config_flow";
|
||||
import { DataEntryFlowProgress } from "../data/data_entry_flow";
|
||||
import { domainToName } from "../data/integration";
|
||||
import { onboardIntegrationStep } from "../data/onboarding";
|
||||
import {
|
||||
loadConfigFlowDialog,
|
||||
showConfigFlowDialog,
|
||||
@@ -167,8 +169,12 @@ class OnboardingIntegrations extends LitElement {
|
||||
}
|
||||
|
||||
private async _finish() {
|
||||
const result = await onboardIntegrationStep(this.hass, {
|
||||
client_id: genClientId(),
|
||||
});
|
||||
fireEvent(this, "onboarding-step", {
|
||||
type: "integration",
|
||||
result,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import { customElement, LitElement, property } from "lit-element";
|
||||
import { html } from "lit-html";
|
||||
import { WaitAction } from "../../../../../data/script";
|
||||
@@ -20,7 +19,7 @@ export class HaWaitAction extends LitElement implements ActionElement {
|
||||
const { wait_template, timeout } = this.action;
|
||||
|
||||
return html`
|
||||
<paper-textarea
|
||||
<ha-textarea
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.wait_template.wait_template"
|
||||
)}
|
||||
@@ -28,7 +27,7 @@ export class HaWaitAction extends LitElement implements ActionElement {
|
||||
.value=${wait_template}
|
||||
@value-changed=${this._valueChanged}
|
||||
dir="ltr"
|
||||
></paper-textarea>
|
||||
></ha-textarea>
|
||||
<paper-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.wait_template.timeout"
|
||||
|
||||
+3
-3
@@ -2,7 +2,7 @@ import "@polymer/paper-input/paper-input";
|
||||
import { customElement, html, LitElement, property } from "lit-element";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/entity/ha-entity-picker";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import "../../../../../components/ha-textarea";
|
||||
import { NumericStateCondition } from "../../../../../data/automation";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { handleChangeEvent } from "../ha-automation-condition-row";
|
||||
@@ -45,7 +45,7 @@ export default class HaNumericStateCondition extends LitElement {
|
||||
.value=${below}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
<paper-textarea
|
||||
<ha-textarea
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.numeric_state.value_template"
|
||||
)}
|
||||
@@ -53,7 +53,7 @@ export default class HaNumericStateCondition extends LitElement {
|
||||
.value=${value_template}
|
||||
@value-changed=${this._valueChanged}
|
||||
dir="ltr"
|
||||
></paper-textarea>
|
||||
></ha-textarea>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { customElement, html, LitElement, property } from "lit-element";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import "../../../../../components/ha-textarea";
|
||||
import { TemplateCondition } from "../../../../../data/automation";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { handleChangeEvent } from "../ha-automation-condition-row";
|
||||
@@ -17,7 +17,7 @@ export class HaTemplateCondition extends LitElement {
|
||||
protected render() {
|
||||
const { value_template } = this.condition;
|
||||
return html`
|
||||
<paper-textarea
|
||||
<ha-textarea
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.template.value_template"
|
||||
)}
|
||||
@@ -25,7 +25,7 @@ export class HaTemplateCondition extends LitElement {
|
||||
.value=${value_template}
|
||||
@value-changed=${this._valueChanged}
|
||||
dir="ltr"
|
||||
></paper-textarea>
|
||||
></ha-textarea>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import "@polymer/app-layout/app-header/app-header";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import "../../../components/ha-icon-button";
|
||||
import {
|
||||
css,
|
||||
@@ -118,7 +117,7 @@ export class HaAutomationEditor extends LitElement {
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</paper-input>
|
||||
<paper-textarea
|
||||
<ha-textarea
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.description.label"
|
||||
)}
|
||||
@@ -128,7 +127,7 @@ export class HaAutomationEditor extends LitElement {
|
||||
name="description"
|
||||
.value=${this._config.description}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-textarea>
|
||||
></ha-textarea>
|
||||
</div>
|
||||
${stateObj
|
||||
? html`
|
||||
|
||||
@@ -54,7 +54,9 @@ class HaConfigAutomation extends HassRouterPage {
|
||||
private _getAutomations = memoizeOne(
|
||||
(states: HassEntities): AutomationEntity[] => {
|
||||
return Object.values(states).filter(
|
||||
(entity) => computeStateDomain(entity) === "automation"
|
||||
(entity) =>
|
||||
computeStateDomain(entity) === "automation" &&
|
||||
!entity.attributes.hidden
|
||||
) as AutomationEntity[];
|
||||
}
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@ import "@polymer/paper-input/paper-input";
|
||||
import { customElement, html, LitElement, property } from "lit-element";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/entity/ha-entity-picker";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import "../../../../../components/ha-textarea";
|
||||
import { ForDict, NumericStateTrigger } from "../../../../../data/automation";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { handleChangeEvent } from "../ha-automation-trigger-row";
|
||||
@@ -61,7 +61,7 @@ export default class HaNumericStateTrigger extends LitElement {
|
||||
.value=${below}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
<paper-textarea
|
||||
<ha-textarea
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.numeric_state.value_template"
|
||||
)}
|
||||
@@ -69,7 +69,7 @@ export default class HaNumericStateTrigger extends LitElement {
|
||||
.value=${value_template}
|
||||
@value-changed=${this._valueChanged}
|
||||
dir="ltr"
|
||||
></paper-textarea>
|
||||
></ha-textarea>
|
||||
<paper-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.state.for"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { customElement, html, LitElement, property } from "lit-element";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import "../../../../../components/ha-textarea";
|
||||
import { TemplateTrigger } from "../../../../../data/automation";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { handleChangeEvent } from "../ha-automation-trigger-row";
|
||||
@@ -17,7 +17,7 @@ export class HaTemplateTrigger extends LitElement {
|
||||
protected render() {
|
||||
const { value_template } = this.trigger;
|
||||
return html`
|
||||
<paper-textarea
|
||||
<ha-textarea
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.template.value_template"
|
||||
)}
|
||||
@@ -25,7 +25,7 @@ export class HaTemplateTrigger extends LitElement {
|
||||
.value=${value_template}
|
||||
@value-changed=${this._valueChanged}
|
||||
dir="ltr"
|
||||
></paper-textarea>
|
||||
></ha-textarea>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ import { HomeAssistant } from "../../../types";
|
||||
import "../ha-config-section";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import "./ha-config-navigation";
|
||||
import { mdiCloudLock } from "@mdi/js";
|
||||
|
||||
@customElement("ha-config-dashboard")
|
||||
class HaConfigDashboard extends LitElement {
|
||||
@@ -67,7 +66,7 @@ class HaConfigDashboard extends LitElement {
|
||||
path: "/config/cloud",
|
||||
translationKey: "ui.panel.config.cloud.caption",
|
||||
info: this.cloudStatus,
|
||||
iconPath: mdiCloudLock,
|
||||
icon: "hass:cloud-lock",
|
||||
},
|
||||
]}
|
||||
></ha-config-navigation>
|
||||
@@ -85,36 +84,6 @@ class HaConfigDashboard extends LitElement {
|
||||
</ha-card>
|
||||
`
|
||||
)}
|
||||
${isComponentLoaded(this.hass, "zha")
|
||||
? html`
|
||||
<div class="promo-advanced">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integration_panel_move.missing_zha",
|
||||
"integrations_page",
|
||||
html`<a href="/config/integrations">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integration_panel_move.link_integration_page"
|
||||
)}
|
||||
</a>`
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${isComponentLoaded(this.hass, "zwave")
|
||||
? html`
|
||||
<div class="promo-advanced">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integration_panel_move.missing_zwave",
|
||||
"integrations_page",
|
||||
html`<a href="/config/integrations">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integration_panel_move.link_integration_page"
|
||||
)}
|
||||
</a>`
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${!this.showAdvanced
|
||||
? html`
|
||||
<div class="promo-advanced">
|
||||
|
||||
@@ -38,10 +38,7 @@ class HaConfigNavigation extends LitElement {
|
||||
tabindex="-1"
|
||||
>
|
||||
<paper-icon-item>
|
||||
<ha-svg-icon
|
||||
.path=${page.iconPath}
|
||||
slot="item-icon"
|
||||
></ha-svg-icon>
|
||||
<ha-icon .icon=${page.icon} slot="item-icon"></ha-icon>
|
||||
<paper-item-body two-line>
|
||||
${this.hass.localize(
|
||||
page.translationKey ||
|
||||
@@ -91,7 +88,7 @@ class HaConfigNavigation extends LitElement {
|
||||
display: block;
|
||||
outline: 0;
|
||||
}
|
||||
ha-svg-icon,
|
||||
ha-icon,
|
||||
ha-icon-next {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
+10
-19
@@ -5,17 +5,16 @@ import {
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
css,
|
||||
} from "lit-element";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import { removeMQTTDeviceEntry } from "../../../../../../data/mqtt";
|
||||
import { showConfirmationDialog } from "../../../../../../dialogs/generic/show-dialog-box";
|
||||
import { showMQTTDeviceDebugInfoDialog } from "./show-dialog-mqtt-device-debug-info";
|
||||
import { haStyle } from "../../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../../types";
|
||||
import { DeviceRegistryEntry } from "../../../../data/device_registry";
|
||||
import { removeMQTTDeviceEntry } from "../../../../data/mqtt";
|
||||
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||
import { showMQTTDeviceDebugInfoDialog } from "../../../../dialogs/mqtt-device-debug-info-dialog/show-dialog-mqtt-device-debug-info";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
|
||||
@customElement("ha-device-actions-mqtt")
|
||||
export class HaDeviceActionsMqtt extends LitElement {
|
||||
@customElement("ha-device-card-mqtt")
|
||||
export class HaDeviceCardMqtt extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public device!: DeviceRegistryEntry;
|
||||
@@ -48,15 +47,7 @@ export class HaDeviceActionsMqtt extends LitElement {
|
||||
await showMQTTDeviceDebugInfoDialog(this, { device });
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
`,
|
||||
];
|
||||
static get styles(): CSSResult {
|
||||
return haStyle;
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,6 @@ export class HaDeviceCard extends LitElement {
|
||||
: ""}
|
||||
<slot></slot>
|
||||
</div>
|
||||
<slot name="actions"></slot>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
@@ -101,6 +100,7 @@ export class HaDeviceCard extends LitElement {
|
||||
}
|
||||
ha-card {
|
||||
flex: 1 0 100%;
|
||||
padding-bottom: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
.device {
|
||||
|
||||
-138
@@ -1,138 +0,0 @@
|
||||
import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
css,
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import { haStyle } from "../../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../../types";
|
||||
import {
|
||||
ZHADevice,
|
||||
fetchZHADevice,
|
||||
reconfigureNode,
|
||||
} from "../../../../../../data/zha";
|
||||
import { navigate } from "../../../../../../common/navigate";
|
||||
import { showZHADeviceZigbeeInfoDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-device-zigbee-info";
|
||||
import { showConfirmationDialog } from "../../../../../../dialogs/generic/show-dialog-box";
|
||||
import { showZHAClusterDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-cluster";
|
||||
|
||||
@customElement("ha-device-actions-zha")
|
||||
export class HaDeviceActionsZha extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public device!: DeviceRegistryEntry;
|
||||
|
||||
@property() private _zhaDevice?: ZHADevice;
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
if (changedProperties.has("device")) {
|
||||
const zigbeeConnection = this.device.connections.find(
|
||||
(conn) => conn[0] === "zigbee"
|
||||
);
|
||||
if (!zigbeeConnection) {
|
||||
return;
|
||||
}
|
||||
fetchZHADevice(this.hass, zigbeeConnection[1]).then((device) => {
|
||||
this._zhaDevice = device;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._zhaDevice) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
${this._zhaDevice.device_type !== "Coordinator"
|
||||
? html`
|
||||
<mwc-button @click=${this._onReconfigureNodeClick}>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.reconfigure"
|
||||
)}
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
${this._zhaDevice.power_source === "Mains" &&
|
||||
(this._zhaDevice.device_type === "Router" ||
|
||||
this._zhaDevice.device_type === "Coordinator")
|
||||
? html`
|
||||
<mwc-button @click=${this._onAddDevicesClick}>
|
||||
${this.hass!.localize("ui.dialogs.zha_device_info.buttons.add")}
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
${this._zhaDevice.device_type !== "Coordinator"
|
||||
? html`
|
||||
<mwc-button @click=${this._handleZigbeeInfoClicked}>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.zigbee_information"
|
||||
)}
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._showClustersDialog}>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.clusters"
|
||||
)}
|
||||
</mwc-button>
|
||||
<mwc-button class="warning" @click=${this._removeDevice}>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.remove"
|
||||
)}
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
private async _showClustersDialog(): Promise<void> {
|
||||
await showZHAClusterDialog(this, { device: this._zhaDevice! });
|
||||
}
|
||||
|
||||
private async _onReconfigureNodeClick(): Promise<void> {
|
||||
if (!this.hass) {
|
||||
return;
|
||||
}
|
||||
reconfigureNode(this.hass, this._zhaDevice!.ieee);
|
||||
}
|
||||
|
||||
private _onAddDevicesClick() {
|
||||
navigate(this, "/config/zha/add/" + this._zhaDevice!.ieee);
|
||||
}
|
||||
|
||||
private async _handleZigbeeInfoClicked() {
|
||||
showZHADeviceZigbeeInfoDialog(this, { device: this._zhaDevice! });
|
||||
}
|
||||
|
||||
private async _removeDevice() {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.dialogs.zha_device_info.confirmations.remove"
|
||||
),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.hass.callService("zha", "remove", {
|
||||
ieee_address: this._zhaDevice!.ieee,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
-93
@@ -1,93 +0,0 @@
|
||||
import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
css,
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import { haStyle } from "../../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../../types";
|
||||
import { ZHADevice, fetchZHADevice } from "../../../../../../data/zha";
|
||||
import { formatAsPaddedHex } from "../../../../integrations/integration-panels/zha/functions";
|
||||
|
||||
@customElement("ha-device-info-zha")
|
||||
export class HaDeviceActionsZha extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public device!: DeviceRegistryEntry;
|
||||
|
||||
@property() private _zhaDevice?: ZHADevice;
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
if (changedProperties.has("device")) {
|
||||
const zigbeeConnection = this.device.connections.find(
|
||||
(conn) => conn[0] === "zigbee"
|
||||
);
|
||||
if (!zigbeeConnection) {
|
||||
return;
|
||||
}
|
||||
fetchZHADevice(this.hass, zigbeeConnection[1]).then((device) => {
|
||||
this._zhaDevice = device;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._zhaDevice) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<h4>Zigbee info</h4>
|
||||
<div>IEEE: ${this._zhaDevice.ieee}</div>
|
||||
<div>Nwk: ${formatAsPaddedHex(this._zhaDevice.nwk)}</div>
|
||||
<div>Device Type: ${this._zhaDevice.device_type}</div>
|
||||
<div>
|
||||
LQI:
|
||||
${this._zhaDevice.lqi ||
|
||||
this.hass!.localize("ui.dialogs.zha_device_info.unknown")}
|
||||
</div>
|
||||
<div>
|
||||
RSSI:
|
||||
${this._zhaDevice.rssi ||
|
||||
this.hass!.localize("ui.dialogs.zha_device_info.unknown")}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass!.localize("ui.dialogs.zha_device_info.last_seen")}:
|
||||
${this._zhaDevice.last_seen ||
|
||||
this.hass!.localize("ui.dialogs.zha_device_info.unknown")}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass!.localize("ui.dialogs.zha_device_info.power_source")}:
|
||||
${this._zhaDevice.power_source ||
|
||||
this.hass!.localize("ui.dialogs.zha_device_info.unknown")}
|
||||
</div>
|
||||
${this._zhaDevice.quirk_applied
|
||||
? html`
|
||||
<div>
|
||||
${this.hass!.localize("ui.dialogs.zha_device_info.quirk")}:
|
||||
${this._zhaDevice.quirk_class}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
h4 {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
div {
|
||||
word-break: break-all;
|
||||
margin-top: 2px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
import memoizeOne from "memoize-one";
|
||||
@@ -39,12 +38,13 @@ import "../../../layouts/hass-tabs-subpage";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
import "../ha-config-section";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import "./device-detail/ha-device-card-mqtt";
|
||||
import "./device-detail/ha-device-entities-card";
|
||||
import "./device-detail/ha-device-info-card";
|
||||
import { showDeviceAutomationDialog } from "./device-detail/show-dialog-device-automation";
|
||||
|
||||
export interface EntityRegistryStateEntry extends EntityRegistryEntry {
|
||||
stateName?: string | null;
|
||||
stateName?: string;
|
||||
}
|
||||
|
||||
@customElement("ha-config-device-page")
|
||||
@@ -226,7 +226,16 @@ export class HaConfigDevicePage extends LitElement {
|
||||
.devices=${this.devices}
|
||||
.device=${device}
|
||||
>
|
||||
${this._renderIntegrationInfo(device, integrations)}
|
||||
${
|
||||
integrations.includes("mqtt")
|
||||
? html`
|
||||
<ha-device-card-mqtt
|
||||
.hass=${this.hass}
|
||||
.device=${device}
|
||||
></ha-device-card-mqtt>
|
||||
`
|
||||
: html``
|
||||
}
|
||||
</ha-device-info-card>
|
||||
|
||||
${
|
||||
@@ -430,7 +439,7 @@ export class HaConfigDevicePage extends LitElement {
|
||||
</hass-tabs-subpage> `;
|
||||
}
|
||||
|
||||
private _computeEntityName(entity: EntityRegistryEntry) {
|
||||
private _computeEntityName(entity) {
|
||||
if (entity.name) {
|
||||
return entity.name;
|
||||
}
|
||||
@@ -471,43 +480,6 @@ export class HaConfigDevicePage extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _renderIntegrationInfo(
|
||||
device,
|
||||
integrations: string[]
|
||||
): TemplateResult[] {
|
||||
const templates: TemplateResult[] = [];
|
||||
if (integrations.includes("mqtt")) {
|
||||
import(
|
||||
"./device-detail/integration-elements/mqtt/ha-device-actions-mqtt"
|
||||
);
|
||||
templates.push(html`
|
||||
<div class="card-actions" slot="actions">
|
||||
<ha-device-actions-mqtt
|
||||
.hass=${this.hass}
|
||||
.device=${device}
|
||||
></ha-device-actions-mqtt>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
if (integrations.includes("zha")) {
|
||||
import("./device-detail/integration-elements/zha/ha-device-actions-zha");
|
||||
import("./device-detail/integration-elements/zha/ha-device-info-zha");
|
||||
templates.push(html`
|
||||
<ha-device-info-zha
|
||||
.hass=${this.hass}
|
||||
.device=${device}
|
||||
></ha-device-info-zha>
|
||||
<div class="card-actions" slot="actions">
|
||||
<ha-device-actions-zha
|
||||
.hass=${this.hass}
|
||||
.device=${device}
|
||||
></ha-device-actions-zha>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
return templates;
|
||||
}
|
||||
|
||||
private async _showSettings() {
|
||||
const device = this._device(this.deviceId, this.devices)!;
|
||||
showDeviceRegistryDetailDialog(this, {
|
||||
|
||||
@@ -9,25 +9,6 @@ import "../../layouts/hass-loading-screen";
|
||||
import { HassRouterPage, RouterOptions } from "../../layouts/hass-router-page";
|
||||
import { PageNavigation } from "../../layouts/hass-tabs-subpage";
|
||||
import { HomeAssistant, Route } from "../../types";
|
||||
import {
|
||||
mdiPuzzle,
|
||||
mdiDevices,
|
||||
mdiShape,
|
||||
mdiSofa,
|
||||
mdiRobot,
|
||||
mdiPalette,
|
||||
mdiScriptText,
|
||||
mdiTools,
|
||||
mdiViewDashboard,
|
||||
mdiAccount,
|
||||
mdiMapMarkerRadius,
|
||||
mdiAccountBadgeHorizontal,
|
||||
mdiHomeAssistant,
|
||||
mdiServer,
|
||||
mdiInformation,
|
||||
mdiMathLog,
|
||||
mdiPencil,
|
||||
} from "@mdi/js";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
@@ -42,28 +23,28 @@ export const configSections: { [name: string]: PageNavigation[] } = {
|
||||
component: "integrations",
|
||||
path: "/config/integrations",
|
||||
translationKey: "ui.panel.config.integrations.caption",
|
||||
iconPath: mdiPuzzle,
|
||||
icon: "hass:puzzle",
|
||||
core: true,
|
||||
},
|
||||
{
|
||||
component: "devices",
|
||||
path: "/config/devices",
|
||||
translationKey: "ui.panel.config.devices.caption",
|
||||
iconPath: mdiDevices,
|
||||
icon: "hass:devices",
|
||||
core: true,
|
||||
},
|
||||
{
|
||||
component: "entities",
|
||||
path: "/config/entities",
|
||||
translationKey: "ui.panel.config.entities.caption",
|
||||
iconPath: mdiShape,
|
||||
icon: "hass:shape",
|
||||
core: true,
|
||||
},
|
||||
{
|
||||
component: "areas",
|
||||
path: "/config/areas",
|
||||
translationKey: "ui.panel.config.areas.caption",
|
||||
iconPath: mdiSofa,
|
||||
icon: "hass:sofa",
|
||||
core: true,
|
||||
},
|
||||
],
|
||||
@@ -72,25 +53,25 @@ export const configSections: { [name: string]: PageNavigation[] } = {
|
||||
component: "automation",
|
||||
path: "/config/automation",
|
||||
translationKey: "ui.panel.config.automation.caption",
|
||||
iconPath: mdiRobot,
|
||||
icon: "hass:robot",
|
||||
},
|
||||
{
|
||||
component: "scene",
|
||||
path: "/config/scene",
|
||||
translationKey: "ui.panel.config.scene.caption",
|
||||
iconPath: mdiPalette,
|
||||
icon: "hass:palette",
|
||||
},
|
||||
{
|
||||
component: "script",
|
||||
path: "/config/script",
|
||||
translationKey: "ui.panel.config.script.caption",
|
||||
iconPath: mdiScriptText,
|
||||
icon: "hass:script-text",
|
||||
},
|
||||
{
|
||||
component: "helpers",
|
||||
path: "/config/helpers",
|
||||
translationKey: "ui.panel.config.helpers.caption",
|
||||
iconPath: mdiTools,
|
||||
icon: "hass:tools",
|
||||
core: true,
|
||||
},
|
||||
],
|
||||
@@ -99,7 +80,7 @@ export const configSections: { [name: string]: PageNavigation[] } = {
|
||||
component: "lovelace",
|
||||
path: "/config/lovelace/dashboards",
|
||||
translationKey: "ui.panel.config.lovelace.caption",
|
||||
iconPath: mdiViewDashboard,
|
||||
icon: "hass:view-dashboard",
|
||||
},
|
||||
],
|
||||
persons: [
|
||||
@@ -107,19 +88,19 @@ export const configSections: { [name: string]: PageNavigation[] } = {
|
||||
component: "person",
|
||||
path: "/config/person",
|
||||
translationKey: "ui.panel.config.person.caption",
|
||||
iconPath: mdiAccount,
|
||||
icon: "hass:account",
|
||||
},
|
||||
{
|
||||
component: "zone",
|
||||
path: "/config/zone",
|
||||
translationKey: "ui.panel.config.zone.caption",
|
||||
iconPath: mdiMapMarkerRadius,
|
||||
icon: "hass:map-marker-radius",
|
||||
},
|
||||
{
|
||||
component: "users",
|
||||
path: "/config/users",
|
||||
translationKey: "ui.panel.config.users.caption",
|
||||
iconPath: mdiAccountBadgeHorizontal,
|
||||
icon: "hass:account-badge-horizontal",
|
||||
core: true,
|
||||
},
|
||||
],
|
||||
@@ -128,41 +109,39 @@ export const configSections: { [name: string]: PageNavigation[] } = {
|
||||
component: "core",
|
||||
path: "/config/core",
|
||||
translationKey: "ui.panel.config.core.caption",
|
||||
iconPath: mdiHomeAssistant,
|
||||
icon: "hass:home-assistant",
|
||||
core: true,
|
||||
},
|
||||
{
|
||||
component: "server_control",
|
||||
path: "/config/server_control",
|
||||
translationKey: "ui.panel.config.server_control.caption",
|
||||
iconPath: mdiServer,
|
||||
icon: "hass:server",
|
||||
core: true,
|
||||
},
|
||||
{
|
||||
component: "logs",
|
||||
path: "/config/logs",
|
||||
translationKey: "ui.panel.config.logs.caption",
|
||||
iconPath: mdiMathLog,
|
||||
core: true,
|
||||
},
|
||||
{
|
||||
component: "info",
|
||||
path: "/config/info",
|
||||
translationKey: "ui.panel.config.info.caption",
|
||||
iconPath: mdiInformation,
|
||||
core: true,
|
||||
},
|
||||
],
|
||||
advanced: [
|
||||
{
|
||||
component: "customize",
|
||||
path: "/config/customize",
|
||||
translationKey: "ui.panel.config.customize.caption",
|
||||
iconPath: mdiPencil,
|
||||
icon: "hass:pencil",
|
||||
core: true,
|
||||
advancedOnly: true,
|
||||
},
|
||||
],
|
||||
other: [
|
||||
{
|
||||
component: "zha",
|
||||
path: "/config/zha",
|
||||
translationKey: "component.zha.title",
|
||||
icon: "hass:zigbee",
|
||||
},
|
||||
{
|
||||
component: "zwave",
|
||||
path: "/config/zwave",
|
||||
translationKey: "component.zwave.title",
|
||||
icon: "hass:z-wave",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@customElement("ha-panel-config")
|
||||
@@ -218,20 +197,6 @@ class HaPanelConfig extends HassRouterPage {
|
||||
/* webpackChunkName: "panel-config-server-control" */ "./server_control/ha-config-server-control"
|
||||
),
|
||||
},
|
||||
logs: {
|
||||
tag: "ha-config-logs",
|
||||
load: () =>
|
||||
import(
|
||||
/* webpackChunkName: "panel-config-logs" */ "./logs/ha-config-logs"
|
||||
),
|
||||
},
|
||||
info: {
|
||||
tag: "ha-config-info",
|
||||
load: () =>
|
||||
import(
|
||||
/* webpackChunkName: "panel-config-info" */ "./info/ha-config-info"
|
||||
),
|
||||
},
|
||||
customize: {
|
||||
tag: "ha-config-customize",
|
||||
load: () =>
|
||||
@@ -313,21 +278,14 @@ class HaPanelConfig extends HassRouterPage {
|
||||
tag: "zha-config-dashboard-router",
|
||||
load: () =>
|
||||
import(
|
||||
/* webpackChunkName: "panel-config-zha" */ "./integrations/integration-panels/zha/zha-config-dashboard-router"
|
||||
/* webpackChunkName: "panel-config-zha" */ "./zha/zha-config-dashboard-router"
|
||||
),
|
||||
},
|
||||
zwave: {
|
||||
tag: "ha-config-zwave",
|
||||
load: () =>
|
||||
import(
|
||||
/* webpackChunkName: "panel-config-zwave" */ "./integrations/integration-panels/zwave/ha-config-zwave"
|
||||
),
|
||||
},
|
||||
mqtt: {
|
||||
tag: "mqtt-config-panel",
|
||||
load: () =>
|
||||
import(
|
||||
/* webpackChunkName: "panel-config-mqtt" */ "./integrations/integration-panels/mqtt/mqtt-config-panel"
|
||||
/* webpackChunkName: "panel-config-zwave" */ "./zwave/ha-config-zwave"
|
||||
),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,209 +0,0 @@
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
import "./integrations-card";
|
||||
import "./system-health-card";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import "../../../layouts/hass-tabs-subpage";
|
||||
|
||||
const JS_TYPE = __BUILD__;
|
||||
const JS_VERSION = __VERSION__;
|
||||
|
||||
class HaConfigInfo extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public narrow!: boolean;
|
||||
|
||||
@property() public isWide!: boolean;
|
||||
|
||||
@property() public showAdvanced!: boolean;
|
||||
|
||||
@property() public route!: Route;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const hass = this.hass;
|
||||
const customUiList: Array<{ name: string; url: string; version: string }> =
|
||||
(window as any).CUSTOM_UI_LIST || [];
|
||||
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
back-path="/config"
|
||||
.route=${this.route}
|
||||
.tabs=${configSections.general}
|
||||
>
|
||||
<div class="about">
|
||||
<a
|
||||
href="https://www.home-assistant.io"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
><img
|
||||
src="/static/icons/favicon-192x192.png"
|
||||
height="192"
|
||||
alt="${this.hass.localize(
|
||||
"ui.panel.config.info.home_assistant_logo"
|
||||
)}"
|
||||
/></a>
|
||||
<br />
|
||||
<h2>Home Assistant ${hass.connection.haVersion}</h2>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.info.path_configuration",
|
||||
"path",
|
||||
hass.config.config_dir
|
||||
)}
|
||||
</p>
|
||||
<p class="develop">
|
||||
<a
|
||||
href="https://www.home-assistant.io/developers/credits/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.info.developed_by")}
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
${this.hass.localize("ui.panel.config.info.license")}<br />
|
||||
${this.hass.localize("ui.panel.config.info.source")}
|
||||
<a
|
||||
href="https://github.com/home-assistant/core"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>${this.hass.localize("ui.panel.config.info.server")}</a
|
||||
>
|
||||
—
|
||||
<a
|
||||
href="https://github.com/home-assistant/frontend"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>${this.hass.localize("ui.panel.config.info.frontend")}</a
|
||||
>
|
||||
</p>
|
||||
<p>
|
||||
${this.hass.localize("ui.panel.config.info.built_using")}
|
||||
<a href="https://www.python.org" target="_blank" rel="noreferrer"
|
||||
>Python 3</a
|
||||
>,
|
||||
<a
|
||||
href="https://www.polymer-project.org"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>Polymer</a
|
||||
>, ${this.hass.localize("ui.panel.config.info.icons_by")}
|
||||
<a
|
||||
href="https://www.google.com/design/icons/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>Google</a
|
||||
>
|
||||
${this.hass.localize("ui.common.and")}
|
||||
<a
|
||||
href="https://MaterialDesignIcons.com"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>MaterialDesignIcons.com</a
|
||||
>.
|
||||
</p>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.info.frontend_version",
|
||||
"version",
|
||||
JS_VERSION,
|
||||
"type",
|
||||
JS_TYPE
|
||||
)}
|
||||
${customUiList.length > 0
|
||||
? html`
|
||||
<div>
|
||||
${this.hass.localize("ui.panel.config.info.custom_uis")}
|
||||
${customUiList.map(
|
||||
(item) => html`
|
||||
<div>
|
||||
<a href="${item.url}" target="_blank"> ${item.name}</a
|
||||
>: ${item.version}
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</p>
|
||||
</div>
|
||||
<div class="content">
|
||||
<system-health-card .hass=${this.hass}></system-health-card>
|
||||
<integrations-card .hass=${this.hass}></integrations-card>
|
||||
</div>
|
||||
</hass-tabs-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps): void {
|
||||
super.firstUpdated(changedProps);
|
||||
|
||||
// Legacy custom UI can be slow to register, give them time.
|
||||
const customUI = ((window as any).CUSTOM_UI_LIST || []).length;
|
||||
setTimeout(() => {
|
||||
if (((window as any).CUSTOM_UI_LIST || []).length !== customUI.length) {
|
||||
this.requestUpdate();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
:host {
|
||||
-ms-user-select: initial;
|
||||
-webkit-user-select: initial;
|
||||
-moz-user-select: initial;
|
||||
}
|
||||
|
||||
.content {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.about {
|
||||
text-align: center;
|
||||
line-height: 2em;
|
||||
}
|
||||
|
||||
.version {
|
||||
@apply --paper-font-headline;
|
||||
}
|
||||
|
||||
.develop {
|
||||
@apply --paper-font-subhead;
|
||||
}
|
||||
|
||||
.about a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
system-health-card,
|
||||
integrations-card {
|
||||
display: block;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-config-info": HaConfigInfo;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-config-info", HaConfigInfo);
|
||||
@@ -299,7 +299,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
${this._showIgnored
|
||||
? ignoredConfigEntries.map(
|
||||
(item: ConfigEntryExtended) => html`
|
||||
<ha-card outlined class="ignored">
|
||||
<ha-card class="ignored">
|
||||
<div class="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.ignore.ignored"
|
||||
@@ -335,7 +335,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
${configEntriesInProgress.length
|
||||
? configEntriesInProgress.map(
|
||||
(flow: DataEntryFlowProgressExtended) => html`
|
||||
<ha-card outlined class="discovered">
|
||||
<ha-card class="discovered">
|
||||
<div class="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.discovered"
|
||||
@@ -396,7 +396,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
)
|
||||
: !this._configEntries.length
|
||||
? html`
|
||||
<ha-card outlined>
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<h1>
|
||||
${this.hass.localize("ui.panel.config.integrations.none")}
|
||||
@@ -613,7 +613,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.discovered {
|
||||
--ha-card-border-color: var(--primary-color);
|
||||
border: 1px solid var(--primary-color);
|
||||
}
|
||||
.discovered .header {
|
||||
background: var(--primary-color);
|
||||
@@ -622,7 +622,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
text-align: center;
|
||||
}
|
||||
.ignored {
|
||||
--ha-card-border-color: var(--light-theme-disabled-color);
|
||||
border: 1px solid var(--light-theme-disabled-color);
|
||||
}
|
||||
.ignored img {
|
||||
filter: grayscale(1);
|
||||
|
||||
@@ -45,21 +45,6 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
const integrationsWithPanel = {
|
||||
mqtt: {
|
||||
buttonLocalizeKey: "ui.panel.config.mqtt.button",
|
||||
path: "/config/mqtt",
|
||||
},
|
||||
zha: {
|
||||
buttonLocalizeKey: "ui.panel.config.zha.button",
|
||||
path: "/config/zha/dashboard",
|
||||
},
|
||||
zwave: {
|
||||
buttonLocalizeKey: "ui.panel.config.zwave.button",
|
||||
path: "/config/zwave",
|
||||
},
|
||||
};
|
||||
|
||||
@customElement("ha-integration-card")
|
||||
export class HaIntegrationCard extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@@ -91,7 +76,7 @@ export class HaIntegrationCard extends LitElement {
|
||||
|
||||
private _renderGroupedIntegration(): TemplateResult {
|
||||
return html`
|
||||
<ha-card outlined class="group">
|
||||
<ha-card class="group">
|
||||
<div class="group-header">
|
||||
<img
|
||||
src="https://brands.home-assistant.io/${this.domain}/icon.png"
|
||||
@@ -127,7 +112,6 @@ export class HaIntegrationCard extends LitElement {
|
||||
const entities = this._getEntities(item);
|
||||
return html`
|
||||
<ha-card
|
||||
outlined
|
||||
class="single integration"
|
||||
.configEntry=${item}
|
||||
.id=${item.entry_id}
|
||||
@@ -195,24 +179,13 @@ export class HaIntegrationCard extends LitElement {
|
||||
"ui.panel.config.integrations.config_entry.rename"
|
||||
)}</mwc-button
|
||||
>
|
||||
${item.domain in integrationsWithPanel
|
||||
? html`<a
|
||||
href=${`${
|
||||
integrationsWithPanel[item.domain].path
|
||||
}?config_entry=${item.entry_id}`}
|
||||
><mwc-button>
|
||||
${this.hass.localize(
|
||||
integrationsWithPanel[item.domain].buttonLocalizeKey
|
||||
)}
|
||||
</mwc-button></a
|
||||
>`
|
||||
: item.supports_options
|
||||
${item.supports_options
|
||||
? html`
|
||||
<mwc-button @click=${this._showOptions}>
|
||||
${this.hass.localize(
|
||||
<mwc-button @click=${this._showOptions}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.options"
|
||||
)}
|
||||
</mwc-button>
|
||||
)}</mwc-button
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
import "../../../../../components/ha-code-editor";
|
||||
import { createCloseHeading } from "../../../../../components/ha-dialog";
|
||||
import { haStyleDialog } from "../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { ZHADeviceZigbeeInfoDialogParams } from "./show-dialog-zha-device-zigbee-info";
|
||||
import {
|
||||
ZHADevice,
|
||||
Cluster,
|
||||
ZHAGroup,
|
||||
fetchBindableDevices,
|
||||
fetchGroups,
|
||||
} from "../../../../../data/zha";
|
||||
import { ZHAClusterSelectedParams } from "./types";
|
||||
import "./zha-cluster-attributes";
|
||||
import "./zha-cluster-commands";
|
||||
import "./zha-clusters";
|
||||
import "./zha-device-binding";
|
||||
import "./zha-group-binding";
|
||||
import { HASSDomEvent } from "../../../../../common/dom/fire_event";
|
||||
import { sortZHADevices, sortZHAGroups } from "./functions";
|
||||
|
||||
@customElement("dialog-zha-cluster")
|
||||
class DialogZHACluster extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() private _device?: ZHADevice;
|
||||
|
||||
@property() private _selectedCluster?: Cluster;
|
||||
|
||||
@property() private _bindableDevices: ZHADevice[] = [];
|
||||
|
||||
@property() private _groups: ZHAGroup[] = [];
|
||||
|
||||
public async showDialog(
|
||||
params: ZHADeviceZigbeeInfoDialogParams
|
||||
): Promise<void> {
|
||||
this._device = params.device;
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
super.update(changedProperties);
|
||||
if (changedProperties.has("_device")) {
|
||||
this._fetchData();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._device) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
hideActions
|
||||
@closing="${this._close}"
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this.hass.localize("ui.panel.config.zha.clusters.header")
|
||||
)}
|
||||
>
|
||||
<zha-clusters
|
||||
.hass=${this.hass}
|
||||
.selectedDevice="${this._device}"
|
||||
@zha-cluster-selected="${this._onClusterSelected}"
|
||||
></zha-clusters>
|
||||
${this._selectedCluster
|
||||
? html`
|
||||
<zha-cluster-attributes
|
||||
.hass=${this.hass}
|
||||
.selectedNode="${this._device}"
|
||||
.selectedCluster="${this._selectedCluster}"
|
||||
></zha-cluster-attributes>
|
||||
<zha-cluster-commands
|
||||
.hass=${this.hass}
|
||||
.selectedNode="${this._device}"
|
||||
.selectedCluster="${this._selectedCluster}"
|
||||
></zha-cluster-commands>
|
||||
`
|
||||
: ""}
|
||||
${this._bindableDevices.length > 0
|
||||
? html`
|
||||
<zha-device-binding-control
|
||||
.hass=${this.hass}
|
||||
.selectedDevice="${this._device}"
|
||||
.bindableDevices="${this._bindableDevices}"
|
||||
></zha-device-binding-control>
|
||||
`
|
||||
: ""}
|
||||
${this._device && this._groups.length > 0
|
||||
? html`
|
||||
<zha-group-binding-control
|
||||
.hass=${this.hass}
|
||||
.selectedDevice="${this._device}"
|
||||
.groups="${this._groups}"
|
||||
></zha-group-binding-control>
|
||||
`
|
||||
: ""}
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _onClusterSelected(
|
||||
selectedClusterEvent: HASSDomEvent<ZHAClusterSelectedParams>
|
||||
): void {
|
||||
this._selectedCluster = selectedClusterEvent.detail.cluster;
|
||||
}
|
||||
|
||||
private _close(): void {
|
||||
this._device = undefined;
|
||||
}
|
||||
|
||||
private async _fetchData(): Promise<void> {
|
||||
if (this._device && this.hass) {
|
||||
this._bindableDevices =
|
||||
this._device && this._device.device_type !== "Coordinator"
|
||||
? (await fetchBindableDevices(this.hass, this._device.ieee)).sort(
|
||||
sortZHADevices
|
||||
)
|
||||
: [];
|
||||
this._groups = (await fetchGroups(this.hass!)).sort(sortZHAGroups);
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return haStyleDialog;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-zha-cluster": DialogZHACluster;
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { ZHADevice } from "../../../../../data/zha";
|
||||
|
||||
export interface ZHAClusterDialogParams {
|
||||
device: ZHADevice;
|
||||
}
|
||||
|
||||
export const loadZHAClusterDialog = () =>
|
||||
import(
|
||||
/* webpackChunkName: "dialog-zha-device-zigbee-info" */ "./dialog-zha-cluster"
|
||||
);
|
||||
|
||||
export const showZHAClusterDialog = (
|
||||
element: HTMLElement,
|
||||
params: ZHAClusterDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-zha-cluster",
|
||||
dialogImport: loadZHAClusterDialog,
|
||||
dialogParams: params,
|
||||
});
|
||||
};
|
||||
@@ -1,132 +0,0 @@
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import "@material/mwc-fab";
|
||||
import {
|
||||
css,
|
||||
CSSResultArray,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-icon-next";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant, Route } from "../../../../../types";
|
||||
import "../../../ha-config-section";
|
||||
import { mdiNetwork, mdiFolderMultipleOutline, mdiPlus } from "@mdi/js";
|
||||
import "../../../../../layouts/hass-tabs-subpage";
|
||||
import type { PageNavigation } from "../../../../../layouts/hass-tabs-subpage";
|
||||
import { computeRTL } from "../../../../../common/util/compute_rtl";
|
||||
|
||||
export const zhaTabs: PageNavigation[] = [
|
||||
{
|
||||
translationKey: "ui.panel.config.zha.network.caption",
|
||||
path: `/config/zha/dashboard`,
|
||||
iconPath: mdiNetwork,
|
||||
},
|
||||
{
|
||||
translationKey: "ui.panel.config.zha.groups.caption",
|
||||
path: `/config/zha/groups`,
|
||||
iconPath: mdiFolderMultipleOutline,
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("zha-config-dashboard")
|
||||
class ZHAConfigDashboard extends LitElement {
|
||||
@property({ type: Object }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Object }) public route!: Route;
|
||||
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
@property({ type: Boolean }) public isWide!: boolean;
|
||||
|
||||
@property() public configEntryId?: string;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.route=${this.route}
|
||||
.tabs=${zhaTabs}
|
||||
back-path="/config/integrations"
|
||||
>
|
||||
<ha-card header="Zigbee Network">
|
||||
<div class="card-content">
|
||||
Network info/settings for specific config entry
|
||||
</div>
|
||||
${this.configEntryId
|
||||
? html`<div class="card-actions">
|
||||
<a
|
||||
href="${`/config/devices/dashboard?historyBack=1&config_entry=${this.configEntryId}`}"
|
||||
>
|
||||
<mwc-button>Devices</mwc-button>
|
||||
</a>
|
||||
<a
|
||||
href="${`/config/entities/dashboard?historyBack=1&config_entry=${this.configEntryId}`}"
|
||||
>
|
||||
<mwc-button>Entities</mwc-button>
|
||||
</a>
|
||||
</div>`
|
||||
: ""}
|
||||
</ha-card>
|
||||
<a href="/config/zha/add">
|
||||
<mwc-fab
|
||||
?is-wide=${this.isWide}
|
||||
?narrow=${this.narrow}
|
||||
title=${this.hass.localize("ui.panel.config.zha.add_device")}
|
||||
?rtl=${computeRTL(this.hass)}
|
||||
>
|
||||
<ha-svg-icon slot="icon" path=${mdiPlus}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</a>
|
||||
</hass-tabs-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultArray {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
ha-card {
|
||||
margin: auto;
|
||||
margin-top: 16px;
|
||||
max-width: 500px;
|
||||
}
|
||||
mwc-fab {
|
||||
position: fixed;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
mwc-fab[is-wide] {
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
mwc-fab[narrow] {
|
||||
bottom: 84px;
|
||||
}
|
||||
mwc-fab[rtl] {
|
||||
right: auto;
|
||||
left: 16px;
|
||||
}
|
||||
|
||||
mwc-fab[rtl][is-wide] {
|
||||
bottom: 24px;
|
||||
right: auto;
|
||||
left: 24px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"zha-config-dashboard": ZHAConfigDashboard;
|
||||
}
|
||||
}
|
||||
@@ -1,243 +0,0 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { computeStateName } from "../../../../../common/entity/compute_state_name";
|
||||
import "../../../../../components/buttons/ha-call-service-button";
|
||||
import "../../../../../components/entity/state-badge";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-service-description";
|
||||
import { updateDeviceRegistryEntry } from "../../../../../data/device_registry";
|
||||
import { ZHADevice } from "../../../../../data/zha";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import "../../../../../components/ha-area-picker";
|
||||
import { showAlertDialog } from "../../../../../dialogs/generic/show-dialog-box";
|
||||
import { SubscribeMixin } from "../../../../../mixins/subscribe-mixin";
|
||||
import {
|
||||
subscribeEntityRegistry,
|
||||
EntityRegistryEntry,
|
||||
updateEntityRegistryEntry,
|
||||
} from "../../../../../data/entity_registry";
|
||||
import { createValidEntityId } from "../../../../../common/entity/valid_entity_id";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { EntityRegistryStateEntry } from "../../../devices/ha-config-device-page";
|
||||
import { compare } from "../../../../../common/string/compare";
|
||||
import { getIeeeTail } from "./functions";
|
||||
|
||||
@customElement("zha-device-card")
|
||||
class ZHADeviceCard extends SubscribeMixin(LitElement) {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public device?: ZHADevice;
|
||||
|
||||
@property({ type: Boolean }) public narrow?: boolean;
|
||||
|
||||
@property() private _entities: EntityRegistryEntry[] = [];
|
||||
|
||||
private _deviceEntities = memoizeOne(
|
||||
(
|
||||
deviceId: string,
|
||||
entities: EntityRegistryEntry[]
|
||||
): EntityRegistryStateEntry[] =>
|
||||
entities
|
||||
.filter((entity) => entity.device_id === deviceId)
|
||||
.map((entity) => {
|
||||
return { ...entity, stateName: this._computeEntityName(entity) };
|
||||
})
|
||||
.sort((ent1, ent2) =>
|
||||
compare(
|
||||
ent1.stateName || `zzz${ent1.entity_id}`,
|
||||
ent2.stateName || `zzz${ent2.entity_id}`
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeEntityRegistry(this.hass.connection, (entities) => {
|
||||
this._entities = entities;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass || !this.device) {
|
||||
return html``;
|
||||
}
|
||||
const entities = this._deviceEntities(
|
||||
this.device.device_reg_id,
|
||||
this._entities
|
||||
);
|
||||
|
||||
return html`
|
||||
<ha-card .header=${this.device.user_given_name || this.device.name}>
|
||||
<div class="card-content">
|
||||
<div class="info">
|
||||
<div class="model">${this.device.model}</div>
|
||||
<div class="manuf">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.zha_device_info.manuf",
|
||||
"manufacturer",
|
||||
this.device.manufacturer
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="device-entities">
|
||||
${entities.map(
|
||||
(entity) => html`
|
||||
<state-badge
|
||||
@click="${this._openMoreInfo}"
|
||||
.title=${entity.stateName!}
|
||||
.stateObj="${this.hass!.states[entity.entity_id]}"
|
||||
slot="item-icon"
|
||||
></state-badge>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
<paper-input
|
||||
type="string"
|
||||
@change=${this._rename}
|
||||
.value=${this.device.user_given_name || this.device.name}
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.zha_device_info.zha_device_card.device_name_placeholder"
|
||||
)}
|
||||
></paper-input>
|
||||
<ha-area-picker
|
||||
.hass=${this.hass}
|
||||
.device=${this.device.device_reg_id}
|
||||
@value-changed=${this._areaPicked}
|
||||
></ha-area-picker>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _rename(event): Promise<void> {
|
||||
if (!this.hass || !this.device) {
|
||||
return;
|
||||
}
|
||||
const device = this.device;
|
||||
|
||||
const oldDeviceName = device.user_given_name || device.name;
|
||||
const newDeviceName = event.target.value;
|
||||
this.device.user_given_name = newDeviceName;
|
||||
await updateDeviceRegistryEntry(this.hass, device.device_reg_id, {
|
||||
name_by_user: newDeviceName,
|
||||
});
|
||||
|
||||
if (!oldDeviceName || !newDeviceName || oldDeviceName === newDeviceName) {
|
||||
return;
|
||||
}
|
||||
const entities = this._deviceEntities(device.device_reg_id, this._entities);
|
||||
|
||||
const oldDeviceEntityId = createValidEntityId(oldDeviceName);
|
||||
const newDeviceEntityId = createValidEntityId(newDeviceName);
|
||||
const ieeeTail = getIeeeTail(device.ieee);
|
||||
|
||||
const updateProms = entities.map((entity) => {
|
||||
const name = entity.name || entity.stateName;
|
||||
let newEntityId: string | null = null;
|
||||
let newName: string | null = null;
|
||||
|
||||
if (name && name.includes(oldDeviceName)) {
|
||||
newName = name.replace(` ${ieeeTail}`, "");
|
||||
newName = newName.replace(oldDeviceName, newDeviceName);
|
||||
newEntityId = entity.entity_id.replace(`_${ieeeTail}`, "");
|
||||
newEntityId = newEntityId.replace(oldDeviceEntityId, newDeviceEntityId);
|
||||
}
|
||||
|
||||
if (!newName && !newEntityId) {
|
||||
return new Promise((resolve) => resolve());
|
||||
}
|
||||
|
||||
return updateEntityRegistryEntry(this.hass!, entity.entity_id, {
|
||||
name: newName || name,
|
||||
disabled_by: entity.disabled_by,
|
||||
new_entity_id: newEntityId || entity.entity_id,
|
||||
});
|
||||
});
|
||||
await Promise.all(updateProms);
|
||||
}
|
||||
|
||||
private _openMoreInfo(ev: MouseEvent): void {
|
||||
fireEvent(this, "hass-more-info", {
|
||||
entityId: (ev.currentTarget as any).stateObj.entity_id,
|
||||
});
|
||||
}
|
||||
|
||||
private _computeEntityName(entity: EntityRegistryEntry): string {
|
||||
if (this.hass.states[entity.entity_id]) {
|
||||
return computeStateName(this.hass.states[entity.entity_id]);
|
||||
}
|
||||
return entity.name;
|
||||
}
|
||||
|
||||
private async _areaPicked(ev: CustomEvent) {
|
||||
const picker = ev.currentTarget as any;
|
||||
|
||||
const area = ev.detail.value;
|
||||
try {
|
||||
await updateDeviceRegistryEntry(this.hass, this.device!.device_reg_id, {
|
||||
area_id: area,
|
||||
});
|
||||
this.device!.area_id = area;
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.error_saving_area",
|
||||
"error",
|
||||
err.message
|
||||
),
|
||||
});
|
||||
picker.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.device-entities {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 4px;
|
||||
justify-content: left;
|
||||
min-height: 48px;
|
||||
}
|
||||
.device {
|
||||
width: 30%;
|
||||
}
|
||||
.device .name {
|
||||
font-weight: bold;
|
||||
}
|
||||
.device .manuf {
|
||||
color: var(--secondary-text-color);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.extra-info {
|
||||
margin-top: 8px;
|
||||
}
|
||||
state-badge {
|
||||
cursor: pointer;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"zha-device-card": ZHADeviceCard;
|
||||
}
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
import "@material/mwc-button";
|
||||
import "@material/mwc-fab";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import memoizeOne from "memoize-one";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
CSSResultArray,
|
||||
css,
|
||||
} from "lit-element";
|
||||
import { HASSDomEvent } from "../../../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../../../common/navigate";
|
||||
import {
|
||||
DataTableColumnContainer,
|
||||
RowClickedEvent,
|
||||
} from "../../../../../components/data-table/ha-data-table";
|
||||
import { fetchGroups, ZHAGroup, ZHADevice } from "../../../../../data/zha";
|
||||
import "../../../../../layouts/hass-tabs-subpage-data-table";
|
||||
import { HomeAssistant, Route } from "../../../../../types";
|
||||
import { sortZHAGroups, formatAsPaddedHex } from "./functions";
|
||||
import { zhaTabs } from "./zha-config-dashboard";
|
||||
import { computeRTL } from "../../../../../common/util/compute_rtl";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
|
||||
export interface GroupRowData extends ZHAGroup {
|
||||
group?: GroupRowData;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
@customElement("zha-groups-dashboard")
|
||||
export class ZHAGroupsDashboard extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Object }) public route!: Route;
|
||||
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
@property({ type: Boolean }) public isWide!: boolean;
|
||||
|
||||
@property() public _groups: ZHAGroup[] = [];
|
||||
|
||||
private _firstUpdatedCalled = false;
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
if (this.hass && this._firstUpdatedCalled) {
|
||||
this._fetchGroups();
|
||||
}
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues): void {
|
||||
super.firstUpdated(changedProperties);
|
||||
if (this.hass) {
|
||||
this._fetchGroups();
|
||||
}
|
||||
this._firstUpdatedCalled = true;
|
||||
}
|
||||
|
||||
private _formattedGroups = memoizeOne((groups: ZHAGroup[]) => {
|
||||
let outputGroups: GroupRowData[] = groups;
|
||||
|
||||
outputGroups = outputGroups.map((group) => {
|
||||
return {
|
||||
...group,
|
||||
id: String(group.group_id),
|
||||
};
|
||||
});
|
||||
|
||||
return outputGroups;
|
||||
});
|
||||
|
||||
private _columns = memoizeOne(
|
||||
(narrow: boolean): DataTableColumnContainer =>
|
||||
narrow
|
||||
? {
|
||||
name: {
|
||||
title: "Group",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
direction: "asc",
|
||||
grows: true,
|
||||
},
|
||||
}
|
||||
: {
|
||||
name: {
|
||||
title: this.hass.localize("ui.panel.config.zha.groups.groups"),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
direction: "asc",
|
||||
grows: true,
|
||||
},
|
||||
group_id: {
|
||||
title: this.hass.localize("ui.panel.config.zha.groups.group_id"),
|
||||
type: "numeric",
|
||||
width: "15%",
|
||||
template: (groupId: number) => {
|
||||
return html` ${formatAsPaddedHex(groupId)} `;
|
||||
},
|
||||
sortable: true,
|
||||
},
|
||||
members: {
|
||||
title: this.hass.localize("ui.panel.config.zha.groups.members"),
|
||||
type: "numeric",
|
||||
width: "15%",
|
||||
template: (members: ZHADevice[]) => {
|
||||
return html` ${members.length} `;
|
||||
},
|
||||
sortable: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<hass-tabs-subpage-data-table
|
||||
.tabs=${zhaTabs}
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.route=${this.route}
|
||||
.columns=${this._columns(this.narrow)}
|
||||
.data=${this._formattedGroups(this._groups)}
|
||||
@row-click=${this._handleRowClicked}
|
||||
>
|
||||
</hass-tabs-subpage-data-table>
|
||||
<a href="/config/zha/group-add">
|
||||
<mwc-fab
|
||||
?is-wide=${this.isWide}
|
||||
?narrow=${this.narrow}
|
||||
title=${this.hass!.localize("ui.panel.config.zha.groups.add_group")}
|
||||
?rtl=${computeRTL(this.hass)}
|
||||
>
|
||||
<ha-svg-icon slot="icon" path=${mdiPlus}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</a>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _fetchGroups() {
|
||||
this._groups = (await fetchGroups(this.hass!)).sort(sortZHAGroups);
|
||||
}
|
||||
|
||||
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
|
||||
const groupId = ev.detail.id;
|
||||
navigate(this, `/config/zha/group/${groupId}`);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultArray {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
mwc-fab {
|
||||
position: fixed;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
mwc-fab[is-wide] {
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
mwc-fab[narrow] {
|
||||
bottom: 84px;
|
||||
}
|
||||
mwc-fab[rtl] {
|
||||
right: auto;
|
||||
left: 16px;
|
||||
}
|
||||
|
||||
mwc-fab[rtl][is-wide] {
|
||||
bottom: 24px;
|
||||
right: auto;
|
||||
left: 24px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"zha-groups-dashboard": ZHAGroupsDashboard;
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,8 @@ class HaConfigScene extends HassRouterPage {
|
||||
|
||||
private _getScenes = memoizeOne((states: HassEntities): SceneEntity[] => {
|
||||
return Object.values(states).filter(
|
||||
(entity) => computeStateDomain(entity) === "scene"
|
||||
(entity) =>
|
||||
computeStateDomain(entity) === "scene" && !entity.attributes.hidden
|
||||
) as SceneEntity[];
|
||||
});
|
||||
|
||||
|
||||
@@ -53,7 +53,8 @@ class HaConfigScript extends HassRouterPage {
|
||||
|
||||
private _getScripts = memoizeOne((states: HassEntities): ScriptEntity[] => {
|
||||
return Object.values(states).filter(
|
||||
(entity) => computeStateDomain(entity) === "script"
|
||||
(entity) =>
|
||||
computeStateDomain(entity) === "script" && !entity.attributes.hidden
|
||||
) as ScriptEntity[];
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import "../../../components/buttons/ha-call-service-button";
|
||||
import "../../../components/ha-card";
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
import "../../../styles/polymer-ha-style";
|
||||
import "../ha-config-section";
|
||||
|
||||
/*
|
||||
* @appliesMixin LocalizeMixin
|
||||
*/
|
||||
class HaConfigSectionServerControl extends LocalizeMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="iron-flex ha-style">
|
||||
.validate-container {
|
||||
@apply --layout-vertical;
|
||||
@apply --layout-center-center;
|
||||
height: 140px;
|
||||
}
|
||||
|
||||
.validate-result {
|
||||
color: var(--google-green-500);
|
||||
font-weight: 500;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.config-invalid {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.config-invalid .text {
|
||||
color: var(--google-red-500);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.config-invalid mwc-button {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.validate-log {
|
||||
white-space: pre-wrap;
|
||||
direction: ltr;
|
||||
}
|
||||
</style>
|
||||
<ha-config-section is-wide="[[isWide]]">
|
||||
<span slot="header"
|
||||
>[[localize('ui.panel.config.server_control.caption')]]</span
|
||||
>
|
||||
<span slot="introduction"
|
||||
>[[localize('ui.panel.config.server_control.description')]]</span
|
||||
>
|
||||
|
||||
<template is="dom-if" if="[[showAdvanced]]">
|
||||
<ha-card
|
||||
header="[[localize('ui.panel.config.server_control.section.validation.heading')]]"
|
||||
>
|
||||
<div class="card-content">
|
||||
[[localize('ui.panel.config.server_control.section.validation.introduction')]]
|
||||
<template is="dom-if" if="[[!validateLog]]">
|
||||
<div class="validate-container">
|
||||
<template is="dom-if" if="[[!validating]]">
|
||||
<template is="dom-if" if="[[isValid]]">
|
||||
<div class="validate-result" id="result">
|
||||
[[localize('ui.panel.config.server_control.section.validation.valid')]]
|
||||
</div>
|
||||
</template>
|
||||
<mwc-button raised="" on-click="validateConfig">
|
||||
[[localize('ui.panel.config.server_control.section.validation.check_config')]]
|
||||
</mwc-button>
|
||||
</template>
|
||||
<template is="dom-if" if="[[validating]]">
|
||||
<paper-spinner active=""></paper-spinner>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template is="dom-if" if="[[validateLog]]">
|
||||
<div class="config-invalid">
|
||||
<span class="text">
|
||||
[[localize('ui.panel.config.server_control.section.validation.invalid')]]
|
||||
</span>
|
||||
<mwc-button raised="" on-click="validateConfig">
|
||||
[[localize('ui.panel.config.server_control.section.validation.check_config')]]
|
||||
</mwc-button>
|
||||
</div>
|
||||
<div id="configLog" class="validate-log">[[validateLog]]</div>
|
||||
</template>
|
||||
</div>
|
||||
</ha-card>
|
||||
|
||||
<ha-card
|
||||
header="[[localize('ui.panel.config.server_control.section.reloading.heading')]]"
|
||||
>
|
||||
<div class="card-content">
|
||||
[[localize('ui.panel.config.server_control.section.reloading.introduction')]]
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-call-service-button
|
||||
hass="[[hass]]"
|
||||
domain="homeassistant"
|
||||
service="reload_core_config"
|
||||
>[[localize('ui.panel.config.server_control.section.reloading.core')]]
|
||||
</ha-call-service-button>
|
||||
</div>
|
||||
<template is="dom-if" if="[[groupLoaded(hass)]]">
|
||||
<div class="card-actions">
|
||||
<ha-call-service-button
|
||||
hass="[[hass]]"
|
||||
domain="group"
|
||||
service="reload"
|
||||
>[[localize('ui.panel.config.server_control.section.reloading.group')]]
|
||||
</ha-call-service-button>
|
||||
</div>
|
||||
</template>
|
||||
<template is="dom-if" if="[[automationLoaded(hass)]]">
|
||||
<div class="card-actions">
|
||||
<ha-call-service-button
|
||||
hass="[[hass]]"
|
||||
domain="automation"
|
||||
service="reload"
|
||||
>[[localize('ui.panel.config.server_control.section.reloading.automation')]]
|
||||
</ha-call-service-button>
|
||||
</div>
|
||||
</template>
|
||||
<template is="dom-if" if="[[scriptLoaded(hass)]]">
|
||||
<div class="card-actions">
|
||||
<ha-call-service-button
|
||||
hass="[[hass]]"
|
||||
domain="script"
|
||||
service="reload"
|
||||
>[[localize('ui.panel.config.server_control.section.reloading.script')]]
|
||||
</ha-call-service-button>
|
||||
</div>
|
||||
</template>
|
||||
<template is="dom-if" if="[[sceneLoaded(hass)]]">
|
||||
<div class="card-actions">
|
||||
<ha-call-service-button
|
||||
hass="[[hass]]"
|
||||
domain="scene"
|
||||
service="reload"
|
||||
>[[localize('ui.panel.config.server_control.section.reloading.scene')]]
|
||||
</ha-call-service-button>
|
||||
</div>
|
||||
</template>
|
||||
<template is="dom-if" if="[[personLoaded(hass)]]">
|
||||
<div class="card-actions">
|
||||
<ha-call-service-button
|
||||
hass="[[hass]]"
|
||||
domain="person"
|
||||
service="reload"
|
||||
>[[localize('ui.panel.config.server_control.section.reloading.person')]]
|
||||
</ha-call-service-button>
|
||||
</div>
|
||||
</template>
|
||||
<div class="card-actions">
|
||||
<ha-call-service-button
|
||||
hass="[[hass]]"
|
||||
domain="zone"
|
||||
service="reload"
|
||||
>[[localize('ui.panel.config.server_control.section.reloading.zone')]]
|
||||
</ha-call-service-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
</template>
|
||||
<ha-card
|
||||
header="[[localize('ui.panel.config.server_control.section.server_management.heading')]]"
|
||||
>
|
||||
<div class="card-content">
|
||||
[[localize('ui.panel.config.server_control.section.server_management.introduction')]]
|
||||
</div>
|
||||
<div class="card-actions warning">
|
||||
<ha-call-service-button
|
||||
class="warning"
|
||||
hass="[[hass]]"
|
||||
domain="homeassistant"
|
||||
service="restart"
|
||||
confirmation="[[localize('ui.panel.config.server_control.section.server_management.confirm_restart')]]"
|
||||
>[[localize('ui.panel.config.server_control.section.server_management.restart')]]
|
||||
</ha-call-service-button>
|
||||
<ha-call-service-button
|
||||
class="warning"
|
||||
hass="[[hass]]"
|
||||
domain="homeassistant"
|
||||
service="stop"
|
||||
confirmation="[[localize('ui.panel.config.server_control.section.server_management.confirm_stop')]]"
|
||||
>[[localize('ui.panel.config.server_control.section.server_management.stop')]]
|
||||
</ha-call-service-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
isWide: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
validating: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
isValid: {
|
||||
type: Boolean,
|
||||
value: null,
|
||||
},
|
||||
|
||||
validateLog: {
|
||||
type: String,
|
||||
value: "",
|
||||
},
|
||||
|
||||
showAdvanced: Boolean,
|
||||
};
|
||||
}
|
||||
|
||||
groupLoaded(hass) {
|
||||
return isComponentLoaded(hass, "group");
|
||||
}
|
||||
|
||||
automationLoaded(hass) {
|
||||
return isComponentLoaded(hass, "automation");
|
||||
}
|
||||
|
||||
scriptLoaded(hass) {
|
||||
return isComponentLoaded(hass, "script");
|
||||
}
|
||||
|
||||
sceneLoaded(hass) {
|
||||
return isComponentLoaded(hass, "scene");
|
||||
}
|
||||
|
||||
personLoaded(hass) {
|
||||
return isComponentLoaded(hass, "person");
|
||||
}
|
||||
|
||||
validateConfig() {
|
||||
this.validating = true;
|
||||
this.validateLog = "";
|
||||
this.isValid = null;
|
||||
|
||||
this.hass.callApi("POST", "config/core/check_config").then((result) => {
|
||||
this.validating = false;
|
||||
this.isValid = result.result === "valid";
|
||||
|
||||
if (!this.isValid) {
|
||||
this.validateLog = result.errors;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(
|
||||
"ha-config-section-server-control",
|
||||
HaConfigSectionServerControl
|
||||
);
|
||||
@@ -0,0 +1,72 @@
|
||||
import "@polymer/app-layout/app-header/app-header";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "../../../layouts/hass-tabs-subpage";
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
import "../../../styles/polymer-ha-style";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import "./ha-config-section-server-control";
|
||||
|
||||
/*
|
||||
* @appliesMixin LocalizeMixin
|
||||
*/
|
||||
class HaConfigServerControl extends LocalizeMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="iron-flex ha-style">
|
||||
.content {
|
||||
padding-bottom: 32px;
|
||||
}
|
||||
|
||||
.border {
|
||||
margin: 32px auto 0;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
||||
max-width: 1040px;
|
||||
}
|
||||
|
||||
.narrow .border {
|
||||
max-width: 640px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<hass-tabs-subpage
|
||||
hass="[[hass]]"
|
||||
narrow="[[narrow]]"
|
||||
route="[[route]]"
|
||||
back-path="/config"
|
||||
tabs="[[_computeTabs()]]"
|
||||
show-advanced="[[showAdvanced]]"
|
||||
>
|
||||
<div class$="[[computeClasses(isWide)]]">
|
||||
<ha-config-section-server-control
|
||||
is-wide="[[isWide]]"
|
||||
show-advanced="[[showAdvanced]]"
|
||||
hass="[[hass]]"
|
||||
></ha-config-section-server-control>
|
||||
</div>
|
||||
</hass-tabs-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
isWide: Boolean,
|
||||
narrow: Boolean,
|
||||
route: Object,
|
||||
showAdvanced: Boolean,
|
||||
};
|
||||
}
|
||||
|
||||
_computeTabs() {
|
||||
return configSections.general;
|
||||
}
|
||||
|
||||
computeClasses(isWide) {
|
||||
return isWide ? "content" : "content narrow";
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-config-server-control", HaConfigServerControl);
|
||||
@@ -1,269 +0,0 @@
|
||||
import "@polymer/app-layout/app-header/app-header";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import "../../../layouts/hass-tabs-subpage";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import {
|
||||
LitElement,
|
||||
property,
|
||||
customElement,
|
||||
html,
|
||||
css,
|
||||
CSSResult,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import "../../../components/buttons/ha-call-service-button";
|
||||
import "../../../components/ha-card";
|
||||
import "../ha-config-section";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { checkCoreConfig } from "../../../data/core";
|
||||
|
||||
const reloadableDomains = [
|
||||
"group",
|
||||
"automation",
|
||||
"script",
|
||||
"scene",
|
||||
"person",
|
||||
"zone",
|
||||
"input_boolean",
|
||||
"input_text",
|
||||
"input_number",
|
||||
"input_datetime",
|
||||
"input_select",
|
||||
];
|
||||
|
||||
@customElement("ha-config-server-control")
|
||||
export class HaConfigServerControl extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public isWide!: boolean;
|
||||
|
||||
@property() public narrow!: boolean;
|
||||
|
||||
@property() public route!: Route;
|
||||
|
||||
@property() public showAdvanced!: boolean;
|
||||
|
||||
@property() private _validating = false;
|
||||
|
||||
private _validateLog = "";
|
||||
|
||||
private _isValid: boolean | null = null;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.route=${this.route}
|
||||
back-path="/config"
|
||||
.tabs=${configSections.general}
|
||||
.showAdvanced=${this.showAdvanced}
|
||||
>
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<span slot="header"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.server_control.caption"
|
||||
)}</span
|
||||
>
|
||||
<span slot="introduction"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.server_control.description"
|
||||
)}</span
|
||||
>
|
||||
|
||||
${this.showAdvanced
|
||||
? html` <ha-card
|
||||
header=${this.hass.localize(
|
||||
"ui.panel.config.server_control.section.validation.heading"
|
||||
)}
|
||||
>
|
||||
<div class="card-content">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.server_control.section.validation.introduction"
|
||||
)}
|
||||
${!this._validateLog
|
||||
? html`
|
||||
<div
|
||||
class="validate-container layout vertical center-center"
|
||||
>
|
||||
${!this._validating
|
||||
? html`
|
||||
${this._isValid
|
||||
? html` <div
|
||||
class="validate-result"
|
||||
id="result"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.server_control.section.validation.valid"
|
||||
)}
|
||||
</div>`
|
||||
: ""}
|
||||
<mwc-button
|
||||
raised
|
||||
@click=${this._validateConfig}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.server_control.section.validation.check_config"
|
||||
)}
|
||||
</mwc-button>
|
||||
`
|
||||
: html` <paper-spinner active></paper-spinner> `}
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<div class="config-invalid">
|
||||
<span class="text">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.server_control.section.validation.invalid"
|
||||
)}
|
||||
</span>
|
||||
<mwc-button raised @click=${this._validateConfig}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.server_control.section.validation.check_config"
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
<div id="configLog" class="validate-log">
|
||||
${this._validateLog}
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
</ha-card>`
|
||||
: ""}
|
||||
|
||||
<ha-card
|
||||
header=${this.hass.localize(
|
||||
"ui.panel.config.server_control.section.server_management.heading"
|
||||
)}
|
||||
>
|
||||
<div class="card-content">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.server_control.section.server_management.introduction"
|
||||
)}
|
||||
</div>
|
||||
<div class="card-actions warning">
|
||||
<ha-call-service-button
|
||||
class="warning"
|
||||
.hass=${this.hass}
|
||||
domain="homeassistant"
|
||||
service="restart"
|
||||
.confirmation=${this.hass.localize(
|
||||
"ui.panel.config.server_control.section.server_management.confirm_restart"
|
||||
)}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.server_control.section.server_management.restart"
|
||||
)}
|
||||
</ha-call-service-button>
|
||||
<ha-call-service-button
|
||||
class="warning"
|
||||
.hass=${this.hass}
|
||||
domain="homeassistant"
|
||||
service="stop"
|
||||
confirmation=${this.hass.localize(
|
||||
"ui.panel.config.server_control.section.server_management.confirm_stop"
|
||||
)}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.server_control.section.server_management.stop"
|
||||
)}
|
||||
</ha-call-service-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
|
||||
${this.showAdvanced
|
||||
? html`
|
||||
<ha-card
|
||||
header=${this.hass.localize(
|
||||
"ui.panel.config.server_control.section.reloading.heading"
|
||||
)}
|
||||
>
|
||||
<div class="card-content">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.server_control.section.reloading.introduction"
|
||||
)}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-call-service-button
|
||||
.hass=${this.hass}
|
||||
domain="homeassistant"
|
||||
service="reload_core_config"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.server_control.section.reloading.core"
|
||||
)}
|
||||
</ha-call-service-button>
|
||||
</div>
|
||||
${reloadableDomains.map((domain) =>
|
||||
isComponentLoaded(this.hass, domain)
|
||||
? html`<div class="card-actions">
|
||||
<ha-call-service-button
|
||||
.hass=${this.hass}
|
||||
.domain=${domain}
|
||||
service="reload"
|
||||
>${this.hass.localize(
|
||||
`ui.panel.config.server_control.section.reloading.${domain}`
|
||||
)}
|
||||
</ha-call-service-button>
|
||||
</div>`
|
||||
: ""
|
||||
)}
|
||||
</ha-card>
|
||||
`
|
||||
: ""}
|
||||
</ha-config-section>
|
||||
</hass-tabs-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _validateConfig() {
|
||||
this._validating = true;
|
||||
this._validateLog = "";
|
||||
this._isValid = null;
|
||||
|
||||
const configCheck = await checkCoreConfig(this.hass);
|
||||
this._validating = false;
|
||||
this._isValid = configCheck.result === "valid";
|
||||
|
||||
if (configCheck.errors) {
|
||||
this._validateLog = configCheck.errors;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.validate-container {
|
||||
height: 140px;
|
||||
}
|
||||
|
||||
.validate-result {
|
||||
color: var(--google-green-500);
|
||||
font-weight: 500;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.config-invalid {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.config-invalid .text {
|
||||
color: var(--google-red-500);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.config-invalid mwc-button {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.validate-log {
|
||||
white-space: pre-wrap;
|
||||
direction: ltr;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
+1
-4
@@ -1,4 +1,4 @@
|
||||
import { Cluster, ZHADevice, ZHAGroup } from "../../../../../data/zha";
|
||||
import { Cluster, ZHADevice, ZHAGroup } from "../../../data/zha";
|
||||
|
||||
export const formatAsPaddedHex = (value: string | number): string => {
|
||||
let hex = value;
|
||||
@@ -8,9 +8,6 @@ export const formatAsPaddedHex = (value: string | number): string => {
|
||||
return "0x" + hex.toString(16).padStart(4, "0");
|
||||
};
|
||||
|
||||
export const getIeeeTail = (ieee: string) =>
|
||||
ieee.split(":").slice(-4).reverse().join("");
|
||||
|
||||
export const sortZHADevices = (a: ZHADevice, b: ZHADevice): number => {
|
||||
const nameA = a.user_given_name ? a.user_given_name : a.name;
|
||||
const nameb = b.user_given_name ? b.user_given_name : b.name;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { Cluster, ZHADevice } from "../../../../../data/zha";
|
||||
import { Cluster, ZHADevice } from "../../../data/zha";
|
||||
|
||||
export interface PickerTarget extends EventTarget {
|
||||
selected: number;
|
||||
+87
-99
@@ -1,5 +1,5 @@
|
||||
import "@material/mwc-button";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "@polymer/paper-spinner/paper-spinner";
|
||||
import {
|
||||
css,
|
||||
@@ -9,24 +9,19 @@ import {
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
import "../../../../../components/ha-service-description";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import { ZHADevice } from "../../../../../data/zha";
|
||||
import "../../../../../layouts/hass-tabs-subpage";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../../../types";
|
||||
import "../../../components/ha-service-description";
|
||||
import "../../../components/ha-textarea";
|
||||
import { ZHADevice } from "../../../data/zha";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
import "./zha-device-card";
|
||||
import { zhaTabs } from "./zha-config-dashboard";
|
||||
import { IronAutogrowTextareaElement } from "@polymer/iron-autogrow-textarea";
|
||||
|
||||
@customElement("zha-add-devices-page")
|
||||
class ZHAAddDevicesPage extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public narrow?: boolean;
|
||||
|
||||
@property() public isWide?: boolean;
|
||||
|
||||
@property() public route?: Route;
|
||||
@@ -41,8 +36,6 @@ class ZHAAddDevicesPage extends LitElement {
|
||||
|
||||
@property() private _showHelp = false;
|
||||
|
||||
@property() private _showLogs = false;
|
||||
|
||||
private _ieeeAddress?: string;
|
||||
|
||||
private _addDevicesTimeoutHandle: any = undefined;
|
||||
@@ -67,63 +60,58 @@ class ZHAAddDevicesPage extends LitElement {
|
||||
this._formattedEvents = "";
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
if (
|
||||
changedProps.has("hass") &&
|
||||
!this._active &&
|
||||
!changedProps.get("hass")
|
||||
) {
|
||||
this._subscribe();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.route=${this.route}
|
||||
.tabs=${zhaTabs}
|
||||
<hass-subpage
|
||||
header="${this.hass!.localize(
|
||||
"ui.panel.config.zha.add_device_page.header"
|
||||
)}"
|
||||
>
|
||||
<mwc-button slot="toolbar-icon" @click=${this._toggleLogs}
|
||||
>${this._showLogs ? "Hide logs" : "Show logs"}</mwc-button
|
||||
>
|
||||
<div class="searching">
|
||||
${this._active
|
||||
? html`
|
||||
<h1>
|
||||
${this._active
|
||||
? html`
|
||||
<h2>
|
||||
<paper-spinner
|
||||
?active="${this._active}"
|
||||
alt="Searching"
|
||||
></paper-spinner>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.add_device_page.spinner"
|
||||
)}
|
||||
</h2>
|
||||
`
|
||||
: html`
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._subscribe} class="search-button">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.add_device_page.spinner"
|
||||
"ui.panel.config.zha.add_device_page.search_again"
|
||||
)}
|
||||
</h1>
|
||||
<paper-spinner active alt="Searching"></paper-spinner>
|
||||
`
|
||||
: html`
|
||||
<div>
|
||||
<mwc-button @click=${this._subscribe} class="search-button">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.add_device_page.search_again"
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
</mwc-button>
|
||||
<ha-icon-button
|
||||
class="toggle-help-icon"
|
||||
@click="${this._onHelpTap}"
|
||||
icon="hass:help-circle"
|
||||
></ha-icon-button>
|
||||
${this._showHelp
|
||||
? html`
|
||||
<ha-service-description
|
||||
.hass=${this.hass}
|
||||
domain="zha"
|
||||
service="permit"
|
||||
class="help-text"
|
||||
></ha-service-description>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`}
|
||||
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
||||
<div class="content-header"></div>
|
||||
<div class="content">
|
||||
${this._discoveredDevices.length < 1
|
||||
? html`
|
||||
<div class="discovery-text">
|
||||
<h4>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.add_device_page.pairing_mode"
|
||||
)}
|
||||
</h4>
|
||||
<h4>
|
||||
${this.hass!.localize(
|
||||
this._active
|
||||
? "ui.panel.config.zha.add_device_page.discovered_text"
|
||||
: "ui.panel.config.zha.add_device_page.no_devices_found"
|
||||
"ui.panel.config.zha.add_device_page.discovery_text"
|
||||
)}
|
||||
</h4>
|
||||
</div>
|
||||
@@ -135,38 +123,27 @@ class ZHAAddDevicesPage extends LitElement {
|
||||
class="card"
|
||||
.hass=${this.hass}
|
||||
.device=${device}
|
||||
.narrow=${this.narrow}
|
||||
.narrow=${!this.isWide}
|
||||
.showHelp=${this._showHelp}
|
||||
.showActions=${!this._active}
|
||||
.showEntityDetail=${false}
|
||||
></zha-device-card>
|
||||
`
|
||||
)}
|
||||
`}
|
||||
</div>
|
||||
${this._showLogs
|
||||
? html`<paper-textarea
|
||||
readonly
|
||||
max-rows="10"
|
||||
class="log"
|
||||
value="${this._formattedEvents}"
|
||||
>
|
||||
</paper-textarea>`
|
||||
: ""}
|
||||
</hass-tabs-subpage>
|
||||
<ha-textarea class="events" value="${this._formattedEvents}">
|
||||
</ha-textarea>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
private _toggleLogs() {
|
||||
this._showLogs = !this._showLogs;
|
||||
}
|
||||
|
||||
private _handleMessage(message: any): void {
|
||||
if (message.type === "log_output") {
|
||||
this._formattedEvents += message.log_entry.message + "\n";
|
||||
if (this.shadowRoot) {
|
||||
const paperTextArea = this.shadowRoot.querySelector("paper-textarea");
|
||||
if (paperTextArea) {
|
||||
const textArea = (paperTextArea.inputElement as IronAutogrowTextareaElement)
|
||||
.textarea;
|
||||
const textArea = this.shadowRoot.querySelector("ha-textarea");
|
||||
if (textArea) {
|
||||
textArea.scrollTop = textArea.scrollHeight;
|
||||
}
|
||||
}
|
||||
@@ -188,58 +165,69 @@ class ZHAAddDevicesPage extends LitElement {
|
||||
}
|
||||
|
||||
private _subscribe(): void {
|
||||
if (!this.hass) {
|
||||
return;
|
||||
}
|
||||
this._active = true;
|
||||
const data: any = { type: "zha/devices/permit" };
|
||||
if (this._ieeeAddress) {
|
||||
data.ieee = this._ieeeAddress;
|
||||
}
|
||||
this._subscribed = this.hass.connection.subscribeMessage(
|
||||
this._subscribed = this.hass!.connection.subscribeMessage(
|
||||
(message) => this._handleMessage(message),
|
||||
data
|
||||
);
|
||||
this._active = true;
|
||||
this._addDevicesTimeoutHandle = setTimeout(
|
||||
() => this._unsubscribe(),
|
||||
120000
|
||||
);
|
||||
}
|
||||
|
||||
private _onHelpTap(): void {
|
||||
this._showHelp = !this._showHelp;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.discovery-text {
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
.discovery-text,
|
||||
.content-header {
|
||||
margin: 16px;
|
||||
}
|
||||
.content {
|
||||
border-top: 1px solid var(--light-primary-color);
|
||||
min-height: 500px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 4px;
|
||||
justify-content: center;
|
||||
justify-content: left;
|
||||
overflow: scroll;
|
||||
}
|
||||
.error {
|
||||
color: var(--google-red-500);
|
||||
}
|
||||
paper-spinner {
|
||||
padding: 20px;
|
||||
display: none;
|
||||
margin-right: 20px;
|
||||
margin-left: 16px;
|
||||
}
|
||||
.searching {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
paper-spinner[active] {
|
||||
display: block;
|
||||
float: left;
|
||||
margin-right: 20px;
|
||||
margin-left: 16px;
|
||||
}
|
||||
.card {
|
||||
margin: 8px;
|
||||
margin-left: 16px;
|
||||
margin-right: 16px;
|
||||
margin-bottom: 0px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.log {
|
||||
padding: 16px;
|
||||
.events {
|
||||
margin: 16px;
|
||||
border-top: 1px solid var(--light-primary-color);
|
||||
padding-top: 16px;
|
||||
min-height: 200px;
|
||||
max-height: 200px;
|
||||
overflow: scroll;
|
||||
}
|
||||
.toggle-help-icon {
|
||||
position: absolute;
|
||||
+9
-9
@@ -12,20 +12,20 @@ import {
|
||||
PropertyValues,
|
||||
query,
|
||||
} from "lit-element";
|
||||
import type { HASSDomEvent } from "../../../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../../../common/navigate";
|
||||
import type { SelectionChangedEvent } from "../../../../../components/data-table/ha-data-table";
|
||||
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import type { SelectionChangedEvent } from "../../../components/data-table/ha-data-table";
|
||||
import {
|
||||
addGroup,
|
||||
fetchGroupableDevices,
|
||||
ZHAGroup,
|
||||
ZHADeviceEndpoint,
|
||||
} from "../../../../../data/zha";
|
||||
import "../../../../../layouts/hass-error-screen";
|
||||
import "../../../../../layouts/hass-subpage";
|
||||
import type { PolymerChangedEvent } from "../../../../../polymer-types";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import "../../../ha-config-section";
|
||||
} from "../../../data/zha";
|
||||
import "../../../layouts/hass-error-screen";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import type { PolymerChangedEvent } from "../../../polymer-types";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "../ha-config-section";
|
||||
import "./zha-device-endpoint-data-table";
|
||||
import type { ZHADeviceEndpointDataTable } from "./zha-device-endpoint-data-table";
|
||||
|
||||
+8
-8
@@ -1,6 +1,6 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
@@ -13,9 +13,9 @@ import {
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../../../components/buttons/ha-call-service-button";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-service-description";
|
||||
import "../../../components/buttons/ha-call-service-button";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-service-description";
|
||||
import {
|
||||
Attribute,
|
||||
Cluster,
|
||||
@@ -23,10 +23,10 @@ import {
|
||||
ReadAttributeServiceData,
|
||||
readAttributeValue,
|
||||
ZHADevice,
|
||||
} from "../../../../../data/zha";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import "../../../ha-config-section";
|
||||
} from "../../../data/zha";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import "../ha-config-section";
|
||||
import { formatAsPaddedHex } from "./functions";
|
||||
import {
|
||||
ChangeEvent,
|
||||
+8
-8
@@ -1,5 +1,5 @@
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
@@ -12,18 +12,18 @@ import {
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../../../components/buttons/ha-call-service-button";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-service-description";
|
||||
import "../../../components/buttons/ha-call-service-button";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-service-description";
|
||||
import {
|
||||
Cluster,
|
||||
Command,
|
||||
fetchCommandsForCluster,
|
||||
ZHADevice,
|
||||
} from "../../../../../data/zha";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import "../../../ha-config-section";
|
||||
} from "../../../data/zha";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import "../ha-config-section";
|
||||
import { formatAsPaddedHex } from "./functions";
|
||||
import {
|
||||
ChangeEvent,
|
||||
+5
-5
@@ -7,14 +7,14 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
import "../../../../../components/data-table/ha-data-table";
|
||||
import "../../../components/data-table/ha-data-table";
|
||||
import type {
|
||||
DataTableColumnContainer,
|
||||
HaDataTable,
|
||||
} from "../../../../../components/data-table/ha-data-table";
|
||||
import "../../../../../components/entity/ha-state-icon";
|
||||
import type { Cluster } from "../../../../../data/zha";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
} from "../../../components/data-table/ha-data-table";
|
||||
import "../../../components/entity/ha-state-icon";
|
||||
import type { Cluster } from "../../../data/zha";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { formatAsPaddedHex } from "./functions";
|
||||
|
||||
export interface ClusterRowData extends Cluster {
|
||||
+12
-13
@@ -1,5 +1,5 @@
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import {
|
||||
@@ -11,18 +11,14 @@ import {
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/buttons/ha-call-service-button";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-service-description";
|
||||
import {
|
||||
Cluster,
|
||||
fetchClustersForZhaNode,
|
||||
ZHADevice,
|
||||
} from "../../../../../data/zha";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import "../../../ha-config-section";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/buttons/ha-call-service-button";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-service-description";
|
||||
import { Cluster, fetchClustersForZhaNode, ZHADevice } from "../../../data/zha";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import "../ha-config-section";
|
||||
import { computeClusterKey } from "./functions";
|
||||
import { ItemSelectedEvent } from "./types";
|
||||
|
||||
@@ -64,6 +60,9 @@ export class ZHAClusters extends LitElement {
|
||||
return html`
|
||||
<ha-config-section .isWide="${this.isWide}">
|
||||
<div class="header" slot="header">
|
||||
<span>
|
||||
${this.hass!.localize("ui.panel.config.zha.clusters.header")}
|
||||
</span>
|
||||
<ha-icon-button
|
||||
class="toggle-help-icon"
|
||||
@click="${this._onHelpTap}"
|
||||
+9
-20
@@ -2,9 +2,8 @@ import { customElement, property } from "lit-element";
|
||||
import {
|
||||
HassRouterPage,
|
||||
RouterOptions,
|
||||
} from "../../../../../layouts/hass-router-page";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { navigate } from "../../../../../common/navigate";
|
||||
} from "../../../layouts/hass-router-page";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
|
||||
@customElement("zha-config-dashboard-router")
|
||||
class ZHAConfigDashboardRouter extends HassRouterPage {
|
||||
@@ -14,10 +13,6 @@ class ZHAConfigDashboardRouter extends HassRouterPage {
|
||||
|
||||
@property() public narrow!: boolean;
|
||||
|
||||
private _configEntry = new URLSearchParams(window.location.search).get(
|
||||
"config_entry"
|
||||
);
|
||||
|
||||
protected routerOptions: RouterOptions = {
|
||||
defaultPage: "dashboard",
|
||||
showLoading: true,
|
||||
@@ -29,6 +24,13 @@ class ZHAConfigDashboardRouter extends HassRouterPage {
|
||||
/* webpackChunkName: "zha-config-dashboard" */ "./zha-config-dashboard"
|
||||
),
|
||||
},
|
||||
device: {
|
||||
tag: "zha-device-page",
|
||||
load: () =>
|
||||
import(
|
||||
/* webpackChunkName: "zha-devices-page" */ "./zha-device-page"
|
||||
),
|
||||
},
|
||||
add: {
|
||||
tag: "zha-add-devices-page",
|
||||
load: () =>
|
||||
@@ -63,24 +65,11 @@ class ZHAConfigDashboardRouter extends HassRouterPage {
|
||||
el.hass = this.hass;
|
||||
el.isWide = this.isWide;
|
||||
el.narrow = this.narrow;
|
||||
el.configEntryId = this._configEntry;
|
||||
if (this._currentPage === "group") {
|
||||
el.groupId = this.routeTail.path.substr(1);
|
||||
} else if (this._currentPage === "device") {
|
||||
el.ieee = this.routeTail.path.substr(1);
|
||||
}
|
||||
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
if (this._configEntry && !searchParams.has("config_entry")) {
|
||||
searchParams.append("config_entry", this._configEntry);
|
||||
navigate(
|
||||
this,
|
||||
`${this.routeTail.prefix}${
|
||||
this.routeTail.path
|
||||
}?${searchParams.toString()}`,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import {
|
||||
css,
|
||||
CSSResultArray,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import "../../../components/data-table/ha-data-table";
|
||||
import type {
|
||||
DataTableColumnContainer,
|
||||
RowClickedEvent,
|
||||
DataTableRowData,
|
||||
} from "../../../components/data-table/ha-data-table";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-next";
|
||||
import { fetchDevices } from "../../../data/zha";
|
||||
import type { ZHADevice } from "../../../data/zha";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant, Route } from "../../../types";
|
||||
import "../ha-config-section";
|
||||
import { formatAsPaddedHex, sortZHADevices } from "./functions";
|
||||
|
||||
export interface DeviceRowData extends DataTableRowData {
|
||||
device?: DeviceRowData;
|
||||
}
|
||||
|
||||
@customElement("zha-config-dashboard")
|
||||
class ZHAConfigDashboard extends LitElement {
|
||||
@property({ type: Object }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Object }) public route!: Route;
|
||||
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
@property({ type: Boolean }) public isWide!: boolean;
|
||||
|
||||
@property() private _devices: ZHADevice[] = [];
|
||||
|
||||
private pages: string[] = ["add", "groups"];
|
||||
|
||||
private _firstUpdatedCalled = false;
|
||||
|
||||
private _memoizeDevices = memoizeOne((devices: ZHADevice[]) => {
|
||||
let outputDevices: DeviceRowData[] = devices;
|
||||
|
||||
outputDevices = outputDevices.map((device) => {
|
||||
return {
|
||||
...device,
|
||||
name: device.user_given_name ? device.user_given_name : device.name,
|
||||
nwk: formatAsPaddedHex(device.nwk),
|
||||
};
|
||||
});
|
||||
|
||||
return outputDevices;
|
||||
});
|
||||
|
||||
private _columns = memoizeOne(
|
||||
(narrow: boolean): DataTableColumnContainer =>
|
||||
narrow
|
||||
? {
|
||||
name: {
|
||||
title: "Devices",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
direction: "asc",
|
||||
grows: true,
|
||||
},
|
||||
}
|
||||
: {
|
||||
name: {
|
||||
title: "Name",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
direction: "asc",
|
||||
grows: true,
|
||||
},
|
||||
nwk: {
|
||||
title: "Nwk",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
width: "15%",
|
||||
},
|
||||
ieee: {
|
||||
title: "IEEE",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
width: "30%",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
if (this.hass && this._firstUpdatedCalled) {
|
||||
this._fetchDevices();
|
||||
}
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues): void {
|
||||
super.firstUpdated(changedProperties);
|
||||
if (this.hass) {
|
||||
this._fetchDevices();
|
||||
}
|
||||
this._firstUpdatedCalled = true;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<hass-subpage .header=${this.hass.localize("component.zha.title")}>
|
||||
<ha-config-section .narrow=${this.narrow} .isWide=${this.isWide}>
|
||||
<div slot="header">
|
||||
${this.hass.localize("ui.panel.config.zha.header")}
|
||||
</div>
|
||||
|
||||
<div slot="introduction">
|
||||
${this.hass.localize("ui.panel.config.zha.introduction")}
|
||||
</div>
|
||||
|
||||
<ha-card>
|
||||
${this.pages.map((page) => {
|
||||
return html`
|
||||
<a href=${`/config/zha/${page}`}>
|
||||
<paper-item>
|
||||
<paper-item-body two-line="">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.zha.${page}.caption`
|
||||
)}
|
||||
<div secondary>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.zha.${page}.description`
|
||||
)}
|
||||
</div>
|
||||
</paper-item-body>
|
||||
<ha-icon-next></ha-icon-next>
|
||||
</paper-item>
|
||||
</a>
|
||||
`;
|
||||
})}
|
||||
</ha-card>
|
||||
<ha-card>
|
||||
<ha-data-table
|
||||
.columns=${this._columns(this.narrow)}
|
||||
.data=${this._memoizeDevices(this._devices)}
|
||||
@row-click=${this._handleDeviceClicked}
|
||||
.id=${"ieee"}
|
||||
auto-height
|
||||
></ha-data-table>
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _fetchDevices() {
|
||||
this._devices = (await fetchDevices(this.hass!)).sort(sortZHADevices);
|
||||
}
|
||||
|
||||
private async _handleDeviceClicked(ev: CustomEvent) {
|
||||
const deviceId = (ev.detail as RowClickedEvent).id;
|
||||
navigate(this, `/config/zha/device/${deviceId}`);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultArray {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"zha-config-dashboard": ZHAConfigDashboard;
|
||||
}
|
||||
}
|
||||
+8
-8
@@ -1,6 +1,6 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import {
|
||||
@@ -13,13 +13,13 @@ import {
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../../../components/buttons/ha-call-service-button";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-service-description";
|
||||
import { bindDevices, unbindDevices, ZHADevice } from "../../../../../data/zha";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import "../../../ha-config-section";
|
||||
import "../../../components/buttons/ha-call-service-button";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-service-description";
|
||||
import { bindDevices, unbindDevices, ZHADevice } from "../../../data/zha";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import "../ha-config-section";
|
||||
import { ItemSelectedEvent } from "./types";
|
||||
|
||||
@customElement("zha-device-binding-control")
|
||||
@@ -0,0 +1,566 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import { HassEvent, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import "../../../components/buttons/ha-call-service-button";
|
||||
import "../../../components/entity/state-badge";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-service-description";
|
||||
import {
|
||||
AreaRegistryEntry,
|
||||
subscribeAreaRegistry,
|
||||
} from "../../../data/area_registry";
|
||||
import {
|
||||
DeviceRegistryEntryMutableParams,
|
||||
updateDeviceRegistryEntry,
|
||||
} from "../../../data/device_registry";
|
||||
import {
|
||||
reconfigureNode,
|
||||
ZHADevice,
|
||||
ZHAEntityReference,
|
||||
} from "../../../data/zha";
|
||||
import { showZHADeviceZigbeeInfoDialog } from "../../../dialogs/zha-device-zigbee-signature-dialog/show-dialog-zha-device-zigbee-info";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { addEntitiesToLovelaceView } from "../../lovelace/editor/add-entities-to-view";
|
||||
import { formatAsPaddedHex } from "./functions";
|
||||
import { ItemSelectedEvent, NodeServiceData } from "./types";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"zha-device-removed": {
|
||||
device?: ZHADevice;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("zha-device-card")
|
||||
class ZHADeviceCard extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public device?: ZHADevice;
|
||||
|
||||
@property({ type: Boolean }) public narrow?: boolean;
|
||||
|
||||
@property({ type: Boolean }) public showHelp?: boolean = false;
|
||||
|
||||
@property({ type: Boolean }) public showActions?: boolean = true;
|
||||
|
||||
@property({ type: Boolean }) public showName?: boolean = true;
|
||||
|
||||
@property({ type: Boolean }) public showEntityDetail?: boolean = true;
|
||||
|
||||
@property({ type: Boolean }) public showModelInfo?: boolean = true;
|
||||
|
||||
@property({ type: Boolean }) public showEditableInfo?: boolean = true;
|
||||
|
||||
@property() private _serviceData?: NodeServiceData;
|
||||
|
||||
@property() private _areas: AreaRegistryEntry[] = [];
|
||||
|
||||
@property() private _selectedAreaIndex = -1;
|
||||
|
||||
@property() private _userGivenName?: string;
|
||||
|
||||
private _unsubAreas?: UnsubscribeFunc;
|
||||
|
||||
private _unsubEntities?: UnsubscribeFunc;
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
if (this._unsubAreas) {
|
||||
this._unsubAreas();
|
||||
}
|
||||
if (this._unsubEntities) {
|
||||
this._unsubEntities();
|
||||
}
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._unsubAreas = subscribeAreaRegistry(this.hass.connection, (areas) => {
|
||||
this._areas = areas;
|
||||
if (this.device) {
|
||||
this._selectedAreaIndex =
|
||||
this._areas.findIndex(
|
||||
(area) => area.area_id === this.device!.area_id
|
||||
) + 1; // account for the no area selected index
|
||||
}
|
||||
});
|
||||
this.hass.connection
|
||||
.subscribeEvents((event: HassEvent) => {
|
||||
if (this.device) {
|
||||
this.device!.entities.forEach((deviceEntity) => {
|
||||
if (event.data.old_entity_id === deviceEntity.entity_id) {
|
||||
deviceEntity.entity_id = event.data.entity_id;
|
||||
}
|
||||
});
|
||||
}
|
||||
}, "entity_registry_updated")
|
||||
.then((unsub) => {
|
||||
this._unsubEntities = unsub;
|
||||
});
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues): void {
|
||||
super.firstUpdated(changedProperties);
|
||||
this.addEventListener("hass-service-called", (ev) =>
|
||||
this.serviceCalled(ev)
|
||||
);
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
if (changedProperties.has("device")) {
|
||||
if (!this._areas || !this.device || !this.device.area_id) {
|
||||
this._selectedAreaIndex = 0;
|
||||
} else {
|
||||
this._selectedAreaIndex =
|
||||
this._areas.findIndex(
|
||||
(area) => area.area_id === this.device!.area_id
|
||||
) + 1;
|
||||
}
|
||||
this._userGivenName = this.device!.user_given_name;
|
||||
this._serviceData = {
|
||||
ieee_address: this.device!.ieee,
|
||||
};
|
||||
}
|
||||
super.update(changedProperties);
|
||||
}
|
||||
|
||||
protected serviceCalled(ev): void {
|
||||
// Check if this is for us
|
||||
if (ev.detail.success && ev.detail.service === "remove") {
|
||||
fireEvent(this, "zha-device-removed", {
|
||||
device: this.device,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-card header="${this.showName ? this.device!.name : ""}">
|
||||
${
|
||||
this.showModelInfo
|
||||
? html`
|
||||
<div class="info">
|
||||
<div class="model">${this.device!.model}</div>
|
||||
<div class="manuf">
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.manuf",
|
||||
"manufacturer",
|
||||
this.device!.manufacturer
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
<div class="card-content">
|
||||
<dl>
|
||||
<dt>IEEE:</dt>
|
||||
<dd class="zha-info">${this.device!.ieee}</dd>
|
||||
<dt>Nwk:</dt>
|
||||
<dd class="zha-info">${formatAsPaddedHex(this.device!.nwk)}</dd>
|
||||
<dt>Device Type:</dt>
|
||||
<dd class="zha-info">${this.device!.device_type}</dd>
|
||||
<dt>LQI:</dt>
|
||||
<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 ||
|
||||
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>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.quirk"
|
||||
)}:
|
||||
</dt>
|
||||
<dd class="zha-info">${this.device!.quirk_class}</dd>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="device-entities">
|
||||
${this.device!.entities.map(
|
||||
(entity) => html`
|
||||
<paper-icon-item
|
||||
@click="${this._openMoreInfo}"
|
||||
.entity="${entity}"
|
||||
>
|
||||
<state-badge
|
||||
.stateObj="${this.hass!.states[entity.entity_id]}"
|
||||
slot="item-icon"
|
||||
></state-badge>
|
||||
${this.showEntityDetail
|
||||
? html`
|
||||
<paper-item-body>
|
||||
<div class="name">
|
||||
${this._computeEntityName(entity)}
|
||||
</div>
|
||||
<div class="secondary entity-id">
|
||||
${entity.entity_id}
|
||||
</div>
|
||||
</paper-item-body>
|
||||
`
|
||||
: ""}
|
||||
</paper-icon-item>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
${
|
||||
this.device!.entities && this.device!.entities.length > 0
|
||||
? html`
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._addToLovelaceView}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.entities.add_entities_lovelace"
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
this.showEditableInfo
|
||||
? html`
|
||||
<div class="editable">
|
||||
<paper-input
|
||||
type="string"
|
||||
@change="${this._saveCustomName}"
|
||||
.value="${this._userGivenName || ""}"
|
||||
.placeholder="${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.zha_device_card.device_name_placeholder"
|
||||
)}"
|
||||
></paper-input>
|
||||
</div>
|
||||
<div class="node-picker">
|
||||
<paper-dropdown-menu
|
||||
.label="${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.zha_device_card.area_picker_label"
|
||||
)}"
|
||||
class="menu"
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected="${this._selectedAreaIndex}"
|
||||
@iron-select="${this._selectedAreaChanged}"
|
||||
>
|
||||
<paper-item>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.no_area"
|
||||
)}
|
||||
</paper-item>
|
||||
|
||||
${this._areas.map(
|
||||
(entry) => html`
|
||||
<paper-item>${entry.name}</paper-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
</div>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
this.showActions
|
||||
? html`
|
||||
<div class="card-actions">
|
||||
${this.device!.device_type !== "Coordinator"
|
||||
? html`
|
||||
<mwc-button @click=${this._onReconfigureNodeClick}>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.reconfigure"
|
||||
)}
|
||||
</mwc-button>
|
||||
${this.showHelp
|
||||
? html`
|
||||
<div class="help-text">
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.services.reconfigure"
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
|
||||
<ha-call-service-button
|
||||
.hass=${this.hass}
|
||||
domain="zha"
|
||||
service="remove"
|
||||
.confirmation=${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.confirmations.remove"
|
||||
)}
|
||||
.serviceData=${this._serviceData}
|
||||
>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.remove"
|
||||
)}
|
||||
</ha-call-service-button>
|
||||
${this.showHelp
|
||||
? html`
|
||||
<div class="help-text">
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.services.remove"
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
`
|
||||
: ""}
|
||||
${this.device!.power_source === "Mains" &&
|
||||
(this.device!.device_type === "Router" ||
|
||||
this.device!.device_type === "Coordinator")
|
||||
? html`
|
||||
<mwc-button @click=${this._onAddDevicesClick}>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.common.add_devices"
|
||||
)}
|
||||
</mwc-button>
|
||||
${this.showHelp
|
||||
? html`
|
||||
<ha-service-description
|
||||
.hass=${this.hass}
|
||||
domain="zha"
|
||||
service="permit"
|
||||
class="help-text2"
|
||||
></ha-service-description>
|
||||
`
|
||||
: ""}
|
||||
`
|
||||
: ""}
|
||||
${this.device!.device_type !== "Coordinator"
|
||||
? html`
|
||||
<mwc-button @click=${this._handleZigbeeInfoClicked}>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.zigbee_information"
|
||||
)}
|
||||
</mwc-button>
|
||||
${this.showHelp
|
||||
? html`
|
||||
<div class="help-text">
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.services.zigbee_information"
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _onReconfigureNodeClick(): Promise<void> {
|
||||
if (this.hass) {
|
||||
await reconfigureNode(this.hass, this.device!.ieee);
|
||||
}
|
||||
}
|
||||
|
||||
private _computeEntityName(entity: ZHAEntityReference): string {
|
||||
if (this.hass.states[entity.entity_id]) {
|
||||
return computeStateName(this.hass.states[entity.entity_id]);
|
||||
}
|
||||
return entity.name;
|
||||
}
|
||||
|
||||
private async _saveCustomName(event): Promise<void> {
|
||||
if (this.hass) {
|
||||
const values: DeviceRegistryEntryMutableParams = {
|
||||
name_by_user: event.target.value,
|
||||
area_id: this.device!.area_id ? this.device!.area_id : undefined,
|
||||
};
|
||||
|
||||
await updateDeviceRegistryEntry(
|
||||
this.hass,
|
||||
this.device!.device_reg_id,
|
||||
values
|
||||
);
|
||||
|
||||
this.device!.user_given_name = event.target.value;
|
||||
}
|
||||
}
|
||||
|
||||
private _openMoreInfo(ev: MouseEvent): void {
|
||||
fireEvent(this, "hass-more-info", {
|
||||
entityId: (ev.currentTarget as any).entity.entity_id,
|
||||
});
|
||||
}
|
||||
|
||||
private async _selectedAreaChanged(event: ItemSelectedEvent) {
|
||||
if (!this.device || !this._areas) {
|
||||
return;
|
||||
}
|
||||
this._selectedAreaIndex = event!.target!.selected;
|
||||
const area = this._areas[this._selectedAreaIndex - 1]; // account for No Area
|
||||
if (
|
||||
(!area && !this.device.area_id) ||
|
||||
(area && area.area_id === this.device.area_id)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newAreaId = area ? area.area_id : undefined;
|
||||
await updateDeviceRegistryEntry(this.hass!, this.device.device_reg_id, {
|
||||
area_id: newAreaId,
|
||||
name_by_user: this.device!.user_given_name,
|
||||
});
|
||||
this.device!.area_id = newAreaId;
|
||||
}
|
||||
|
||||
private _onAddDevicesClick() {
|
||||
navigate(this, "/config/zha/add/" + this.device!.ieee);
|
||||
}
|
||||
|
||||
private async _handleZigbeeInfoClicked() {
|
||||
showZHADeviceZigbeeInfoDialog(this, { device: this.device! });
|
||||
}
|
||||
|
||||
private _addToLovelaceView(): void {
|
||||
addEntitiesToLovelaceView(
|
||||
this,
|
||||
this.hass,
|
||||
this.device!.entities.map((entity) => entity.entity_id)
|
||||
);
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
:host(:not([narrow])) .device-entities {
|
||||
max-height: 225px;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 4px;
|
||||
justify-content: left;
|
||||
}
|
||||
ha-card {
|
||||
flex: 1 0 100%;
|
||||
padding-bottom: 10px;
|
||||
min-width: 300px;
|
||||
}
|
||||
.device {
|
||||
width: 30%;
|
||||
}
|
||||
.device .name {
|
||||
font-weight: bold;
|
||||
}
|
||||
.device .manuf {
|
||||
color: var(--secondary-text-color);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.extra-info {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.manuf,
|
||||
.zha-info,
|
||||
.name {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.entity-id {
|
||||
text-overflow: ellipsis;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.info {
|
||||
margin-left: 16px;
|
||||
}
|
||||
dl {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
}
|
||||
dl dt {
|
||||
display: inline-block;
|
||||
width: 30%;
|
||||
padding-left: 12px;
|
||||
float: left;
|
||||
text-align: left;
|
||||
}
|
||||
dl dd {
|
||||
width: 60%;
|
||||
overflow-wrap: break-word;
|
||||
margin-inline-start: 20px;
|
||||
}
|
||||
paper-icon-item {
|
||||
overflow-x: hidden;
|
||||
cursor: pointer;
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
.editable {
|
||||
padding-left: 28px;
|
||||
padding-right: 28px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.help-text {
|
||||
color: grey;
|
||||
padding: 16px;
|
||||
}
|
||||
.menu {
|
||||
width: 100%;
|
||||
}
|
||||
.node-picker {
|
||||
align-items: center;
|
||||
padding-left: 28px;
|
||||
padding-right: 28px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.buttons .icon {
|
||||
margin-right: 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"zha-device-card": ZHADeviceCard;
|
||||
}
|
||||
}
|
||||
+28
-15
@@ -9,18 +9,16 @@ import {
|
||||
CSSResult,
|
||||
} from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
import "../../../../../components/data-table/ha-data-table";
|
||||
import "../../../components/data-table/ha-data-table";
|
||||
import type {
|
||||
DataTableColumnContainer,
|
||||
HaDataTable,
|
||||
DataTableRowData,
|
||||
} from "../../../../../components/data-table/ha-data-table";
|
||||
import "../../../../../components/entity/ha-state-icon";
|
||||
import type {
|
||||
ZHADeviceEndpoint,
|
||||
ZHAEntityReference,
|
||||
} from "../../../../../data/zha";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
} from "../../../components/data-table/ha-data-table";
|
||||
import "../../../components/entity/ha-state-icon";
|
||||
import type { ZHADeviceEndpoint, ZHAEntityReference } from "../../../data/zha";
|
||||
import { showZHADeviceInfoDialog } from "../../../dialogs/zha-device-info-dialog/show-dialog-zha-device-info";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
|
||||
export interface DeviceEndpointRowData extends DataTableRowData {
|
||||
id: string;
|
||||
@@ -57,7 +55,6 @@ export class ZHADeviceEndpointDataTable extends LitElement {
|
||||
ieee: deviceEndpoint.device.ieee,
|
||||
endpoint_id: deviceEndpoint.endpoint_id,
|
||||
entities: deviceEndpoint.entities,
|
||||
dev_id: deviceEndpoint.device.device_reg_id,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -75,10 +72,14 @@ export class ZHADeviceEndpointDataTable extends LitElement {
|
||||
filterable: true,
|
||||
direction: "asc",
|
||||
grows: true,
|
||||
template: (name, device: any) => html`
|
||||
<a href="${`/config/devices/device/${device.dev_id}`}">
|
||||
template: (name) => html`
|
||||
<div
|
||||
class="mdc-data-table__cell table-cell-text"
|
||||
@click=${this._handleClicked}
|
||||
style="cursor: pointer;"
|
||||
>
|
||||
${name}
|
||||
</a>
|
||||
</div>
|
||||
`,
|
||||
},
|
||||
endpoint_id: {
|
||||
@@ -94,10 +95,14 @@ export class ZHADeviceEndpointDataTable extends LitElement {
|
||||
filterable: true,
|
||||
direction: "asc",
|
||||
grows: true,
|
||||
template: (name, device: any) => html`
|
||||
<a href="${`/config/devices/device/${device.dev_id}`}">
|
||||
template: (name) => html`
|
||||
<div
|
||||
class="mdc-data-table__cell table-cell-text"
|
||||
@click=${this._handleClicked}
|
||||
style="cursor: pointer;"
|
||||
>
|
||||
${name}
|
||||
</a>
|
||||
</div>
|
||||
`,
|
||||
},
|
||||
endpoint_id: {
|
||||
@@ -151,6 +156,14 @@ export class ZHADeviceEndpointDataTable extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private async _handleClicked(ev: CustomEvent) {
|
||||
const rowId = ((ev.target as HTMLElement).closest(
|
||||
".mdc-data-table__row"
|
||||
) as any).rowId;
|
||||
const ieee = rowId.substring(0, rowId.indexOf("_"));
|
||||
showZHADeviceInfoDialog(this, { ieee });
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
css`
|
||||
Executable
+157
@@ -0,0 +1,157 @@
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import {
|
||||
Cluster,
|
||||
fetchBindableDevices,
|
||||
fetchGroups,
|
||||
fetchZHADevice,
|
||||
ZHADevice,
|
||||
ZHAGroup,
|
||||
} from "../../../data/zha";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { sortZHADevices, sortZHAGroups } from "./functions";
|
||||
import { ZHAClusterSelectedParams } from "./types";
|
||||
import "./zha-cluster-attributes";
|
||||
import "./zha-cluster-commands";
|
||||
import "./zha-clusters";
|
||||
import "./zha-device-binding";
|
||||
import "./zha-group-binding";
|
||||
import "./zha-node";
|
||||
|
||||
@customElement("zha-device-page")
|
||||
export class ZHADevicePage extends LitElement {
|
||||
@property() public hass?: HomeAssistant;
|
||||
|
||||
@property() public isWide?: boolean;
|
||||
|
||||
@property() public ieee?: string;
|
||||
|
||||
@property() public device?: ZHADevice;
|
||||
|
||||
@property() public narrow?: boolean;
|
||||
|
||||
@property() private _selectedCluster?: Cluster;
|
||||
|
||||
@property() private _bindableDevices: ZHADevice[] = [];
|
||||
|
||||
@property() private _groups: ZHAGroup[] = [];
|
||||
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
if (changedProperties.has("ieee")) {
|
||||
this._fetchData();
|
||||
}
|
||||
super.update(changedProperties);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<hass-subpage
|
||||
.header=${this.hass!.localize("ui.panel.config.zha.devices.header")}
|
||||
.back=${!this.isWide}
|
||||
>
|
||||
<zha-node
|
||||
.isWide="${this.isWide}"
|
||||
.hass=${this.hass}
|
||||
.device=${this.device}
|
||||
></zha-node>
|
||||
|
||||
${this.device && this.device.device_type !== "Coordinator"
|
||||
? html`
|
||||
<zha-clusters
|
||||
.hass=${this.hass}
|
||||
.isWide="${this.isWide}"
|
||||
.selectedDevice="${this.device}"
|
||||
@zha-cluster-selected="${this._onClusterSelected}"
|
||||
></zha-clusters>
|
||||
${this._selectedCluster
|
||||
? html`
|
||||
<zha-cluster-attributes
|
||||
.isWide="${this.isWide}"
|
||||
.hass=${this.hass}
|
||||
.selectedNode="${this.device}"
|
||||
.selectedCluster="${this._selectedCluster}"
|
||||
></zha-cluster-attributes>
|
||||
|
||||
<zha-cluster-commands
|
||||
.isWide="${this.isWide}"
|
||||
.hass=${this.hass}
|
||||
.selectedNode="${this.device}"
|
||||
.selectedCluster="${this._selectedCluster}"
|
||||
></zha-cluster-commands>
|
||||
`
|
||||
: ""}
|
||||
${this._bindableDevices.length > 0
|
||||
? html`
|
||||
<zha-device-binding-control
|
||||
.isWide="${this.isWide}"
|
||||
.hass=${this.hass}
|
||||
.selectedDevice="${this.device}"
|
||||
.bindableDevices="${this._bindableDevices}"
|
||||
></zha-device-binding-control>
|
||||
`
|
||||
: ""}
|
||||
${this.device && this._groups.length > 0
|
||||
? html`
|
||||
<zha-group-binding-control
|
||||
.isWide="${this.isWide}"
|
||||
.narrow="${this.narrow}"
|
||||
.hass=${this.hass}
|
||||
.selectedDevice="${this.device}"
|
||||
.groups="${this._groups}"
|
||||
></zha-group-binding-control>
|
||||
`
|
||||
: ""}
|
||||
`
|
||||
: ""}
|
||||
<div class="spacer"></div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
private _onClusterSelected(
|
||||
selectedClusterEvent: HASSDomEvent<ZHAClusterSelectedParams>
|
||||
): void {
|
||||
this._selectedCluster = selectedClusterEvent.detail.cluster;
|
||||
}
|
||||
|
||||
private async _fetchData(): Promise<void> {
|
||||
if (this.ieee && this.hass) {
|
||||
this.device = await fetchZHADevice(this.hass, this.ieee);
|
||||
this._bindableDevices =
|
||||
this.device && this.device.device_type !== "Coordinator"
|
||||
? (await fetchBindableDevices(this.hass, this.ieee)).sort(
|
||||
sortZHADevices
|
||||
)
|
||||
: [];
|
||||
this._groups = (await fetchGroups(this.hass!)).sort(sortZHAGroups);
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.spacer {
|
||||
height: 50px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"zha-device-page": ZHADevicePage;
|
||||
}
|
||||
}
|
||||
+10
-10
@@ -1,6 +1,6 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import {
|
||||
@@ -14,11 +14,11 @@ import {
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import type { HASSDomEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/buttons/ha-call-service-button";
|
||||
import { SelectionChangedEvent } from "../../../../../components/data-table/ha-data-table";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-service-description";
|
||||
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/buttons/ha-call-service-button";
|
||||
import { SelectionChangedEvent } from "../../../components/data-table/ha-data-table";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-service-description";
|
||||
import {
|
||||
bindDeviceToGroup,
|
||||
Cluster,
|
||||
@@ -26,10 +26,10 @@ import {
|
||||
unbindDeviceFromGroup,
|
||||
ZHADevice,
|
||||
ZHAGroup,
|
||||
} from "../../../../../data/zha";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import "../../../ha-config-section";
|
||||
} from "../../../data/zha";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "../ha-config-section";
|
||||
import { ItemSelectedEvent } from "./types";
|
||||
import "./zha-clusters-data-table";
|
||||
import type { ZHAClustersDataTable } from "./zha-clusters-data-table";
|
||||
+33
-37
@@ -1,5 +1,5 @@
|
||||
import "@material/mwc-button";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "@polymer/paper-spinner/paper-spinner";
|
||||
import {
|
||||
css,
|
||||
@@ -11,9 +11,9 @@ import {
|
||||
PropertyValues,
|
||||
query,
|
||||
} from "lit-element";
|
||||
import { HASSDomEvent } from "../../../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../../../common/navigate";
|
||||
import { SelectionChangedEvent } from "../../../../../components/data-table/ha-data-table";
|
||||
import { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { SelectionChangedEvent } from "../../../components/data-table/ha-data-table";
|
||||
import {
|
||||
addMembersToGroup,
|
||||
fetchGroup,
|
||||
@@ -22,12 +22,13 @@ import {
|
||||
removeMembersFromGroup,
|
||||
ZHAGroup,
|
||||
ZHADeviceEndpoint,
|
||||
} from "../../../../../data/zha";
|
||||
import "../../../../../layouts/hass-error-screen";
|
||||
import "../../../../../layouts/hass-subpage";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import "../../../ha-config-section";
|
||||
} from "../../../data/zha";
|
||||
import "../../../layouts/hass-error-screen";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import "../ha-config-section";
|
||||
import { formatAsPaddedHex } from "./functions";
|
||||
import "./zha-device-card";
|
||||
import "./zha-device-endpoint-data-table";
|
||||
import type { ZHADeviceEndpointDataTable } from "./zha-device-endpoint-data-table";
|
||||
|
||||
@@ -121,26 +122,25 @@ export class ZHAGroupPage extends LitElement {
|
||||
<div class="header">
|
||||
${this.hass.localize("ui.panel.config.zha.groups.members")}
|
||||
</div>
|
||||
<ha-card>
|
||||
${this.group.members.length
|
||||
? this.group.members.map(
|
||||
(member) =>
|
||||
html`<a
|
||||
href="/config/devices/device/${member.device
|
||||
.device_reg_id}"
|
||||
>
|
||||
<paper-item
|
||||
>${member.device.user_given_name ||
|
||||
member.device.name}</paper-item
|
||||
>
|
||||
</a>`
|
||||
)
|
||||
: html`
|
||||
<paper-item>
|
||||
This group has no members
|
||||
</paper-item>
|
||||
`}
|
||||
</ha-card>
|
||||
|
||||
${this.group.members.length
|
||||
? this.group.members.map(
|
||||
(member) => html`
|
||||
<zha-device-card
|
||||
class="card"
|
||||
.hass=${this.hass}
|
||||
.device=${member.device}
|
||||
.narrow=${this.narrow}
|
||||
.showActions=${false}
|
||||
.showEditableInfo=${false}
|
||||
></zha-device-card>
|
||||
`
|
||||
)
|
||||
: html`
|
||||
<p>
|
||||
This group has no members
|
||||
</p>
|
||||
`}
|
||||
${this.group.members.length
|
||||
? html`
|
||||
<div class="header">
|
||||
@@ -285,9 +285,6 @@ export class ZHAGroupPage extends LitElement {
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
css`
|
||||
hass-subpage {
|
||||
--app-header-text-color: var(--sidebar-icon-color);
|
||||
}
|
||||
.header {
|
||||
font-family: var(--paper-font-display1_-_font-family);
|
||||
-webkit-font-smoothing: var(
|
||||
@@ -300,13 +297,12 @@ export class ZHAGroupPage extends LitElement {
|
||||
opacity: var(--dark-primary-opacity);
|
||||
}
|
||||
|
||||
.button {
|
||||
float: right;
|
||||
ha-config-section *:last-child {
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
.button {
|
||||
float: right;
|
||||
}
|
||||
|
||||
mwc-button paper-spinner {
|
||||
@@ -0,0 +1,172 @@
|
||||
import "@material/mwc-button";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "@polymer/paper-spinner/paper-spinner";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { SelectionChangedEvent } from "../../../components/data-table/ha-data-table";
|
||||
import { fetchGroups, removeGroups, ZHAGroup } from "../../../data/zha";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { sortZHAGroups } from "./functions";
|
||||
import "./zha-groups-data-table";
|
||||
|
||||
@customElement("zha-groups-dashboard")
|
||||
export class ZHAGroupsDashboard extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public narrow = false;
|
||||
|
||||
@property() public _groups?: ZHAGroup[];
|
||||
|
||||
@property() private _processingRemove = false;
|
||||
|
||||
@property() private _selectedGroupsToRemove: number[] = [];
|
||||
|
||||
private _firstUpdatedCalled = false;
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
if (this.hass && this._firstUpdatedCalled) {
|
||||
this._fetchGroups();
|
||||
}
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues): void {
|
||||
super.firstUpdated(changedProperties);
|
||||
if (this.hass) {
|
||||
this._fetchGroups();
|
||||
}
|
||||
this._firstUpdatedCalled = true;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<hass-subpage
|
||||
.header=${this.hass!.localize(
|
||||
"ui.panel.config.zha.groups.groups-header"
|
||||
)}
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="toolbar-icon"
|
||||
icon="hass:plus"
|
||||
@click=${this._addGroup}
|
||||
></ha-icon-button>
|
||||
|
||||
<div class="content">
|
||||
${this._groups
|
||||
? html`
|
||||
<zha-groups-data-table
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.groups=${this._groups}
|
||||
.selectable=${true}
|
||||
@selection-changed=${this._handleRemoveSelectionChanged}
|
||||
class="table"
|
||||
></zha-groups-data-table>
|
||||
`
|
||||
: html`
|
||||
<paper-spinner
|
||||
active
|
||||
alt=${this.hass!.localize("ui.common.loading")}
|
||||
></paper-spinner>
|
||||
`}
|
||||
</div>
|
||||
<div class="paper-dialog-buttons">
|
||||
<mwc-button
|
||||
?disabled="${!this._selectedGroupsToRemove.length ||
|
||||
this._processingRemove}"
|
||||
@click="${this._removeGroup}"
|
||||
class="button"
|
||||
>
|
||||
<paper-spinner
|
||||
?active="${this._processingRemove}"
|
||||
alt=${this.hass!.localize(
|
||||
"ui.panel.config.zha.groups.removing_groups"
|
||||
)}
|
||||
></paper-spinner>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.groups.remove_groups"
|
||||
)}</mwc-button
|
||||
>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _fetchGroups() {
|
||||
this._groups = (await fetchGroups(this.hass!)).sort(sortZHAGroups);
|
||||
}
|
||||
|
||||
private _handleRemoveSelectionChanged(
|
||||
ev: HASSDomEvent<SelectionChangedEvent>
|
||||
): void {
|
||||
this._selectedGroupsToRemove = ev.detail.value.map((value) =>
|
||||
Number(value)
|
||||
);
|
||||
}
|
||||
|
||||
private async _removeGroup(): Promise<void> {
|
||||
this._processingRemove = true;
|
||||
this._groups = await removeGroups(this.hass, this._selectedGroupsToRemove);
|
||||
this._selectedGroupsToRemove = [];
|
||||
this._processingRemove = false;
|
||||
}
|
||||
|
||||
private async _addGroup(): Promise<void> {
|
||||
navigate(this, `/config/zha/group-add`);
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
css`
|
||||
.content {
|
||||
padding: 4px;
|
||||
}
|
||||
zha-groups-data-table {
|
||||
width: 100%;
|
||||
}
|
||||
.button {
|
||||
float: right;
|
||||
}
|
||||
.table {
|
||||
height: 200px;
|
||||
overflow: auto;
|
||||
}
|
||||
mwc-button paper-spinner {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
paper-spinner {
|
||||
display: none;
|
||||
}
|
||||
paper-spinner[active] {
|
||||
display: block;
|
||||
}
|
||||
.paper-dialog-buttons {
|
||||
align-items: flex-end;
|
||||
padding: 8px;
|
||||
}
|
||||
.paper-dialog-buttons .warning {
|
||||
--mdc-theme-primary: var(--google-red-500);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"zha-groups-dashboard": ZHAGroupsDashboard;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import "../../../components/data-table/ha-data-table";
|
||||
import type {
|
||||
DataTableColumnContainer,
|
||||
HaDataTable,
|
||||
} from "../../../components/data-table/ha-data-table";
|
||||
import "../../../components/entity/ha-state-icon";
|
||||
import type { ZHADevice, ZHAGroup } from "../../../data/zha";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { formatAsPaddedHex } from "./functions";
|
||||
|
||||
export interface GroupRowData extends ZHAGroup {
|
||||
group?: GroupRowData;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
@customElement("zha-groups-data-table")
|
||||
export class ZHAGroupsDataTable extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public narrow = false;
|
||||
|
||||
@property() public groups: ZHAGroup[] = [];
|
||||
|
||||
@property() public selectable = false;
|
||||
|
||||
@query("ha-data-table") private _dataTable!: HaDataTable;
|
||||
|
||||
private _groups = memoizeOne((groups: ZHAGroup[]) => {
|
||||
let outputGroups: GroupRowData[] = groups;
|
||||
|
||||
outputGroups = outputGroups.map((group) => {
|
||||
return {
|
||||
...group,
|
||||
id: String(group.group_id),
|
||||
};
|
||||
});
|
||||
|
||||
return outputGroups;
|
||||
});
|
||||
|
||||
private _columns = memoizeOne(
|
||||
(narrow: boolean): DataTableColumnContainer =>
|
||||
narrow
|
||||
? {
|
||||
name: {
|
||||
title: "Group",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
direction: "asc",
|
||||
grows: true,
|
||||
template: (name) => html`
|
||||
<div @click=${this._handleRowClicked} style="cursor: pointer;">
|
||||
${name}
|
||||
</div>
|
||||
`,
|
||||
},
|
||||
}
|
||||
: {
|
||||
name: {
|
||||
title: this.hass.localize("ui.panel.config.zha.groups.groups"),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
direction: "asc",
|
||||
grows: true,
|
||||
template: (name) => html`
|
||||
<div @click=${this._handleRowClicked} style="cursor: pointer;">
|
||||
${name}
|
||||
</div>
|
||||
`,
|
||||
},
|
||||
group_id: {
|
||||
title: this.hass.localize("ui.panel.config.zha.groups.group_id"),
|
||||
type: "numeric",
|
||||
width: "15%",
|
||||
template: (groupId: number) => {
|
||||
return html` ${formatAsPaddedHex(groupId)} `;
|
||||
},
|
||||
sortable: true,
|
||||
},
|
||||
members: {
|
||||
title: this.hass.localize("ui.panel.config.zha.groups.members"),
|
||||
type: "numeric",
|
||||
width: "15%",
|
||||
template: (members: ZHADevice[]) => {
|
||||
return html` ${members.length} `;
|
||||
},
|
||||
sortable: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
public clearSelection() {
|
||||
this._dataTable.clearSelection();
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-data-table
|
||||
.columns=${this._columns(this.narrow)}
|
||||
.data=${this._groups(this.groups)}
|
||||
.selectable=${this.selectable}
|
||||
auto-height
|
||||
></ha-data-table>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleRowClicked(ev: CustomEvent) {
|
||||
const groupId = ((ev.target as HTMLElement).closest(
|
||||
".mdc-data-table__row"
|
||||
) as any).rowId;
|
||||
navigate(this, `/config/zha/group/${groupId}`);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"zha-groups-data-table": ZHAGroupsDataTable;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
import "../../../components/ha-icon-button";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import "../../../components/buttons/ha-call-service-button";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-service-description";
|
||||
import { ZHADevice } from "../../../data/zha";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import "../ha-config-section";
|
||||
import "./zha-device-card";
|
||||
|
||||
@customElement("zha-node")
|
||||
export class ZHANode extends LitElement {
|
||||
@property() public hass?: HomeAssistant;
|
||||
|
||||
@property() public isWide?: boolean;
|
||||
|
||||
@property() public device?: ZHADevice;
|
||||
|
||||
@property() private _showHelp = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-config-section .isWide="${this.isWide}">
|
||||
<div class="header" slot="header">
|
||||
<span
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.zha.node_management.header"
|
||||
)}</span
|
||||
>
|
||||
<ha-icon-button
|
||||
class="toggle-help-icon"
|
||||
@click="${this._onHelpTap}"
|
||||
icon="hass:help-circle"
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
<span slot="introduction">
|
||||
${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>
|
||||
<div class="content">
|
||||
${this.device
|
||||
? html`
|
||||
<zha-device-card
|
||||
class="card"
|
||||
.hass=${this.hass}
|
||||
.device=${this.device}
|
||||
.narrow=${!this.isWide}
|
||||
.showHelp=${this._showHelp}
|
||||
showName
|
||||
showModelInfo
|
||||
.showEntityDetail=${false}
|
||||
showActions
|
||||
@zha-device-removed=${this._onDeviceRemoved}
|
||||
></zha-device-card>
|
||||
`
|
||||
: html`
|
||||
<paper-spinner
|
||||
active
|
||||
alt=${this.hass!.localize("ui.common.loading")}
|
||||
></paper-spinner>
|
||||
`}
|
||||
</div>
|
||||
</ha-config-section>
|
||||
`;
|
||||
}
|
||||
|
||||
private _onHelpTap(): void {
|
||||
this._showHelp = !this._showHelp;
|
||||
}
|
||||
|
||||
private _onDeviceRemoved(): void {
|
||||
this.device = undefined;
|
||||
navigate(this, `/config/zha`, true);
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.node-info {
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.help-text {
|
||||
color: grey;
|
||||
padding-left: 28px;
|
||||
padding-right: 28px;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.content {
|
||||
max-width: 680px;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 28px 20px 0;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
ha-service-description {
|
||||
display: block;
|
||||
color: grey;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.header {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.toggle-help-icon {
|
||||
float: right;
|
||||
top: 6px;
|
||||
right: 0;
|
||||
padding-right: 0px;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"zha-node": ZHANode;
|
||||
}
|
||||
}
|
||||
+15
-19
@@ -1,27 +1,27 @@
|
||||
import "@polymer/app-layout/app-header/app-header";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import { computeStateDomain } from "../../../../../common/entity/compute_state_domain";
|
||||
import { computeStateName } from "../../../../../common/entity/compute_state_name";
|
||||
import { sortStatesByName } from "../../../../../common/entity/states_sort_by_name";
|
||||
import "../../../../../components/buttons/ha-call-service-button";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-menu-button";
|
||||
import "../../../../../components/ha-icon-button-arrow-prev";
|
||||
import "../../../../../components/ha-service-description";
|
||||
import "../../../../../layouts/ha-app-layout";
|
||||
import { EventsMixin } from "../../../../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../../../../mixins/localize-mixin";
|
||||
import "../../../../../styles/polymer-ha-style";
|
||||
import "../../../ha-config-section";
|
||||
import "../../../ha-form-style";
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { sortStatesByName } from "../../../common/entity/states_sort_by_name";
|
||||
import "../../../components/buttons/ha-call-service-button";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-menu-button";
|
||||
import "../../../components/ha-icon-button-arrow-prev";
|
||||
import "../../../components/ha-service-description";
|
||||
import "../../../layouts/ha-app-layout";
|
||||
import { EventsMixin } from "../../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
import "../../../styles/polymer-ha-style";
|
||||
import "../ha-config-section";
|
||||
import "../ha-form-style";
|
||||
import "./zwave-groups";
|
||||
import "./zwave-log";
|
||||
import "./zwave-network";
|
||||
@@ -38,10 +38,6 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="iron-flex ha-style ha-form-style">
|
||||
app-toolbar {
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-top: 24px;
|
||||
}
|
||||
+4
-4
@@ -4,10 +4,10 @@ import "@polymer/paper-listbox/paper-listbox";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import { computeStateName } from "../../../../../common/entity/compute_state_name";
|
||||
import "../../../../../components/buttons/ha-call-service-button";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../styles/polymer-ha-style";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import "../../../components/buttons/ha-call-service-button";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../styles/polymer-ha-style";
|
||||
|
||||
class ZwaveGroups extends PolymerElement {
|
||||
static get template() {
|
||||
+3
-3
@@ -2,9 +2,9 @@ import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "../../../../../components/dialog/ha-paper-dialog";
|
||||
import { EventsMixin } from "../../../../../mixins/events-mixin";
|
||||
import "../../../../../styles/polymer-ha-style-dialog";
|
||||
import "../../../components/dialog/ha-paper-dialog";
|
||||
import { EventsMixin } from "../../../mixins/events-mixin";
|
||||
import "../../../styles/polymer-ha-style-dialog";
|
||||
|
||||
class ZwaveLogDialog extends EventsMixin(PolymerElement) {
|
||||
static get template() {
|
||||
+6
-6
@@ -4,12 +4,12 @@ import "@polymer/paper-input/paper-input";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import isPwa from "../../../../../common/config/is_pwa";
|
||||
import "../../../../../components/ha-card";
|
||||
import { EventsMixin } from "../../../../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../../../../mixins/localize-mixin";
|
||||
import "../../../ha-config-section";
|
||||
import "../../../../../styles/polymer-ha-style";
|
||||
import isPwa from "../../../common/config/is_pwa";
|
||||
import "../../../components/ha-card";
|
||||
import { EventsMixin } from "../../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
import "../ha-config-section";
|
||||
import "../../../styles/polymer-ha-style";
|
||||
|
||||
let registeredDialog = false;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user