mirror of
https://github.com/home-assistant/frontend.git
synced 2025-09-27 05:49:39 +00:00
Compare commits
15 Commits
20220524.0
...
fix-button
Author | SHA1 | Date | |
---|---|---|---|
![]() |
026d027386 | ||
![]() |
d0ead1fdb8 | ||
![]() |
b0e6c41238 | ||
![]() |
2c1550b10f | ||
![]() |
ffc4ca5b56 | ||
![]() |
85ad6619b7 | ||
![]() |
7358faf88e | ||
![]() |
19d014307a | ||
![]() |
5217f5c50c | ||
![]() |
c4624faa71 | ||
![]() |
b35ba4d673 | ||
![]() |
f8303bff76 | ||
![]() |
e61aa266a6 | ||
![]() |
d7971c69ad | ||
![]() |
966a624ef6 |
@@ -26,8 +26,8 @@ module.exports = {
|
||||
},
|
||||
version() {
|
||||
const version = fs
|
||||
.readFileSync(path.resolve(paths.polymer_dir, "setup.cfg"), "utf8")
|
||||
.match(/version\W+=\W(\d{8}\.\d)/);
|
||||
.readFileSync(path.resolve(paths.polymer_dir, "pyproject.toml"), "utf8")
|
||||
.match(/version\W+=\W"(\d{8}\.\d)"/);
|
||||
if (!version) {
|
||||
throw Error("Version not found");
|
||||
}
|
||||
|
@@ -1,3 +1,30 @@
|
||||
[build-system]
|
||||
requires = ["setuptools~=60.5", "wheel~=0.37.1"]
|
||||
requires = ["setuptools~=62.3", "wheel~=0.37.1"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20220525.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
{name = "The Home Assistant Authors", email = "hello@home-assistant.io"}
|
||||
]
|
||||
requires-python = ">=3.4.0"
|
||||
|
||||
[project.urls]
|
||||
"Homepage" = "https://github.com/home-assistant/frontend"
|
||||
|
||||
[tool.setuptools]
|
||||
platforms = ["any"]
|
||||
zip-safe = false
|
||||
include-package-data = true
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
include = ["hass_frontend*"]
|
||||
|
||||
[tool.mypy]
|
||||
python_version = 3.4
|
||||
show_error_codes = true
|
||||
strict = true
|
||||
|
@@ -50,14 +50,14 @@ async function main(args) {
|
||||
return;
|
||||
}
|
||||
|
||||
const setup = fs.readFileSync("setup.cfg", "utf8");
|
||||
const version = setup.match(/\d{8}\.\d+/)[0];
|
||||
const setup = fs.readFileSync("pyproject.toml", "utf8");
|
||||
const version = setup.match(/version\W+=\W"(\d{8}\.\d)"/)[1];
|
||||
const newVersion = method(version);
|
||||
|
||||
console.log("Current version:", version);
|
||||
console.log("New version:", newVersion);
|
||||
|
||||
fs.writeFileSync("setup.cfg", setup.replace(version, newVersion), "utf-8");
|
||||
fs.writeFileSync("pyproject.toml", setup.replace(version, newVersion), "utf-8");
|
||||
|
||||
if (!commit) {
|
||||
return;
|
||||
|
26
setup.cfg
26
setup.cfg
@@ -1,26 +0,0 @@
|
||||
[metadata]
|
||||
name = home-assistant-frontend
|
||||
version = 20220524.0
|
||||
author = The Home Assistant Authors
|
||||
author_email = hello@home-assistant.io
|
||||
license = Apache-2.0
|
||||
platforms = any
|
||||
description = The Home Assistant frontend
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown
|
||||
url = https://github.com/home-assistant/frontend
|
||||
|
||||
[options]
|
||||
packages = find:
|
||||
zip_safe = False
|
||||
include_package_data = True
|
||||
python_requires = >= 3.4.0
|
||||
|
||||
[options.packages.find]
|
||||
include =
|
||||
hass_frontend*
|
||||
|
||||
[mypy]
|
||||
python_version = 3.4
|
||||
show_error_codes = True
|
||||
strict = True
|
@@ -1,6 +1,11 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { UNAVAILABLE_STATES } from "../../data/entity";
|
||||
|
||||
export const computeActiveState = (stateObj: HassEntity): string => {
|
||||
if (UNAVAILABLE_STATES.includes(stateObj.state)) {
|
||||
return stateObj.state;
|
||||
}
|
||||
|
||||
const domain = stateObj.entity_id.split(".")[0];
|
||||
let state = stateObj.state;
|
||||
|
||||
|
@@ -29,7 +29,8 @@ import {
|
||||
mdiWeatherNight,
|
||||
} from "@mdi/js";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { updateIsInstalling, UpdateEntity } from "../../data/update";
|
||||
import { UpdateEntity, updateIsInstalling } from "../../data/update";
|
||||
import { weatherIcon } from "../../data/weather";
|
||||
/**
|
||||
* Return the icon to be used for a domain.
|
||||
*
|
||||
@@ -101,6 +102,15 @@ export const domainIconWithoutDefault = (
|
||||
? mdiCheckCircleOutline
|
||||
: mdiCloseCircleOutline;
|
||||
|
||||
case "input_datetime":
|
||||
if (!stateObj?.attributes.has_date) {
|
||||
return mdiClock;
|
||||
}
|
||||
if (!stateObj.attributes.has_time) {
|
||||
return mdiCalendar;
|
||||
}
|
||||
break;
|
||||
|
||||
case "lock":
|
||||
switch (compareState) {
|
||||
case "unlocked":
|
||||
@@ -138,15 +148,6 @@ export const domainIconWithoutDefault = (
|
||||
break;
|
||||
}
|
||||
|
||||
case "input_datetime":
|
||||
if (!stateObj?.attributes.has_date) {
|
||||
return mdiClock;
|
||||
}
|
||||
if (!stateObj.attributes.has_time) {
|
||||
return mdiCalendar;
|
||||
}
|
||||
break;
|
||||
|
||||
case "sun":
|
||||
return stateObj?.state === "above_horizon"
|
||||
? FIXED_DOMAIN_ICONS[domain]
|
||||
@@ -158,6 +159,9 @@ export const domainIconWithoutDefault = (
|
||||
? mdiPackageDown
|
||||
: mdiPackageUp
|
||||
: mdiPackage;
|
||||
|
||||
case "weather":
|
||||
return weatherIcon(stateObj?.state);
|
||||
}
|
||||
|
||||
if (domain in FIXED_DOMAIN_ICONS) {
|
||||
|
@@ -2,12 +2,7 @@ import type { Button } from "@material/mwc-button";
|
||||
import "@material/mwc-menu";
|
||||
import type { Corner, Menu, MenuCorner } from "@material/mwc-menu";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import {
|
||||
customElement,
|
||||
property,
|
||||
query,
|
||||
queryAssignedElements,
|
||||
} from "lit/decorators";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { FOCUS_TARGET } from "../dialogs/make-dialog-manager";
|
||||
import type { HaIconButton } from "./ha-icon-button";
|
||||
|
||||
@@ -33,12 +28,6 @@ export class HaButtonMenu extends LitElement {
|
||||
|
||||
@query("mwc-menu", true) private _menu?: Menu;
|
||||
|
||||
@queryAssignedElements({
|
||||
slot: "trigger",
|
||||
selector: "ha-icon-button, mwc-button",
|
||||
})
|
||||
private _triggerButton!: Array<HaIconButton | Button>;
|
||||
|
||||
public get items() {
|
||||
return this._menu?.items;
|
||||
}
|
||||
@@ -51,14 +40,14 @@ export class HaButtonMenu extends LitElement {
|
||||
if (this._menu?.open) {
|
||||
this._menu.focusItemAtIndex(0);
|
||||
} else {
|
||||
this._triggerButton[0]?.focus();
|
||||
this._triggerButton?.focus();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div @click=${this._handleClick}>
|
||||
<slot name="trigger"></slot>
|
||||
<slot name="trigger" @slotchange=${this._setTriggerAria}></slot>
|
||||
</div>
|
||||
<mwc-menu
|
||||
.corner=${this.corner}
|
||||
@@ -97,6 +86,18 @@ export class HaButtonMenu extends LitElement {
|
||||
this._menu!.show();
|
||||
}
|
||||
|
||||
private get _triggerButton() {
|
||||
return this.querySelector(
|
||||
'ha-icon-button[slot="trigger"], mwc-button[slot="trigger"]'
|
||||
) as HaIconButton | Button | null;
|
||||
}
|
||||
|
||||
private _setTriggerAria() {
|
||||
if (this._triggerButton) {
|
||||
this._triggerButton.ariaHasPopup = "menu";
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host {
|
||||
|
@@ -3,6 +3,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { formatNumber } from "../common/number/format_number";
|
||||
import { CLIMATE_PRESET_NONE } from "../data/climate";
|
||||
import { UNAVAILABLE_STATES } from "../data/entity";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
@customElement("ha-climate-state")
|
||||
@@ -15,22 +16,22 @@ class HaClimateState extends LitElement {
|
||||
const currentStatus = this._computeCurrentStatus();
|
||||
|
||||
return html`<div class="target">
|
||||
${this.stateObj.state !== "unknown"
|
||||
${!UNAVAILABLE_STATES.includes(this.stateObj.state)
|
||||
? html`<span class="state-label">
|
||||
${this._localizeState()}
|
||||
${this.stateObj.attributes.preset_mode &&
|
||||
this.stateObj.attributes.preset_mode !== CLIMATE_PRESET_NONE
|
||||
? html`-
|
||||
${this.hass.localize(
|
||||
`state_attributes.climate.preset_mode.${this.stateObj.attributes.preset_mode}`
|
||||
) || this.stateObj.attributes.preset_mode}`
|
||||
: ""}
|
||||
</span>`
|
||||
: ""}
|
||||
<div class="unit">${this._computeTarget()}</div>
|
||||
${this._localizeState()}
|
||||
${this.stateObj.attributes.preset_mode &&
|
||||
this.stateObj.attributes.preset_mode !== CLIMATE_PRESET_NONE
|
||||
? html`-
|
||||
${this.hass.localize(
|
||||
`state_attributes.climate.preset_mode.${this.stateObj.attributes.preset_mode}`
|
||||
) || this.stateObj.attributes.preset_mode}`
|
||||
: ""}
|
||||
</span>
|
||||
<div class="unit">${this._computeTarget()}</div>`
|
||||
: this._localizeState()}
|
||||
</div>
|
||||
|
||||
${currentStatus
|
||||
${currentStatus && !UNAVAILABLE_STATES.includes(this.stateObj.state)
|
||||
? html`<div class="current">
|
||||
${this.hass.localize("ui.card.climate.currently")}:
|
||||
<div class="unit">${currentStatus}</div>
|
||||
@@ -108,6 +109,10 @@ class HaClimateState extends LitElement {
|
||||
}
|
||||
|
||||
private _localizeState(): string {
|
||||
if (UNAVAILABLE_STATES.includes(this.stateObj.state)) {
|
||||
return this.hass.localize(`state.default.${this.stateObj.state}`);
|
||||
}
|
||||
|
||||
const stateString = this.hass.localize(
|
||||
`component.climate.state._.${this.stateObj.state}`
|
||||
);
|
||||
|
@@ -2,6 +2,7 @@ import "@material/mwc-icon-button";
|
||||
import type { IconButton } from "@material/mwc-icon-button";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import "./ha-svg-icon";
|
||||
|
||||
@customElement("ha-icon-button")
|
||||
@@ -12,7 +13,12 @@ export class HaIconButton extends LitElement {
|
||||
@property({ type: String }) path?: string;
|
||||
|
||||
// Label that is used for ARIA support and as tooltip
|
||||
@property({ type: String }) label = "";
|
||||
@property({ type: String }) label?: string;
|
||||
|
||||
// These should always be set as properties, not attributes,
|
||||
// so that only the <button> element gets the attribute
|
||||
@property({ type: String, attribute: "aria-haspopup" })
|
||||
override ariaHasPopup!: IconButton["ariaHasPopup"];
|
||||
|
||||
@property({ type: Boolean }) hideTitle = false;
|
||||
|
||||
@@ -28,11 +34,11 @@ export class HaIconButton extends LitElement {
|
||||
};
|
||||
|
||||
protected render(): TemplateResult {
|
||||
// Note: `ariaLabel` required despite the `mwc-icon-button` docs saying `label` should be enough
|
||||
return html`
|
||||
<mwc-icon-button
|
||||
.ariaLabel=${this.label}
|
||||
.title=${this.hideTitle ? "" : this.label}
|
||||
aria-label=${ifDefined(this.label)}
|
||||
title=${ifDefined(this.hideTitle ? undefined : this.label)}
|
||||
aria-haspopup=${ifDefined(this.ariaHasPopup)}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.path
|
||||
|
@@ -34,7 +34,7 @@ const RECENT_THRESHOLD = 60000; // 1 minute
|
||||
const RECENT_CACHE: { [cacheKey: string]: RecentCacheResults } = {};
|
||||
const stateHistoryCache: { [cacheKey: string]: CachedResults } = {};
|
||||
|
||||
// Cached type 1 unction. Without cache config.
|
||||
// Cached type 1 function. Without cache config.
|
||||
export const getRecent = (
|
||||
hass: HomeAssistant,
|
||||
entityId: string,
|
||||
@@ -103,13 +103,14 @@ export const getRecentWithCache = (
|
||||
language: string
|
||||
) => {
|
||||
const cacheKey = cacheConfig.cacheKey;
|
||||
const fullCacheKey = cacheKey + `_${cacheConfig.hoursToShow}`;
|
||||
const endTime = new Date();
|
||||
const startTime = new Date(endTime);
|
||||
startTime.setHours(startTime.getHours() - cacheConfig.hoursToShow);
|
||||
let toFetchStartTime = startTime;
|
||||
let appendingToCache = false;
|
||||
|
||||
let cache = stateHistoryCache[cacheKey + `_${cacheConfig.hoursToShow}`];
|
||||
let cache = stateHistoryCache[fullCacheKey];
|
||||
if (
|
||||
cache &&
|
||||
toFetchStartTime >= cache.startTime &&
|
||||
@@ -123,7 +124,7 @@ export const getRecentWithCache = (
|
||||
return cache.prom;
|
||||
}
|
||||
} else {
|
||||
cache = stateHistoryCache[cacheKey] = getEmptyCache(
|
||||
cache = stateHistoryCache[fullCacheKey] = getEmptyCache(
|
||||
language,
|
||||
startTime,
|
||||
endTime
|
||||
@@ -152,7 +153,7 @@ export const getRecentWithCache = (
|
||||
]);
|
||||
fetchedHistory = results[1];
|
||||
} catch (err: any) {
|
||||
delete stateHistoryCache[cacheKey];
|
||||
delete stateHistoryCache[fullCacheKey];
|
||||
throw err;
|
||||
}
|
||||
const stateHistory = computeHistory(hass, fetchedHistory, localize);
|
||||
|
@@ -20,3 +20,20 @@ export const BOARD_NAMES: Record<string, string> = {
|
||||
"intel-nuc": "Intel NUC",
|
||||
yellow: "Home Assistant Yellow",
|
||||
};
|
||||
|
||||
export interface HardwareInfo {
|
||||
hardware: HardwareInfoEntry[];
|
||||
}
|
||||
|
||||
export interface HardwareInfoEntry {
|
||||
board: HardwareInfoBoardInfo;
|
||||
name: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export interface HardwareInfoBoardInfo {
|
||||
manufacturer: string;
|
||||
model?: string;
|
||||
revision?: string;
|
||||
hassio_board_id?: string;
|
||||
}
|
||||
|
@@ -197,7 +197,7 @@ export const fetchRecent = (
|
||||
|
||||
export const fetchRecentWS = (
|
||||
hass: HomeAssistant,
|
||||
entityId: string,
|
||||
entityId: string, // This may be CSV
|
||||
startTime: Date,
|
||||
endTime: Date,
|
||||
skipInitialState = false,
|
||||
@@ -213,7 +213,7 @@ export const fetchRecentWS = (
|
||||
include_start_time_state: !skipInitialState,
|
||||
minimal_response: minimalResponse,
|
||||
no_attributes: noAttributes || false,
|
||||
entity_ids: [entityId],
|
||||
entity_ids: entityId.split(","),
|
||||
});
|
||||
|
||||
export const fetchDate = (
|
||||
|
@@ -2,9 +2,21 @@ import {
|
||||
mdiAlertCircleOutline,
|
||||
mdiGauge,
|
||||
mdiWaterPercent,
|
||||
mdiWeatherCloudy,
|
||||
mdiWeatherFog,
|
||||
mdiWeatherHail,
|
||||
mdiWeatherLightning,
|
||||
mdiWeatherLightningRainy,
|
||||
mdiWeatherNight,
|
||||
mdiWeatherNightPartlyCloudy,
|
||||
mdiWeatherPartlyCloudy,
|
||||
mdiWeatherPouring,
|
||||
mdiWeatherRainy,
|
||||
mdiWeatherSnowy,
|
||||
mdiWeatherSnowyRainy,
|
||||
mdiWeatherSunny,
|
||||
mdiWeatherWindy,
|
||||
mdiWeatherWindyVariant,
|
||||
} from "@mdi/js";
|
||||
import {
|
||||
HassEntityAttributeBase,
|
||||
@@ -57,7 +69,21 @@ export const weatherSVGs = new Set<string>([
|
||||
]);
|
||||
|
||||
export const weatherIcons = {
|
||||
"clear-night": mdiWeatherNight,
|
||||
cloudy: mdiWeatherCloudy,
|
||||
exceptional: mdiAlertCircleOutline,
|
||||
fog: mdiWeatherFog,
|
||||
hail: mdiWeatherHail,
|
||||
lightning: mdiWeatherLightning,
|
||||
"lightning-rainy": mdiWeatherLightningRainy,
|
||||
partlycloudy: mdiWeatherPartlyCloudy,
|
||||
pouring: mdiWeatherPouring,
|
||||
rainy: mdiWeatherRainy,
|
||||
snowy: mdiWeatherSnowy,
|
||||
"snowy-rainy": mdiWeatherSnowyRainy,
|
||||
sunny: mdiWeatherSunny,
|
||||
windy: mdiWeatherWindy,
|
||||
"windy-variant": mdiWeatherWindyVariant,
|
||||
};
|
||||
|
||||
export const weatherAttrIcons = {
|
||||
@@ -437,6 +463,13 @@ export const getWeatherStateIcon = (
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const weatherIcon = (state?: string, nightTime?: boolean): string =>
|
||||
!state
|
||||
? undefined
|
||||
: nightTime && state === "partlycloudy"
|
||||
? mdiWeatherNightPartlyCloudy
|
||||
: weatherIcons[state];
|
||||
|
||||
const DAY_IN_MILLISECONDS = 86400000;
|
||||
|
||||
export const isForecastHourly = (
|
||||
|
@@ -167,6 +167,9 @@ export interface ZwaveJSNodeMetadata {
|
||||
wakeup: string;
|
||||
reset: string;
|
||||
device_database_url: string;
|
||||
}
|
||||
|
||||
export interface ZwaveJSNodeComments {
|
||||
comments: ZWaveJSNodeComment[];
|
||||
}
|
||||
|
||||
@@ -227,6 +230,20 @@ export interface ZWaveJSHealNetworkStatusMessage {
|
||||
heal_node_status: { [key: number]: string };
|
||||
}
|
||||
|
||||
export interface ZWaveJSControllerStatisticsUpdatedMessage {
|
||||
event: "statistics updated";
|
||||
source: "controller";
|
||||
messages_tx: number;
|
||||
messages_rx: number;
|
||||
messages_dropped_tx: number;
|
||||
messages_dropped_rx: number;
|
||||
nak: number;
|
||||
can: number;
|
||||
timeout_ack: number;
|
||||
timeout_response: number;
|
||||
timeout_callback: number;
|
||||
}
|
||||
|
||||
export interface ZWaveJSRemovedNode {
|
||||
node_id: number;
|
||||
manufacturer: string;
|
||||
@@ -284,12 +301,23 @@ export const migrateZwave = (
|
||||
|
||||
export const fetchZwaveNetworkStatus = (
|
||||
hass: HomeAssistant,
|
||||
entry_id: string
|
||||
): Promise<ZWaveJSNetwork> =>
|
||||
hass.callWS({
|
||||
device_or_entry_id: {
|
||||
device_id?: string;
|
||||
entry_id?: string;
|
||||
}
|
||||
): Promise<ZWaveJSNetwork> => {
|
||||
if (device_or_entry_id.device_id && device_or_entry_id.entry_id) {
|
||||
throw new Error("Only one of device or entry ID should be supplied.");
|
||||
}
|
||||
if (!device_or_entry_id.device_id && !device_or_entry_id.entry_id) {
|
||||
throw new Error("Either device or entry ID should be supplied.");
|
||||
}
|
||||
return hass.callWS({
|
||||
type: "zwave_js/network_status",
|
||||
entry_id,
|
||||
device_id: device_or_entry_id.device_id,
|
||||
entry_id: device_or_entry_id.entry_id,
|
||||
});
|
||||
};
|
||||
|
||||
export const fetchZwaveDataCollectionStatus = (
|
||||
hass: HomeAssistant,
|
||||
@@ -442,6 +470,15 @@ export const fetchZwaveNodeMetadata = (
|
||||
device_id,
|
||||
});
|
||||
|
||||
export const fetchZwaveNodeComments = (
|
||||
hass: HomeAssistant,
|
||||
device_id: string
|
||||
): Promise<ZwaveJSNodeComments> =>
|
||||
hass.callWS({
|
||||
type: "zwave_js/node_comments",
|
||||
device_id,
|
||||
});
|
||||
|
||||
export const fetchZwaveNodeConfigParameters = (
|
||||
hass: HomeAssistant,
|
||||
device_id: string
|
||||
@@ -547,6 +584,19 @@ export const subscribeHealZwaveNetworkProgress = (
|
||||
}
|
||||
);
|
||||
|
||||
export const subscribeZwaveControllerStatistics = (
|
||||
hass: HomeAssistant,
|
||||
entry_id: string,
|
||||
callbackFunction: (message: ZWaveJSControllerStatisticsUpdatedMessage) => void
|
||||
): Promise<UnsubscribeFunc> =>
|
||||
hass.connection.subscribeMessage(
|
||||
(message: any) => callbackFunction(message),
|
||||
{
|
||||
type: "zwave_js/subscribe_controller_statistics",
|
||||
entry_id,
|
||||
}
|
||||
);
|
||||
|
||||
export const getZwaveJsIdentifiersFromDevice = (
|
||||
device: DeviceRegistryEntry
|
||||
): ZWaveJSNodeIdentifiers | undefined => {
|
||||
|
@@ -1,23 +1,9 @@
|
||||
import {
|
||||
mdiAlertCircleOutline,
|
||||
mdiEye,
|
||||
mdiGauge,
|
||||
mdiThermometer,
|
||||
mdiWaterPercent,
|
||||
mdiWeatherCloudy,
|
||||
mdiWeatherFog,
|
||||
mdiWeatherHail,
|
||||
mdiWeatherLightning,
|
||||
mdiWeatherLightningRainy,
|
||||
mdiWeatherNight,
|
||||
mdiWeatherPartlyCloudy,
|
||||
mdiWeatherPouring,
|
||||
mdiWeatherRainy,
|
||||
mdiWeatherSnowy,
|
||||
mdiWeatherSnowyRainy,
|
||||
mdiWeatherSunny,
|
||||
mdiWeatherWindy,
|
||||
mdiWeatherWindyVariant,
|
||||
} from "@mdi/js";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
@@ -37,27 +23,10 @@ import {
|
||||
getWeatherUnit,
|
||||
getWind,
|
||||
isForecastHourly,
|
||||
weatherIcons,
|
||||
} from "../../../data/weather";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
|
||||
const weatherIcons = {
|
||||
"clear-night": mdiWeatherNight,
|
||||
cloudy: mdiWeatherCloudy,
|
||||
exceptional: mdiAlertCircleOutline,
|
||||
fog: mdiWeatherFog,
|
||||
hail: mdiWeatherHail,
|
||||
lightning: mdiWeatherLightning,
|
||||
"lightning-rainy": mdiWeatherLightningRainy,
|
||||
partlycloudy: mdiWeatherPartlyCloudy,
|
||||
pouring: mdiWeatherPouring,
|
||||
rainy: mdiWeatherRainy,
|
||||
snowy: mdiWeatherSnowy,
|
||||
"snowy-rainy": mdiWeatherSnowyRainy,
|
||||
sunny: mdiWeatherSunny,
|
||||
windy: mdiWeatherWindy,
|
||||
"windy-variant": mdiWeatherWindyVariant,
|
||||
};
|
||||
|
||||
@customElement("more-info-weather")
|
||||
class MoreInfoWeather extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -235,6 +204,7 @@ class MoreInfoWeather extends LitElement {
|
||||
return css`
|
||||
ha-svg-icon {
|
||||
color: var(--paper-item-icon-color);
|
||||
margin-left: 8px;
|
||||
}
|
||||
.section {
|
||||
margin: 16px 0 8px 0;
|
||||
|
@@ -19,7 +19,6 @@ import { afterNextRender } from "../../../common/util/render-status";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-icon-next";
|
||||
import "../../logbook/ha-logbook";
|
||||
import {
|
||||
AreaRegistryEntry,
|
||||
deleteAreaRegistryEntry,
|
||||
@@ -43,15 +42,16 @@ import { SceneEntity } from "../../../data/scene";
|
||||
import { ScriptEntity } from "../../../data/script";
|
||||
import { findRelated, RelatedResult } from "../../../data/search";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
import "../../logbook/ha-logbook";
|
||||
import { showEntityEditorDialog } from "../entities/show-dialog-entity-editor";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import {
|
||||
loadAreaRegistryDetailDialog,
|
||||
showAreaRegistryDetailDialog,
|
||||
} from "./show-dialog-area-registry-detail";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
|
||||
declare type NameAndEntity<EntityType extends HassEntity> = {
|
||||
name: string;
|
||||
@@ -761,10 +761,10 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
border-radius: 50%;
|
||||
}
|
||||
ha-logbook {
|
||||
height: 800px;
|
||||
height: 400px;
|
||||
}
|
||||
:host([narrow]) ha-logbook {
|
||||
height: 400px;
|
||||
height: 235px;
|
||||
overflow: auto;
|
||||
}
|
||||
`,
|
||||
|
@@ -0,0 +1,13 @@
|
||||
import type { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import type { DeviceAction } from "../../../ha-config-device-page";
|
||||
import { showMQTTDeviceDebugInfoDialog } from "./show-dialog-mqtt-device-debug-info";
|
||||
|
||||
export const getMQTTDeviceActions = (
|
||||
el: HTMLElement,
|
||||
device: DeviceRegistryEntry
|
||||
): DeviceAction[] => [
|
||||
{
|
||||
label: "MQTT Info",
|
||||
action: async () => showMQTTDeviceDebugInfoDialog(el, { device }),
|
||||
},
|
||||
];
|
@@ -1,36 +0,0 @@
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import { haStyle } from "../../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../../types";
|
||||
import { showMQTTDeviceDebugInfoDialog } from "./show-dialog-mqtt-device-debug-info";
|
||||
|
||||
@customElement("ha-device-actions-mqtt")
|
||||
export class HaDeviceActionsMqtt extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public device!: DeviceRegistryEntry;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<mwc-button @click=${this._showDebugInfo}> MQTT Info </mwc-button>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _showDebugInfo(): Promise<void> {
|
||||
const device = this.device;
|
||||
await showMQTTDeviceDebugInfoDialog(this, { device });
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
@@ -0,0 +1,108 @@
|
||||
import { navigate } from "../../../../../../common/navigate";
|
||||
import type { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import { fetchZHADevice } from "../../../../../../data/zha";
|
||||
import { showConfirmationDialog } from "../../../../../../dialogs/generic/show-dialog-box";
|
||||
import type { HomeAssistant } from "../../../../../../types";
|
||||
import { showZHAClusterDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-cluster";
|
||||
import { showZHADeviceChildrenDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-device-children";
|
||||
import { showZHADeviceZigbeeInfoDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-device-zigbee-info";
|
||||
import { showZHAReconfigureDeviceDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-reconfigure-device";
|
||||
import type { DeviceAction } from "../../../ha-config-device-page";
|
||||
|
||||
export const getZHADeviceActions = async (
|
||||
el: HTMLElement,
|
||||
hass: HomeAssistant,
|
||||
device: DeviceRegistryEntry
|
||||
): Promise<DeviceAction[]> => {
|
||||
const zigbeeConnection = device.connections.find(
|
||||
(conn) => conn[0] === "zigbee"
|
||||
);
|
||||
|
||||
if (!zigbeeConnection) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const zhaDevice = await fetchZHADevice(hass, zigbeeConnection[1]);
|
||||
|
||||
if (!zhaDevice) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const actions: DeviceAction[] = [];
|
||||
|
||||
if (zhaDevice.device_type !== "Coordinator") {
|
||||
actions.push({
|
||||
label: hass.localize("ui.dialogs.zha_device_info.buttons.reconfigure"),
|
||||
action: () => showZHAReconfigureDeviceDialog(el, { device: zhaDevice }),
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
zhaDevice.power_source === "Mains" &&
|
||||
(zhaDevice.device_type === "Router" ||
|
||||
zhaDevice.device_type === "Coordinator")
|
||||
) {
|
||||
actions.push(
|
||||
...[
|
||||
{
|
||||
label: hass.localize("ui.dialogs.zha_device_info.buttons.add"),
|
||||
action: () => navigate(`/config/zha/add/${zhaDevice!.ieee}`),
|
||||
},
|
||||
{
|
||||
label: hass.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.device_children"
|
||||
),
|
||||
action: () => showZHADeviceChildrenDialog(el, { device: zhaDevice! }),
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if (zhaDevice.device_type !== "Coordinator") {
|
||||
actions.push(
|
||||
...[
|
||||
{
|
||||
label: hass.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.zigbee_information"
|
||||
),
|
||||
action: () =>
|
||||
showZHADeviceZigbeeInfoDialog(el, { device: zhaDevice }),
|
||||
},
|
||||
{
|
||||
label: hass.localize("ui.dialogs.zha_device_info.buttons.clusters"),
|
||||
action: () => showZHAClusterDialog(el, { device: zhaDevice }),
|
||||
},
|
||||
{
|
||||
label: hass.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.view_in_visualization"
|
||||
),
|
||||
action: () =>
|
||||
navigate(`/config/zha/visualization/${zhaDevice!.device_reg_id}`),
|
||||
},
|
||||
{
|
||||
label: hass.localize("ui.dialogs.zha_device_info.buttons.remove"),
|
||||
classes: "warning",
|
||||
action: async () => {
|
||||
const confirmed = await showConfirmationDialog(el, {
|
||||
text: hass.localize(
|
||||
"ui.dialogs.zha_device_info.confirmations.remove"
|
||||
),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
await hass.callService("zha", "remove", {
|
||||
ieee: zhaDevice.ieee,
|
||||
});
|
||||
|
||||
history.back();
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return actions;
|
||||
};
|
@@ -1,155 +0,0 @@
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { navigate } from "../../../../../../common/navigate";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import { fetchZHADevice, ZHADevice } from "../../../../../../data/zha";
|
||||
import { showConfirmationDialog } from "../../../../../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../../types";
|
||||
import { showZHAClusterDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-cluster";
|
||||
import { showZHADeviceChildrenDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-device-children";
|
||||
import { showZHADeviceZigbeeInfoDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-device-zigbee-info";
|
||||
import { showZHAReconfigureDeviceDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-reconfigure-device";
|
||||
|
||||
@customElement("ha-device-actions-zha")
|
||||
export class HaDeviceActionsZha extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public device!: DeviceRegistryEntry;
|
||||
|
||||
@state() 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>
|
||||
<mwc-button @click=${this._handleDeviceChildrenClicked}>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.device_children"
|
||||
)}
|
||||
</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 @click=${this._onViewInVisualizationClick}>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.view_in_visualization"
|
||||
)}
|
||||
</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;
|
||||
}
|
||||
showZHAReconfigureDeviceDialog(this, { device: this._zhaDevice! });
|
||||
}
|
||||
|
||||
private _onAddDevicesClick() {
|
||||
navigate(`/config/zha/add/${this._zhaDevice!.ieee}`);
|
||||
}
|
||||
|
||||
private _onViewInVisualizationClick() {
|
||||
navigate(`/config/zha/visualization/${this._zhaDevice!.device_reg_id}`);
|
||||
}
|
||||
|
||||
private async _handleZigbeeInfoClicked() {
|
||||
showZHADeviceZigbeeInfoDialog(this, { device: this._zhaDevice! });
|
||||
}
|
||||
|
||||
private async _handleDeviceChildrenClicked() {
|
||||
showZHADeviceChildrenDialog(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;
|
||||
}
|
||||
|
||||
await this.hass.callService("zha", "remove", {
|
||||
ieee: this._zhaDevice!.ieee,
|
||||
});
|
||||
|
||||
history.back();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
@@ -7,6 +7,7 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../../../../components/ha-expansion-panel";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import { fetchZHADevice, ZHADevice } from "../../../../../../data/zha";
|
||||
import { haStyle } from "../../../../../../resources/styles";
|
||||
@@ -40,38 +41,39 @@ export class HaDeviceActionsZha extends LitElement {
|
||||
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>
|
||||
`
|
||||
: ""}
|
||||
<ha-expansion-panel header="Zigbee info">
|
||||
<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>
|
||||
`
|
||||
: ""}
|
||||
</ha-expansion-panel>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -86,6 +88,11 @@ export class HaDeviceActionsZha extends LitElement {
|
||||
word-break: break-all;
|
||||
margin-top: 2px;
|
||||
}
|
||||
ha-expansion-panel {
|
||||
--expansion-panel-summary-padding: 0;
|
||||
--expansion-panel-content-padding: 0;
|
||||
padding-top: 4px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -0,0 +1,68 @@
|
||||
import { getConfigEntries } from "../../../../../../data/config_entries";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import { fetchZwaveNodeStatus } from "../../../../../../data/zwave_js";
|
||||
import type { HomeAssistant } from "../../../../../../types";
|
||||
import { showZWaveJSHealNodeDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-heal-node";
|
||||
import { showZWaveJSReinterviewNodeDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-reinterview-node";
|
||||
import { showZWaveJSRemoveFailedNodeDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-remove-failed-node";
|
||||
import type { DeviceAction } from "../../../ha-config-device-page";
|
||||
|
||||
export const getZwaveDeviceActions = async (
|
||||
el: HTMLElement,
|
||||
hass: HomeAssistant,
|
||||
device: DeviceRegistryEntry
|
||||
): Promise<DeviceAction[]> => {
|
||||
const configEntries = await getConfigEntries(hass, {
|
||||
domain: "zwave_js",
|
||||
});
|
||||
|
||||
const configEntry = configEntries.find((entry) =>
|
||||
device.config_entries.includes(entry.entry_id)
|
||||
);
|
||||
|
||||
if (!configEntry) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const entryId = configEntry.entry_id;
|
||||
|
||||
const node = await fetchZwaveNodeStatus(hass, device.id);
|
||||
|
||||
if (!node || node.is_controller_node) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
label: hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.device_config"
|
||||
),
|
||||
href: `/config/zwave_js/node_config/${device.id}?config_entry=${entryId}`,
|
||||
},
|
||||
{
|
||||
label: hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.reinterview_device"
|
||||
),
|
||||
action: () =>
|
||||
showZWaveJSReinterviewNodeDialog(el, {
|
||||
device_id: device.id,
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: hass.localize("ui.panel.config.zwave_js.device_info.heal_node"),
|
||||
action: () =>
|
||||
showZWaveJSHealNodeDialog(el, {
|
||||
device: device,
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.remove_failed"
|
||||
),
|
||||
action: () =>
|
||||
showZWaveJSRemoveFailedNodeDialog(el, {
|
||||
device_id: device.id,
|
||||
}),
|
||||
},
|
||||
];
|
||||
};
|
@@ -1,138 +0,0 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import {
|
||||
fetchZwaveNodeStatus,
|
||||
ZWaveJSNodeStatus,
|
||||
} from "../../../../../../data/zwave_js";
|
||||
import { haStyle } from "../../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../../types";
|
||||
import { showZWaveJSReinterviewNodeDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-reinterview-node";
|
||||
import { showZWaveJSHealNodeDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-heal-node";
|
||||
import { showZWaveJSRemoveFailedNodeDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-remove-failed-node";
|
||||
import { getConfigEntries } from "../../../../../../data/config_entries";
|
||||
|
||||
@customElement("ha-device-actions-zwave_js")
|
||||
export class HaDeviceActionsZWaveJS extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public device!: DeviceRegistryEntry;
|
||||
|
||||
@state() private _entryId?: string;
|
||||
|
||||
@state() private _node?: ZWaveJSNodeStatus;
|
||||
|
||||
public willUpdate(changedProperties: PropertyValues) {
|
||||
super.willUpdate(changedProperties);
|
||||
if (changedProperties.has("device")) {
|
||||
this._fetchNodeDetails();
|
||||
}
|
||||
}
|
||||
|
||||
protected async _fetchNodeDetails() {
|
||||
if (!this.device) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._node = undefined;
|
||||
|
||||
const configEntries = await getConfigEntries(this.hass, {
|
||||
domain: "zwave_js",
|
||||
});
|
||||
|
||||
const configEntry = configEntries.find((entry) =>
|
||||
this.device.config_entries.includes(entry.entry_id)
|
||||
);
|
||||
|
||||
if (!configEntry) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._entryId = configEntry.entry_id;
|
||||
|
||||
this._node = await fetchZwaveNodeStatus(this.hass, this.device.id);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._node) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
${!this._node.is_controller_node
|
||||
? html`
|
||||
<a
|
||||
.href=${`/config/zwave_js/node_config/${this.device.id}?config_entry=${this._entryId}`}
|
||||
>
|
||||
<mwc-button>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.device_config"
|
||||
)}
|
||||
</mwc-button>
|
||||
</a>
|
||||
<mwc-button @click=${this._reinterviewClicked}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.reinterview_device"
|
||||
)}
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._healNodeClicked}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.heal_node"
|
||||
)}
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._removeFailedNode}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.remove_failed"
|
||||
)}
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
private async _reinterviewClicked() {
|
||||
if (!this.device) {
|
||||
return;
|
||||
}
|
||||
showZWaveJSReinterviewNodeDialog(this, {
|
||||
device_id: this.device.id,
|
||||
});
|
||||
}
|
||||
|
||||
private async _healNodeClicked() {
|
||||
if (!this.device) {
|
||||
return;
|
||||
}
|
||||
showZWaveJSHealNodeDialog(this, {
|
||||
entry_id: this._entryId!,
|
||||
device: this.device,
|
||||
});
|
||||
}
|
||||
|
||||
private async _removeFailedNode() {
|
||||
if (!this.device) {
|
||||
return;
|
||||
}
|
||||
showZWaveJSRemoveFailedNodeDialog(this, {
|
||||
device_id: this.device.id,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import {
|
||||
ZwaveJSNodeComments,
|
||||
fetchZwaveNodeComments,
|
||||
} from "../../../../../../data/zwave_js";
|
||||
import { HomeAssistant } from "../../../../../../types";
|
||||
|
||||
@customElement("ha-device-alerts-zwave_js")
|
||||
export class HaDeviceAlertsZWaveJS extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public device!: DeviceRegistryEntry;
|
||||
|
||||
@state() private _nodeComments?: ZwaveJSNodeComments;
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues) {
|
||||
super.willUpdate(changedProperties);
|
||||
if (changedProperties.has("device")) {
|
||||
this._fetchNodeDetails();
|
||||
}
|
||||
}
|
||||
|
||||
private async _fetchNodeDetails() {
|
||||
this._nodeComments = await fetchZwaveNodeComments(
|
||||
this.hass,
|
||||
this.device.id
|
||||
);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (this._nodeComments && this._nodeComments.comments?.length > 0) {
|
||||
return html`
|
||||
<div>
|
||||
${this._nodeComments.comments.map(
|
||||
(comment) => html`<ha-alert .alertType=${comment.level}>
|
||||
${comment.text}
|
||||
</ha-alert>`
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
return html``;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-device-alerts-zwave_js": HaDeviceAlertsZWaveJS;
|
||||
}
|
||||
}
|
@@ -7,16 +7,17 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import "../../../../../../components/ha-expansion-panel";
|
||||
import {
|
||||
ConfigEntry,
|
||||
getConfigEntries,
|
||||
} from "../../../../../../data/config_entries";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import {
|
||||
fetchZwaveNodeStatus,
|
||||
nodeStatus,
|
||||
ZWaveJSNodeStatus,
|
||||
SecurityClass,
|
||||
ZWaveJSNodeStatus,
|
||||
} from "../../../../../../data/zwave_js";
|
||||
import { haStyle } from "../../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../../types";
|
||||
@@ -69,73 +70,76 @@ export class HaDeviceInfoZWaveJS extends LitElement {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<h4>
|
||||
${this.hass.localize("ui.panel.config.zwave_js.device_info.zwave_info")}
|
||||
</h4>
|
||||
${this._multipleConfigEntries
|
||||
? html`
|
||||
<div>
|
||||
${this.hass.localize("ui.panel.config.zwave_js.common.source")}:
|
||||
${this._configEntry!.title}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<div>
|
||||
${this.hass.localize("ui.panel.config.zwave_js.common.node_id")}:
|
||||
${this._node.node_id}
|
||||
</div>
|
||||
${!this._node.is_controller_node
|
||||
? html`
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.node_status"
|
||||
)}:
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.zwave_js.node_status.${
|
||||
nodeStatus[this._node.status]
|
||||
}`
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.node_ready"
|
||||
)}:
|
||||
${this._node.ready
|
||||
? this.hass.localize("ui.common.yes")
|
||||
: this.hass.localize("ui.common.no")}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.highest_security"
|
||||
)}:
|
||||
${this._node.highest_security_class !== null
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.zwave_js.security_classes.${
|
||||
SecurityClass[this._node.highest_security_class]
|
||||
}.title`
|
||||
)
|
||||
: this._node.is_secure === false
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.zwave_js.security_classes.none.title"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.unknown"
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.zwave_plus"
|
||||
)}:
|
||||
${this._node.zwave_plus_version
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.zwave_plus_version",
|
||||
"version",
|
||||
this._node.zwave_plus_version
|
||||
)
|
||||
: this.hass.localize("ui.common.no")}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<ha-expansion-panel
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.zwave_info"
|
||||
)}
|
||||
>
|
||||
${this._multipleConfigEntries
|
||||
? html`
|
||||
<div>
|
||||
${this.hass.localize("ui.panel.config.zwave_js.common.source")}:
|
||||
${this._configEntry!.title}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<div>
|
||||
${this.hass.localize("ui.panel.config.zwave_js.common.node_id")}:
|
||||
${this._node.node_id}
|
||||
</div>
|
||||
${!this._node.is_controller_node
|
||||
? html`
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.node_status"
|
||||
)}:
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.zwave_js.node_status.${
|
||||
nodeStatus[this._node.status]
|
||||
}`
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.node_ready"
|
||||
)}:
|
||||
${this._node.ready
|
||||
? this.hass.localize("ui.common.yes")
|
||||
: this.hass.localize("ui.common.no")}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.highest_security"
|
||||
)}:
|
||||
${this._node.highest_security_class !== null
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.zwave_js.security_classes.${
|
||||
SecurityClass[this._node.highest_security_class]
|
||||
}.title`
|
||||
)
|
||||
: this._node.is_secure === false
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.zwave_js.security_classes.none.title"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.unknown"
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.zwave_plus"
|
||||
)}:
|
||||
${this._node.zwave_plus_version
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.zwave_plus_version",
|
||||
"version",
|
||||
this._node.zwave_plus_version
|
||||
)
|
||||
: this.hass.localize("ui.common.no")}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</ha-expansion-panel>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -150,6 +154,11 @@ export class HaDeviceInfoZWaveJS extends LitElement {
|
||||
word-break: break-all;
|
||||
margin-top: 2px;
|
||||
}
|
||||
ha-expansion-panel {
|
||||
--expansion-panel-summary-padding: 0;
|
||||
--expansion-panel-content-padding: 0;
|
||||
padding-top: 4px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -1,4 +1,9 @@
|
||||
import { mdiOpenInNew, mdiPencil, mdiPlusCircle } from "@mdi/js";
|
||||
import {
|
||||
mdiDotsVertical,
|
||||
mdiOpenInNew,
|
||||
mdiPencil,
|
||||
mdiPlusCircle,
|
||||
} from "@mdi/js";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
@@ -13,6 +18,7 @@ import { slugify } from "../../../common/string/slugify";
|
||||
import { groupBy } from "../../../common/util/group-by";
|
||||
import "../../../components/entity/ha-battery-icon";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-icon-next";
|
||||
import "../../../components/ha-svg-icon";
|
||||
@@ -26,14 +32,14 @@ import {
|
||||
import {
|
||||
computeDeviceName,
|
||||
DeviceRegistryEntry,
|
||||
updateDeviceRegistryEntry,
|
||||
removeConfigEntryFromDevice,
|
||||
updateDeviceRegistryEntry,
|
||||
} from "../../../data/device_registry";
|
||||
import {
|
||||
fetchDiagnosticHandler,
|
||||
getDeviceDiagnosticsDownloadUrl,
|
||||
getConfigEntryDiagnosticsDownloadUrl,
|
||||
DiagnosticInfo,
|
||||
fetchDiagnosticHandler,
|
||||
getConfigEntryDiagnosticsDownloadUrl,
|
||||
getDeviceDiagnosticsDownloadUrl,
|
||||
} from "../../../data/diagnostics";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
@@ -51,9 +57,10 @@ import {
|
||||
import "../../../layouts/hass-error-screen";
|
||||
import "../../../layouts/hass-tabs-subpage";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
import type { HomeAssistant, Route } from "../../../types";
|
||||
import { brandsUrl } from "../../../util/brands-url";
|
||||
import { fileDownload } from "../../../util/file_download";
|
||||
import "../../logbook/ha-logbook";
|
||||
import "../ha-config-section";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import "./device-detail/ha-device-entities-card";
|
||||
@@ -63,12 +70,19 @@ import {
|
||||
loadDeviceRegistryDetailDialog,
|
||||
showDeviceRegistryDetailDialog,
|
||||
} from "./device-registry-detail/show-dialog-device-registry-detail";
|
||||
import "../../logbook/ha-logbook";
|
||||
|
||||
export interface EntityRegistryStateEntry extends EntityRegistryEntry {
|
||||
stateName?: string | null;
|
||||
}
|
||||
|
||||
export interface DeviceAction {
|
||||
href?: string;
|
||||
action?: (ev: any) => void;
|
||||
label: string;
|
||||
trailingIcon?: string;
|
||||
classes?: string;
|
||||
}
|
||||
|
||||
@customElement("ha-config-device-page")
|
||||
export class HaConfigDevicePage extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -94,11 +108,11 @@ export class HaConfigDevicePage extends LitElement {
|
||||
@state() private _related?: RelatedResult;
|
||||
|
||||
// If a number, it's the request ID so we make sure we don't show older info
|
||||
@state() private _diagnosticDownloadLinks?:
|
||||
| number
|
||||
| (TemplateResult | string)[];
|
||||
@state() private _diagnosticDownloadLinks?: number | DeviceAction[];
|
||||
|
||||
@state() private _deleteButtons?: (TemplateResult | string)[];
|
||||
@state() private _deleteButtons?: DeviceAction[];
|
||||
|
||||
@state() private _deviceActions?: DeviceAction[];
|
||||
|
||||
private _logbookTime = { recent: 86400 };
|
||||
|
||||
@@ -196,15 +210,17 @@ export class HaConfigDevicePage extends LitElement {
|
||||
if (
|
||||
changedProps.has("deviceId") ||
|
||||
changedProps.has("devices") ||
|
||||
changedProps.has("deviceId") ||
|
||||
changedProps.has("entries")
|
||||
) {
|
||||
this._diagnosticDownloadLinks = undefined;
|
||||
this._deleteButtons = undefined;
|
||||
this._deviceActions = undefined;
|
||||
}
|
||||
|
||||
if (
|
||||
(this._diagnosticDownloadLinks && this._deleteButtons) ||
|
||||
(this._diagnosticDownloadLinks &&
|
||||
this._deleteButtons &&
|
||||
this._deviceActions) ||
|
||||
!this.devices ||
|
||||
!this.deviceId ||
|
||||
!this.entries
|
||||
@@ -214,129 +230,10 @@ export class HaConfigDevicePage extends LitElement {
|
||||
|
||||
this._diagnosticDownloadLinks = Math.random();
|
||||
this._deleteButtons = []; // To prevent re-rendering if no delete buttons
|
||||
this._renderDiagnosticButtons(this._diagnosticDownloadLinks);
|
||||
this._renderDeleteButtons();
|
||||
}
|
||||
|
||||
private async _renderDiagnosticButtons(requestId: number): Promise<void> {
|
||||
if (!isComponentLoaded(this.hass, "diagnostics")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const device = this._device(this.deviceId, this.devices);
|
||||
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
|
||||
let links = await Promise.all(
|
||||
this._integrations(device, this.entries).map(
|
||||
async (entry): Promise<boolean | { link: string; domain: string }> => {
|
||||
if (entry.state !== "loaded") {
|
||||
return false;
|
||||
}
|
||||
let info: DiagnosticInfo;
|
||||
try {
|
||||
info = await fetchDiagnosticHandler(this.hass, entry.domain);
|
||||
} catch (err: any) {
|
||||
if (err.code === "not_found") {
|
||||
return false;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (!info.handlers.device && !info.handlers.config_entry) {
|
||||
return false;
|
||||
}
|
||||
return {
|
||||
link: info.handlers.device
|
||||
? getDeviceDiagnosticsDownloadUrl(entry.entry_id, this.deviceId)
|
||||
: getConfigEntryDiagnosticsDownloadUrl(entry.entry_id),
|
||||
domain: entry.domain,
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
links = links.filter(Boolean);
|
||||
|
||||
if (this._diagnosticDownloadLinks !== requestId) {
|
||||
return;
|
||||
}
|
||||
if (links.length > 0) {
|
||||
this._diagnosticDownloadLinks = (
|
||||
links as { link: string; domain: string }[]
|
||||
).map(
|
||||
(link) => html`
|
||||
<a href=${link.link} @click=${this._signUrl}>
|
||||
<mwc-button>
|
||||
${links.length > 1
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.devices.download_diagnostics_integration`,
|
||||
{
|
||||
integration: domainToName(
|
||||
this.hass.localize,
|
||||
link.domain
|
||||
),
|
||||
}
|
||||
)
|
||||
: this.hass.localize(
|
||||
`ui.panel.config.devices.download_diagnostics`
|
||||
)}
|
||||
</mwc-button>
|
||||
</a>
|
||||
`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private _renderDeleteButtons() {
|
||||
const device = this._device(this.deviceId, this.devices);
|
||||
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
|
||||
const buttons: TemplateResult[] = [];
|
||||
this._integrations(device, this.entries).forEach((entry) => {
|
||||
if (entry.state !== "loaded" || !entry.supports_remove_device) {
|
||||
return;
|
||||
}
|
||||
buttons.push(html`
|
||||
<mwc-button
|
||||
class="warning"
|
||||
.entryId=${entry.entry_id}
|
||||
@click=${this._confirmDeleteEntry}
|
||||
>
|
||||
${buttons.length > 1
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.devices.delete_device_integration`,
|
||||
{
|
||||
integration: domainToName(this.hass.localize, entry.domain),
|
||||
}
|
||||
)
|
||||
: this.hass.localize(`ui.panel.config.devices.delete_device`)}
|
||||
</mwc-button>
|
||||
`);
|
||||
});
|
||||
|
||||
if (buttons.length > 0) {
|
||||
this._deleteButtons = buttons;
|
||||
}
|
||||
}
|
||||
|
||||
private async _confirmDeleteEntry(e: MouseEvent): Promise<void> {
|
||||
const entryId = (e.currentTarget as any).entryId;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
text: this.hass.localize("ui.panel.config.devices.confirm_delete"),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
await removeConfigEntryFromDevice(this.hass!, this.deviceId, entryId);
|
||||
this._deviceActions = [];
|
||||
this._getDiagnosticButtons(this._diagnosticDownloadLinks);
|
||||
this._getDeleteActions();
|
||||
this._getDeviceActions();
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps) {
|
||||
@@ -381,14 +278,18 @@ export class HaConfigDevicePage extends LitElement {
|
||||
: undefined;
|
||||
const area = this._computeArea(this.areas, device);
|
||||
|
||||
const configurationUrlIsHomeAssistant =
|
||||
device.configuration_url?.startsWith("homeassistant://") || false;
|
||||
|
||||
const configurationUrl = configurationUrlIsHomeAssistant
|
||||
? device.configuration_url!.replace("homeassistant://", "/")
|
||||
: device.configuration_url;
|
||||
|
||||
const deviceInfo: TemplateResult[] = [];
|
||||
const deviceAlerts: TemplateResult[] = [];
|
||||
|
||||
const actions = [...(this._deviceActions || [])];
|
||||
if (Array.isArray(this._diagnosticDownloadLinks)) {
|
||||
actions.push(...this._diagnosticDownloadLinks);
|
||||
}
|
||||
if (this._deleteButtons) {
|
||||
actions.push(...this._deleteButtons);
|
||||
}
|
||||
|
||||
const firstDeviceAction = actions.shift();
|
||||
|
||||
if (device.disabled_by) {
|
||||
deviceInfo.push(
|
||||
@@ -407,53 +308,19 @@ export class HaConfigDevicePage extends LitElement {
|
||||
)}
|
||||
</ha-alert>
|
||||
${device.disabled_by === "user"
|
||||
? html` <div class="card-actions" slot="actions">
|
||||
<mwc-button unelevated @click=${this._enableDevice}>
|
||||
${this.hass.localize("ui.common.enable")}
|
||||
</mwc-button>
|
||||
</div>`
|
||||
? html`
|
||||
<div class="card-actions" slot="actions">
|
||||
<mwc-button unelevated @click=${this._enableDevice}>
|
||||
${this.hass.localize("ui.common.enable")}
|
||||
</mwc-button>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
`
|
||||
);
|
||||
}
|
||||
|
||||
const deviceActions: (TemplateResult | string)[] = [];
|
||||
|
||||
if (configurationUrl) {
|
||||
deviceActions.push(html`
|
||||
<a
|
||||
href=${configurationUrl}
|
||||
rel="noopener noreferrer"
|
||||
.target=${configurationUrlIsHomeAssistant ? "_self" : "_blank"}
|
||||
>
|
||||
<mwc-button>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.devices.open_configuration_url_${
|
||||
device.entry_type || "device"
|
||||
}`
|
||||
)}
|
||||
<ha-svg-icon
|
||||
.path=${mdiOpenInNew}
|
||||
slot="trailingIcon"
|
||||
></ha-svg-icon>
|
||||
</mwc-button>
|
||||
</a>
|
||||
`);
|
||||
}
|
||||
|
||||
this._renderIntegrationInfo(
|
||||
device,
|
||||
integrations,
|
||||
deviceInfo,
|
||||
deviceActions
|
||||
);
|
||||
|
||||
if (Array.isArray(this._diagnosticDownloadLinks)) {
|
||||
deviceActions.push(...this._diagnosticDownloadLinks);
|
||||
}
|
||||
if (this._deleteButtons) {
|
||||
deviceActions.push(...this._deleteButtons);
|
||||
}
|
||||
this._renderIntegrationInfo(device, integrations, deviceInfo, deviceAlerts);
|
||||
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
@@ -477,10 +344,6 @@ export class HaConfigDevicePage extends LitElement {
|
||||
`
|
||||
: ""
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="container">
|
||||
<div class="header fullwidth">
|
||||
${
|
||||
@@ -547,6 +410,11 @@ export class HaConfigDevicePage extends LitElement {
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
${
|
||||
deviceAlerts.length
|
||||
? html` <div class="fullwidth">${deviceAlerts}</div> `
|
||||
: ""
|
||||
}
|
||||
<ha-device-info-card
|
||||
.hass=${this.hass}
|
||||
.areas=${this.areas}
|
||||
@@ -555,57 +423,70 @@ export class HaConfigDevicePage extends LitElement {
|
||||
>
|
||||
${deviceInfo}
|
||||
${
|
||||
deviceActions.length
|
||||
firstDeviceAction || actions.length
|
||||
? html`
|
||||
<div class="card-actions" slot="actions">
|
||||
${deviceActions}
|
||||
<div>
|
||||
<a href=${ifDefined(firstDeviceAction!.href)}>
|
||||
<mwc-button
|
||||
class=${ifDefined(firstDeviceAction!.classes)}
|
||||
.action=${firstDeviceAction!.action}
|
||||
@click=${this._deviceActionClicked}
|
||||
>
|
||||
${firstDeviceAction!.label}
|
||||
${firstDeviceAction!.trailingIcon
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
.path=${firstDeviceAction!.trailingIcon}
|
||||
slot="trailingIcon"
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: ""}
|
||||
</mwc-button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
${actions.length
|
||||
? html`
|
||||
<ha-button-menu corner="BOTTOM_START">
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize(
|
||||
"ui.common.menu"
|
||||
)}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
${actions.map(
|
||||
(deviceAction) => html`
|
||||
<a href=${ifDefined(deviceAction.href)}>
|
||||
<mwc-list-item
|
||||
class=${ifDefined(
|
||||
deviceAction.classes
|
||||
)}
|
||||
.action=${deviceAction.action}
|
||||
@click=${this._deviceActionClicked}
|
||||
>
|
||||
${deviceAction.label}
|
||||
${deviceAction.trailingIcon
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
.path=${deviceAction.trailingIcon}
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: ""}
|
||||
</mwc-list-item>
|
||||
</a>
|
||||
`
|
||||
)}
|
||||
</ha-button-menu>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
</ha-device-info-card>
|
||||
</div>
|
||||
<div class="column">
|
||||
${["control", "sensor", "config", "diagnostic"].map((category) =>
|
||||
// Make sure we render controls if no other cards will be rendered
|
||||
entitiesByCategory[category].length > 0 ||
|
||||
(entities.length === 0 && category === "control")
|
||||
? html`
|
||||
<ha-device-entities-card
|
||||
.hass=${this.hass}
|
||||
.header=${this.hass.localize(
|
||||
`ui.panel.config.devices.entities.${category}`
|
||||
)}
|
||||
.deviceName=${deviceName}
|
||||
.entities=${entitiesByCategory[category]}
|
||||
.showHidden=${device.disabled_by !== null}
|
||||
>
|
||||
</ha-device-entities-card>
|
||||
`
|
||||
: ""
|
||||
)}
|
||||
${
|
||||
isComponentLoaded(this.hass, "logbook")
|
||||
? html`
|
||||
<ha-card outlined>
|
||||
<h1 class="card-header">
|
||||
${this.hass.localize("panel.logbook")}
|
||||
</h1>
|
||||
<ha-logbook
|
||||
.hass=${this.hass}
|
||||
.time=${this._logbookTime}
|
||||
.entityIds=${this._entityIds(entities)}
|
||||
.deviceIds=${this._deviceIdInList(this.deviceId)}
|
||||
virtualize
|
||||
narrow
|
||||
no-icon
|
||||
></ha-logbook>
|
||||
</ha-card>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
<div class="column">
|
||||
|
||||
${
|
||||
isComponentLoaded(this.hass, "automation")
|
||||
? html`
|
||||
@@ -874,12 +755,227 @@ export class HaConfigDevicePage extends LitElement {
|
||||
`
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
<div class="column">
|
||||
${["control", "sensor", "config", "diagnostic"].map((category) =>
|
||||
// Make sure we render controls if no other cards will be rendered
|
||||
entitiesByCategory[category].length > 0 ||
|
||||
(entities.length === 0 && category === "control")
|
||||
? html`
|
||||
<ha-device-entities-card
|
||||
.hass=${this.hass}
|
||||
.header=${this.hass.localize(
|
||||
`ui.panel.config.devices.entities.${category}`
|
||||
)}
|
||||
.deviceName=${deviceName}
|
||||
.entities=${entitiesByCategory[category]}
|
||||
.showHidden=${device.disabled_by !== null}
|
||||
>
|
||||
</ha-device-entities-card>
|
||||
`
|
||||
: ""
|
||||
)}
|
||||
</div>
|
||||
<div class="column">
|
||||
${
|
||||
isComponentLoaded(this.hass, "logbook")
|
||||
? html`
|
||||
<ha-card outlined>
|
||||
<h1 class="card-header">
|
||||
${this.hass.localize("panel.logbook")}
|
||||
</h1>
|
||||
<ha-logbook
|
||||
.hass=${this.hass}
|
||||
.time=${this._logbookTime}
|
||||
.entityIds=${this._entityIds(entities)}
|
||||
.deviceIds=${this._deviceIdInList(this.deviceId)}
|
||||
virtualize
|
||||
narrow
|
||||
no-icon
|
||||
></ha-logbook>
|
||||
</ha-card>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</ha-config-section>
|
||||
</hass-tabs-subpage> `;
|
||||
}
|
||||
|
||||
private async _getDiagnosticButtons(requestId: number): Promise<void> {
|
||||
if (!isComponentLoaded(this.hass, "diagnostics")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const device = this._device(this.deviceId, this.devices);
|
||||
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
|
||||
let links = await Promise.all(
|
||||
this._integrations(device, this.entries).map(
|
||||
async (entry): Promise<boolean | { link: string; domain: string }> => {
|
||||
if (entry.state !== "loaded") {
|
||||
return false;
|
||||
}
|
||||
let info: DiagnosticInfo;
|
||||
try {
|
||||
info = await fetchDiagnosticHandler(this.hass, entry.domain);
|
||||
} catch (err: any) {
|
||||
if (err.code === "not_found") {
|
||||
return false;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (!info.handlers.device && !info.handlers.config_entry) {
|
||||
return false;
|
||||
}
|
||||
return {
|
||||
link: info.handlers.device
|
||||
? getDeviceDiagnosticsDownloadUrl(entry.entry_id, this.deviceId)
|
||||
: getConfigEntryDiagnosticsDownloadUrl(entry.entry_id),
|
||||
domain: entry.domain,
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
links = links.filter(Boolean);
|
||||
|
||||
if (this._diagnosticDownloadLinks !== requestId) {
|
||||
return;
|
||||
}
|
||||
if (links.length > 0) {
|
||||
this._diagnosticDownloadLinks = (
|
||||
links as { link: string; domain: string }[]
|
||||
).map((link) => ({
|
||||
href: link.link,
|
||||
action: (ev) => this._signUrl(ev),
|
||||
label:
|
||||
links.length > 1
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.devices.download_diagnostics_integration`,
|
||||
{
|
||||
integration: domainToName(this.hass.localize, link.domain),
|
||||
}
|
||||
)
|
||||
: this.hass.localize(
|
||||
`ui.panel.config.devices.download_diagnostics`
|
||||
),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
private _getDeleteActions() {
|
||||
const device = this._device(this.deviceId, this.devices);
|
||||
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
|
||||
const buttons: DeviceAction[] = [];
|
||||
this._integrations(device, this.entries).forEach((entry) => {
|
||||
if (entry.state !== "loaded" || !entry.supports_remove_device) {
|
||||
return;
|
||||
}
|
||||
buttons.push({
|
||||
action: async () => {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
text: this.hass.localize("ui.panel.config.devices.confirm_delete"),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
await removeConfigEntryFromDevice(
|
||||
this.hass!,
|
||||
this.deviceId,
|
||||
entry.entry_id
|
||||
);
|
||||
},
|
||||
classes: "warning",
|
||||
label:
|
||||
buttons.length > 1
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.devices.delete_device_integration`,
|
||||
{
|
||||
integration: domainToName(this.hass.localize, entry.domain),
|
||||
}
|
||||
)
|
||||
: this.hass.localize(`ui.panel.config.devices.delete_device`),
|
||||
});
|
||||
});
|
||||
|
||||
if (buttons.length > 0) {
|
||||
this._deleteButtons = buttons;
|
||||
}
|
||||
}
|
||||
|
||||
private async _getDeviceActions() {
|
||||
const device = this._device(this.deviceId, this.devices);
|
||||
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
|
||||
const deviceActions: DeviceAction[] = [];
|
||||
|
||||
const configurationUrlIsHomeAssistant =
|
||||
device.configuration_url?.startsWith("homeassistant://") || false;
|
||||
|
||||
const configurationUrl = configurationUrlIsHomeAssistant
|
||||
? device.configuration_url!.replace("homeassistant://", "/")
|
||||
: device.configuration_url;
|
||||
|
||||
if (configurationUrl) {
|
||||
deviceActions.push({
|
||||
href: configurationUrl,
|
||||
label: this.hass.localize(
|
||||
`ui.panel.config.devices.open_configuration_url_${
|
||||
device.entry_type || "device"
|
||||
}`
|
||||
),
|
||||
trailingIcon: mdiOpenInNew,
|
||||
});
|
||||
}
|
||||
|
||||
const domains = this._integrations(device, this.entries).map(
|
||||
(int) => int.domain
|
||||
);
|
||||
|
||||
if (domains.includes("mqtt")) {
|
||||
const mqtt = await import(
|
||||
"./device-detail/integration-elements/mqtt/device-actions"
|
||||
);
|
||||
const actions = mqtt.getMQTTDeviceActions(this, device);
|
||||
deviceActions.push(...actions);
|
||||
}
|
||||
if (domains.includes("zha")) {
|
||||
const zha = await import(
|
||||
"./device-detail/integration-elements/zha/device-actions"
|
||||
);
|
||||
const actions = await zha.getZHADeviceActions(this, this.hass, device);
|
||||
deviceActions.push(...actions);
|
||||
}
|
||||
if (domains.includes("zwave_js")) {
|
||||
const zwave = await import(
|
||||
"./device-detail/integration-elements/zwave_js/device-actions"
|
||||
);
|
||||
const actions = await zwave.getZwaveDeviceActions(
|
||||
this,
|
||||
this.hass,
|
||||
device
|
||||
);
|
||||
deviceActions.push(...actions);
|
||||
}
|
||||
|
||||
this._deviceActions = deviceActions;
|
||||
}
|
||||
|
||||
private _computeEntityName(entity: EntityRegistryEntry) {
|
||||
if (entity.name) {
|
||||
return entity.name;
|
||||
@@ -928,22 +1024,10 @@ export class HaConfigDevicePage extends LitElement {
|
||||
device: DeviceRegistryEntry,
|
||||
integrations: ConfigEntry[],
|
||||
deviceInfo: TemplateResult[],
|
||||
deviceActions: (string | TemplateResult)[]
|
||||
deviceAlerts: TemplateResult[]
|
||||
) {
|
||||
const domains = integrations.map((int) => int.domain);
|
||||
if (domains.includes("mqtt")) {
|
||||
import(
|
||||
"./device-detail/integration-elements/mqtt/ha-device-actions-mqtt"
|
||||
);
|
||||
deviceActions.push(html`
|
||||
<ha-device-actions-mqtt
|
||||
.hass=${this.hass}
|
||||
.device=${device}
|
||||
></ha-device-actions-mqtt>
|
||||
`);
|
||||
}
|
||||
if (domains.includes("zha")) {
|
||||
import("./device-detail/integration-elements/zha/ha-device-actions-zha");
|
||||
import("./device-detail/integration-elements/zha/ha-device-info-zha");
|
||||
deviceInfo.push(html`
|
||||
<ha-device-info-zha
|
||||
@@ -951,32 +1035,26 @@ export class HaConfigDevicePage extends LitElement {
|
||||
.device=${device}
|
||||
></ha-device-info-zha>
|
||||
`);
|
||||
deviceActions.push(html`
|
||||
<ha-device-actions-zha
|
||||
.hass=${this.hass}
|
||||
.device=${device}
|
||||
></ha-device-actions-zha>
|
||||
`);
|
||||
}
|
||||
if (domains.includes("zwave_js")) {
|
||||
import(
|
||||
"./device-detail/integration-elements/zwave_js/ha-device-info-zwave_js"
|
||||
"./device-detail/integration-elements/zwave_js/ha-device-alerts-zwave_js"
|
||||
);
|
||||
import(
|
||||
"./device-detail/integration-elements/zwave_js/ha-device-actions-zwave_js"
|
||||
"./device-detail/integration-elements/zwave_js/ha-device-info-zwave_js"
|
||||
);
|
||||
deviceAlerts.push(html`
|
||||
<ha-device-alerts-zwave_js
|
||||
.hass=${this.hass}
|
||||
.device=${device}
|
||||
></ha-device-alerts-zwave_js>
|
||||
`);
|
||||
deviceInfo.push(html`
|
||||
<ha-device-info-zwave_js
|
||||
.hass=${this.hass}
|
||||
.device=${device}
|
||||
></ha-device-info-zwave_js>
|
||||
`);
|
||||
deviceActions.push(html`
|
||||
<ha-device-actions-zwave_js
|
||||
.hass=${this.hass}
|
||||
.device=${device}
|
||||
></ha-device-actions-zwave_js>
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1115,8 +1193,7 @@ export class HaConfigDevicePage extends LitElement {
|
||||
}
|
||||
|
||||
private async _signUrl(ev) {
|
||||
const anchor = ev.target.closest("a");
|
||||
ev.preventDefault();
|
||||
const anchor = ev.currentTarget.closest("a");
|
||||
const signedUrl = await getSignedPath(
|
||||
this.hass,
|
||||
anchor.getAttribute("href")
|
||||
@@ -1124,6 +1201,16 @@ export class HaConfigDevicePage extends LitElement {
|
||||
fileDownload(signedUrl.path);
|
||||
}
|
||||
|
||||
private _deviceActionClicked(ev) {
|
||||
if (!ev.currentTarget.action) {
|
||||
return;
|
||||
}
|
||||
|
||||
ev.preventDefault();
|
||||
|
||||
(ev.currentTarget as any).action(ev);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
@@ -1154,9 +1241,6 @@ export class HaConfigDevicePage extends LitElement {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.show-more {
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-family: var(--paper-font-headline_-_font-family);
|
||||
@@ -1265,7 +1349,19 @@ export class HaConfigDevicePage extends LitElement {
|
||||
:host([narrow]) ha-logbook {
|
||||
height: 235px;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-config-device-page": HaConfigDevicePage;
|
||||
}
|
||||
}
|
||||
|
@@ -1,14 +1,18 @@
|
||||
import "@material/mwc-list/mwc-list";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiDotsVertical } from "@mdi/js";
|
||||
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import "../../../components/buttons/ha-progress-button";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-clickable-list-item";
|
||||
import "../../../components/ha-icon-next";
|
||||
import "../../../components/ha-settings-row";
|
||||
import { BOARD_NAMES } from "../../../data/hardware";
|
||||
import { BOARD_NAMES, HardwareInfo } from "../../../data/hardware";
|
||||
import {
|
||||
extractApiErrorMessage,
|
||||
ignoreSupervisorError,
|
||||
@@ -28,6 +32,8 @@ import {
|
||||
import "../../../layouts/hass-subpage";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { hardwareBrandsUrl } from "../../../util/brands-url";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import { showhardwareAvailableDialog } from "./show-dialog-hardware-available";
|
||||
|
||||
@customElement("ha-config-hardware")
|
||||
@@ -42,14 +48,36 @@ class HaConfigHardware extends LitElement {
|
||||
|
||||
@state() private _hostData?: HassioHostInfo;
|
||||
|
||||
@state() private _hardwareInfo?: HardwareInfo;
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
if (isComponentLoaded(this.hass, "hassio")) {
|
||||
this._load();
|
||||
}
|
||||
this._load();
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
let boardId: string | undefined;
|
||||
let boardName: string | undefined;
|
||||
let imageURL: string | undefined;
|
||||
let documentationURL: string | undefined;
|
||||
|
||||
if (this._hardwareInfo?.hardware.length) {
|
||||
const boardData = this._hardwareInfo!.hardware[0];
|
||||
|
||||
boardId = boardData.board.hassio_board_id;
|
||||
boardName = boardData.name;
|
||||
documentationURL = boardData.url;
|
||||
imageURL = hardwareBrandsUrl({
|
||||
category: "boards",
|
||||
manufacturer: boardData.board.manufacturer,
|
||||
model: boardData.board.model,
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
});
|
||||
} else if (this._OSData?.board) {
|
||||
boardId = this._OSData.board;
|
||||
boardName = BOARD_NAMES[this._OSData.board];
|
||||
}
|
||||
|
||||
return html`
|
||||
<hass-subpage
|
||||
back-path="/config/system"
|
||||
@@ -68,6 +96,20 @@ class HaConfigHardware extends LitElement {
|
||||
"ui.panel.config.hardware.available_hardware.title"
|
||||
)}</mwc-list-item
|
||||
>
|
||||
${this._hostData
|
||||
? html`
|
||||
<mwc-list-item class="warning" @click=${this._hostReboot}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.hardware.reboot_host"
|
||||
)}</mwc-list-item
|
||||
>
|
||||
<mwc-list-item class="warning" @click=${this._hostShutdown}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.hardware.shutdown_host"
|
||||
)}</mwc-list-item
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
</ha-button-menu>
|
||||
${this._error
|
||||
? html`
|
||||
@@ -76,57 +118,55 @@ class HaConfigHardware extends LitElement {
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
${this._OSData || this._hostData
|
||||
${boardName
|
||||
? html`
|
||||
<div class="content">
|
||||
<ha-card outlined>
|
||||
${this._OSData?.board
|
||||
? html`
|
||||
<div class="card-content">
|
||||
<ha-settings-row>
|
||||
<span slot="heading"
|
||||
>${BOARD_NAMES[this._OSData.board] ||
|
||||
this.hass.localize(
|
||||
"ui.panel.config.hardware.board"
|
||||
)}</span
|
||||
<div class="card-content">
|
||||
<mwc-list>
|
||||
<mwc-list-item
|
||||
graphic=${ifDefined(imageURL ? "medium" : undefined)}
|
||||
.twoline=${Boolean(boardId)}
|
||||
>
|
||||
${imageURL
|
||||
? html`<img slot="graphic" src=${imageURL} />`
|
||||
: ""}
|
||||
<span class="primary-text">
|
||||
${boardName ||
|
||||
this.hass.localize("ui.panel.config.hardware.board")}
|
||||
</span>
|
||||
${boardId
|
||||
? html`
|
||||
<span class="secondary-text" slot="secondary"
|
||||
>${boardId}</span
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
</mwc-list-item>
|
||||
${documentationURL
|
||||
? html`
|
||||
<ha-clickable-list-item
|
||||
.href=${documentationURL}
|
||||
openNewTab
|
||||
twoline
|
||||
hasMeta
|
||||
>
|
||||
<div slot="description">
|
||||
<span class="value">${this._OSData.board}</span>
|
||||
</div>
|
||||
</ha-settings-row>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${this._hostData
|
||||
? html`
|
||||
<div class="card-actions">
|
||||
${this._hostData.features.includes("reboot")
|
||||
? html`
|
||||
<ha-progress-button
|
||||
class="warning"
|
||||
@click=${this._hostReboot}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.hardware.reboot_host"
|
||||
)}
|
||||
</ha-progress-button>
|
||||
`
|
||||
: ""}
|
||||
${this._hostData.features.includes("shutdown")
|
||||
? html`
|
||||
<ha-progress-button
|
||||
class="warning"
|
||||
@click=${this._hostShutdown}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.hardware.shutdown_host"
|
||||
)}
|
||||
</ha-progress-button>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<span
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.hardware.documentation"
|
||||
)}</span
|
||||
>
|
||||
<span slot="secondary"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.hardware.documentation_description"
|
||||
)}</span
|
||||
>
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-clickable-list-item>
|
||||
`
|
||||
: ""}
|
||||
</mwc-list>
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
`
|
||||
@@ -136,9 +176,17 @@ class HaConfigHardware extends LitElement {
|
||||
}
|
||||
|
||||
private async _load() {
|
||||
const isHassioLoaded = isComponentLoaded(this.hass, "hassio");
|
||||
try {
|
||||
this._OSData = await fetchHassioHassOsInfo(this.hass);
|
||||
this._hostData = await fetchHassioHostInfo(this.hass);
|
||||
if (isComponentLoaded(this.hass, "hardware")) {
|
||||
this._hardwareInfo = await this.hass.callWS({ type: "hardware/info" });
|
||||
} else if (isHassioLoaded) {
|
||||
this._OSData = await fetchHassioHassOsInfo(this.hass);
|
||||
}
|
||||
|
||||
if (isHassioLoaded) {
|
||||
this._hostData = await fetchHassioHostInfo(this.hass);
|
||||
}
|
||||
} catch (err: any) {
|
||||
this._error = err.message || err;
|
||||
}
|
||||
@@ -148,10 +196,7 @@ class HaConfigHardware extends LitElement {
|
||||
showhardwareAvailableDialog(this);
|
||||
}
|
||||
|
||||
private async _hostReboot(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
private async _hostReboot(): Promise<void> {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.hass.localize("ui.panel.config.hardware.reboot_host"),
|
||||
text: this.hass.localize("ui.panel.config.hardware.reboot_host_confirm"),
|
||||
@@ -160,10 +205,14 @@ class HaConfigHardware extends LitElement {
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
showToast(this, {
|
||||
message: this.hass.localize("ui.panel.config.hardware.rebooting_host"),
|
||||
duration: 0,
|
||||
});
|
||||
|
||||
try {
|
||||
await rebootHost(this.hass);
|
||||
} catch (err: any) {
|
||||
@@ -177,13 +226,9 @@ class HaConfigHardware extends LitElement {
|
||||
});
|
||||
}
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _hostShutdown(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
private async _hostShutdown(): Promise<void> {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.hass.localize("ui.panel.config.hardware.shutdown_host"),
|
||||
text: this.hass.localize(
|
||||
@@ -194,10 +239,16 @@ class HaConfigHardware extends LitElement {
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
showToast(this, {
|
||||
message: this.hass.localize(
|
||||
"ui.panel.config.hardware.host_shutting_down"
|
||||
),
|
||||
duration: 0,
|
||||
});
|
||||
|
||||
try {
|
||||
await shutdownHost(this.hass);
|
||||
} catch (err: any) {
|
||||
@@ -211,7 +262,6 @@ class HaConfigHardware extends LitElement {
|
||||
});
|
||||
}
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
@@ -234,17 +284,18 @@ class HaConfigHardware extends LitElement {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
padding: 16px 16px 0 16px;
|
||||
padding: 16px;
|
||||
}
|
||||
ha-button-menu {
|
||||
color: var(--secondary-text-color);
|
||||
--mdc-menu-min-width: 200px;
|
||||
}
|
||||
.card-actions {
|
||||
height: 48px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.primary-text {
|
||||
font-size: 16px;
|
||||
}
|
||||
.secondary-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -202,10 +202,9 @@ class DialogZWaveJSHealNetwork extends LitElement {
|
||||
if (!this.hass) {
|
||||
return;
|
||||
}
|
||||
const network: ZWaveJSNetwork = await fetchZwaveNetworkStatus(
|
||||
this.hass!,
|
||||
this.entry_id!
|
||||
);
|
||||
const network: ZWaveJSNetwork = await fetchZwaveNetworkStatus(this.hass!, {
|
||||
entry_id: this.entry_id!,
|
||||
});
|
||||
if (network.controller.is_heal_network_active) {
|
||||
this._status = "started";
|
||||
this._subscribed = subscribeHealZwaveNetworkProgress(
|
||||
|
@@ -22,8 +22,6 @@ import { ZWaveJSHealNodeDialogParams } from "./show-dialog-zwave_js-heal-node";
|
||||
class DialogZWaveJSHealNode extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private entry_id?: string;
|
||||
|
||||
@state() private device?: DeviceRegistryEntry;
|
||||
|
||||
@state() private _status?: string;
|
||||
@@ -31,13 +29,11 @@ class DialogZWaveJSHealNode extends LitElement {
|
||||
@state() private _error?: string;
|
||||
|
||||
public showDialog(params: ZWaveJSHealNodeDialogParams): void {
|
||||
this.entry_id = params.entry_id;
|
||||
this.device = params.device;
|
||||
this._fetchData();
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this.entry_id = undefined;
|
||||
this._status = undefined;
|
||||
this.device = undefined;
|
||||
this._error = undefined;
|
||||
@@ -46,7 +42,7 @@ class DialogZWaveJSHealNode extends LitElement {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.entry_id || !this.device) {
|
||||
if (!this.device) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
@@ -202,10 +198,9 @@ class DialogZWaveJSHealNode extends LitElement {
|
||||
if (!this.hass) {
|
||||
return;
|
||||
}
|
||||
const network: ZWaveJSNetwork = await fetchZwaveNetworkStatus(
|
||||
this.hass!,
|
||||
this.entry_id!
|
||||
);
|
||||
const network: ZWaveJSNetwork = await fetchZwaveNetworkStatus(this.hass!, {
|
||||
device_id: this.device!.id,
|
||||
});
|
||||
if (network.controller.is_heal_network_active) {
|
||||
this._status = "network-healing";
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@ import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { DeviceRegistryEntry } from "../../../../../data/device_registry";
|
||||
|
||||
export interface ZWaveJSHealNodeDialogParams {
|
||||
entry_id: string;
|
||||
device: DeviceRegistryEntry;
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-list/mwc-list";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import {
|
||||
mdiAlertCircle,
|
||||
mdiCheckCircle,
|
||||
@@ -11,9 +12,12 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import "../../../../../components/ha-expansion-panel";
|
||||
import "../../../../../components/ha-fab";
|
||||
import "../../../../../components/ha-help-tooltip";
|
||||
import "../../../../../components/ha-icon-next";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
fetchZwaveDataCollectionStatus,
|
||||
fetchZwaveNetworkStatus,
|
||||
@@ -22,7 +26,9 @@ import {
|
||||
setZwaveDataCollectionPreference,
|
||||
stopZwaveExclusion,
|
||||
stopZwaveInclusion,
|
||||
subscribeZwaveControllerStatistics,
|
||||
ZWaveJSClient,
|
||||
ZWaveJSControllerStatisticsUpdatedMessage,
|
||||
ZWaveJSNetwork,
|
||||
ZwaveJSProvisioningEntry,
|
||||
} from "../../../../../data/zwave_js";
|
||||
@@ -41,9 +47,10 @@ import { showZWaveJSRemoveNodeDialog } from "./show-dialog-zwave_js-remove-node"
|
||||
import { configTabs } from "./zwave_js-config-router";
|
||||
import { showOptionsFlowDialog } from "../../../../../dialogs/config-flow/show-dialog-options-flow";
|
||||
import { computeRTL } from "../../../../../common/util/compute_rtl";
|
||||
import { SubscribeMixin } from "../../../../../mixins/subscribe-mixin";
|
||||
|
||||
@customElement("zwave_js-config-dashboard")
|
||||
class ZWaveJSConfigDashboard extends LitElement {
|
||||
class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
@property({ type: Object }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Object }) public route!: Route;
|
||||
@@ -52,7 +59,7 @@ class ZWaveJSConfigDashboard extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public isWide!: boolean;
|
||||
|
||||
@property() public configEntryId?: string;
|
||||
@property() public configEntryId!: string;
|
||||
|
||||
@state() private _configEntry?: ConfigEntry;
|
||||
|
||||
@@ -66,12 +73,30 @@ class ZWaveJSConfigDashboard extends LitElement {
|
||||
|
||||
@state() private _dataCollectionOptIn?: boolean;
|
||||
|
||||
@state()
|
||||
private _statistics?: ZWaveJSControllerStatisticsUpdatedMessage;
|
||||
|
||||
protected firstUpdated() {
|
||||
if (this.hass) {
|
||||
this._fetchData();
|
||||
}
|
||||
}
|
||||
|
||||
public hassSubscribe(): Array<UnsubscribeFunc | Promise<UnsubscribeFunc>> {
|
||||
return [
|
||||
subscribeZwaveControllerStatistics(
|
||||
this.hass,
|
||||
this.configEntryId,
|
||||
(message) => {
|
||||
if (!this.hasUpdated) {
|
||||
return;
|
||||
}
|
||||
this._statistics = message;
|
||||
}
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._configEntry) {
|
||||
return html``;
|
||||
@@ -211,22 +236,178 @@ class ZWaveJSConfigDashboard extends LitElement {
|
||||
</ha-card>
|
||||
<ha-card header="Diagnostics">
|
||||
<div class="card-content">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.driver_version"
|
||||
)}:
|
||||
${this._network.client.driver_version}<br />
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.server_version"
|
||||
)}:
|
||||
${this._network.client.server_version}<br />
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.home_id"
|
||||
)}:
|
||||
${this._network.controller.home_id}<br />
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.server_url"
|
||||
)}:
|
||||
${this._network.client.ws_server_url}<br />
|
||||
<div class="row">
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.driver_version"
|
||||
)}:
|
||||
</span>
|
||||
<span>${this._network.client.driver_version}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.server_version"
|
||||
)}:
|
||||
</span>
|
||||
<span>${this._network.client.server_version}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.home_id"
|
||||
)}:
|
||||
</span>
|
||||
<span>${this._network.controller.home_id}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.server_url"
|
||||
)}:
|
||||
</span>
|
||||
<span>${this._network.client.ws_server_url}</span>
|
||||
</div>
|
||||
<br />
|
||||
<ha-expansion-panel
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.title"
|
||||
)}
|
||||
>
|
||||
<mwc-list noninteractive>
|
||||
<mwc-list-item twoline hasmeta>
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.messages_tx.label"
|
||||
)}
|
||||
</span>
|
||||
<span slot="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.messages_tx.tooltip"
|
||||
)}
|
||||
</span>
|
||||
<span slot="meta"
|
||||
>${this._statistics?.messages_tx ?? 0}</span
|
||||
>
|
||||
</mwc-list-item>
|
||||
<mwc-list-item twoline hasmeta>
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.messages_rx.label"
|
||||
)}
|
||||
</span>
|
||||
<span slot="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.messages_rx.tooltip"
|
||||
)}
|
||||
</span>
|
||||
<span slot="meta"
|
||||
>${this._statistics?.messages_rx ?? 0}</span
|
||||
>
|
||||
</mwc-list-item>
|
||||
<mwc-list-item twoline hasmeta>
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.messages_dropped_tx.label"
|
||||
)}
|
||||
</span>
|
||||
<span slot="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.messages_dropped_tx.tooltip"
|
||||
)}
|
||||
</span>
|
||||
<span slot="meta"
|
||||
>${this._statistics?.messages_dropped_tx ?? 0}</span
|
||||
>
|
||||
</mwc-list-item>
|
||||
<mwc-list-item twoline hasmeta>
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.messages_dropped_rx.label"
|
||||
)}
|
||||
</span>
|
||||
<span slot="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.messages_dropped_rx.tooltip"
|
||||
)}
|
||||
</span>
|
||||
<span slot="meta"
|
||||
>${this._statistics?.messages_dropped_rx ?? 0}</span
|
||||
>
|
||||
</mwc-list-item>
|
||||
<mwc-list-item twoline hasmeta>
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.nak.label"
|
||||
)}
|
||||
</span>
|
||||
<span slot="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.nak.tooltip"
|
||||
)}
|
||||
</span>
|
||||
<span slot="meta">${this._statistics?.nak ?? 0}</span>
|
||||
</mwc-list-item>
|
||||
<mwc-list-item twoline hasmeta>
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.can.label"
|
||||
)}
|
||||
</span>
|
||||
<span slot="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.can.tooltip"
|
||||
)}
|
||||
</span>
|
||||
<span slot="meta">${this._statistics?.can ?? 0}</span>
|
||||
</mwc-list-item>
|
||||
<mwc-list-item twoline hasmeta>
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.timeout_ack.label"
|
||||
)}
|
||||
</span>
|
||||
<span slot="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.timeout_ack.tooltip"
|
||||
)}
|
||||
</span>
|
||||
<span slot="meta"
|
||||
>${this._statistics?.timeout_ack ?? 0}</span
|
||||
>
|
||||
</mwc-list-item>
|
||||
<mwc-list-item twoline hasmeta>
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.timeout_response.label"
|
||||
)}
|
||||
</span>
|
||||
<span slot="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.timeout_response.tooltip"
|
||||
)}
|
||||
</span>
|
||||
<span slot="meta"
|
||||
>${this._statistics?.timeout_response ?? 0}</span
|
||||
>
|
||||
</mwc-list-item>
|
||||
<mwc-list-item twoline hasmeta>
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.timeout_callback.label"
|
||||
)}
|
||||
</span>
|
||||
<span slot="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.statistics.timeout_callback.tooltip"
|
||||
)}
|
||||
</span>
|
||||
<span slot="meta"
|
||||
>${this._statistics?.timeout_callback ?? 0}</span
|
||||
>
|
||||
</mwc-list-item>
|
||||
</mwc-list>
|
||||
</ha-expansion-panel>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<mwc-button
|
||||
@@ -383,7 +564,7 @@ class ZWaveJSConfigDashboard extends LitElement {
|
||||
domain: "zwave_js",
|
||||
});
|
||||
this._configEntry = configEntries.find(
|
||||
(entry) => entry.entry_id === this.configEntryId!
|
||||
(entry) => entry.entry_id === this.configEntryId
|
||||
);
|
||||
|
||||
if (ERROR_STATES.includes(this._configEntry!.state)) {
|
||||
@@ -392,7 +573,7 @@ class ZWaveJSConfigDashboard extends LitElement {
|
||||
|
||||
const [network, dataCollectionStatus, provisioningEntries] =
|
||||
await Promise.all([
|
||||
fetchZwaveNetworkStatus(this.hass!, this.configEntryId),
|
||||
fetchZwaveNetworkStatus(this.hass!, { entry_id: this.configEntryId }),
|
||||
fetchZwaveDataCollectionStatus(this.hass!, this.configEntryId),
|
||||
fetchZwaveProvisioningEntries(this.hass!, this.configEntryId),
|
||||
]);
|
||||
@@ -508,6 +689,11 @@ class ZWaveJSConfigDashboard extends LitElement {
|
||||
padding-right: 40px;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.network-status div.heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -530,6 +716,10 @@ class ZWaveJSConfigDashboard extends LitElement {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
mwc-list-item {
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
}
|
||||
@@ -546,12 +736,6 @@ class ZWaveJSConfigDashboard extends LitElement {
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
button.dump {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
@@ -166,17 +166,6 @@ class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) {
|
||||
</em>
|
||||
</p>
|
||||
</div>
|
||||
${this._nodeMetadata.comments?.length > 0
|
||||
? html`
|
||||
<div>
|
||||
${this._nodeMetadata.comments.map(
|
||||
(comment) => html`<ha-alert .alertType=${comment.level}>
|
||||
${comment.text}
|
||||
</ha-alert>`
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ``}
|
||||
<ha-card>
|
||||
${Object.entries(this._config).map(
|
||||
([id, item]) => html` <ha-settings-row
|
||||
|
@@ -556,10 +556,6 @@ class HaLogbookRenderer extends LitElement {
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.narrow .date {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.rtl .date {
|
||||
direction: rtl;
|
||||
}
|
||||
@@ -590,6 +586,12 @@ class HaLogbookRenderer extends LitElement {
|
||||
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
button.link {
|
||||
color: var(--paper-item-icon-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.container {
|
||||
@@ -607,7 +609,6 @@ class HaLogbookRenderer extends LitElement {
|
||||
|
||||
.narrow .entry {
|
||||
line-height: 1.5;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.narrow .icon-message state-badge {
|
||||
|
@@ -155,7 +155,7 @@ class HuiGenericEntityRow extends LitElement {
|
||||
</div>`
|
||||
: html``}
|
||||
${this.catchInteraction ?? !DOMAINS_INPUT_ROW.includes(domain)
|
||||
? html` <div
|
||||
? html`<div
|
||||
class="text-content ${classMap({
|
||||
pointer,
|
||||
})}"
|
||||
@@ -165,7 +165,7 @@ class HuiGenericEntityRow extends LitElement {
|
||||
hasDoubleClick: hasAction(this.config!.double_tap_action),
|
||||
})}
|
||||
>
|
||||
<slot></slot>
|
||||
<div class="state"><slot></slot></div>
|
||||
</div>`
|
||||
: html`<slot></slot>`}
|
||||
`;
|
||||
@@ -230,6 +230,12 @@ class HuiGenericEntityRow extends LitElement {
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.state {
|
||||
text-align: right;
|
||||
}
|
||||
.state.rtl {
|
||||
text-align: left;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { union, object, string, optional, boolean, enums } from "superstruct";
|
||||
import { boolean, enums, object, optional, string, union } from "superstruct";
|
||||
import { TIMESTAMP_RENDERING_FORMATS } from "../../components/types";
|
||||
import { actionConfigStruct } from "./action-struct";
|
||||
|
||||
@@ -14,6 +14,8 @@ export const entitiesConfigStruct = union([
|
||||
tap_action: optional(actionConfigStruct),
|
||||
hold_action: optional(actionConfigStruct),
|
||||
double_tap_action: optional(actionConfigStruct),
|
||||
show_icon: optional(boolean()),
|
||||
show_name: optional(boolean()),
|
||||
}),
|
||||
string(),
|
||||
]);
|
||||
|
@@ -158,6 +158,7 @@ export const buttonLinkStyle = css`
|
||||
text-align: left;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
`;
|
||||
|
||||
|
@@ -90,6 +90,8 @@ export class StateCardDisplay extends LitElement {
|
||||
text-align: right;
|
||||
flex: 0 0 auto;
|
||||
overflow-wrap: break-word;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
:host([rtl]) .state {
|
||||
margin-right: 16px;
|
||||
|
@@ -24,8 +24,9 @@ class StateCardInputNumber extends mixinBehaviors(
|
||||
@apply --paper-font-body1;
|
||||
color: var(--primary-text-color);
|
||||
|
||||
text-align: right;
|
||||
line-height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
}
|
||||
.sliderstate {
|
||||
min-width: 45px;
|
||||
|
@@ -23,8 +23,9 @@ class StateCardNumber extends mixinBehaviors(
|
||||
@apply --paper-font-body1;
|
||||
color: var(--primary-text-color);
|
||||
|
||||
text-align: right;
|
||||
line-height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
}
|
||||
.sliderstate {
|
||||
min-width: 45px;
|
||||
|
@@ -1569,12 +1569,16 @@
|
||||
"attributes": "Attributes"
|
||||
},
|
||||
"reboot_host": "Reboot host",
|
||||
"rebooting_host": "Rebooting host",
|
||||
"reboot_host_confirm": "Are you sure you want to reboot your host?",
|
||||
"failed_to_reboot_host": "Failed to reboot host",
|
||||
"shutdown_host": "Shutdown host",
|
||||
"host_shutting_down": "Host shutting down",
|
||||
"shutdown_host_confirm": "Are you sure you want to shutdown your host?",
|
||||
"failed_to_shutdown_host": "Failed to shutdown host",
|
||||
"board": "Board"
|
||||
"board": "Board",
|
||||
"documentation": "Documentation",
|
||||
"documentation_description": "Find extra information about your device"
|
||||
},
|
||||
"info": {
|
||||
"caption": "About",
|
||||
@@ -2905,7 +2909,7 @@
|
||||
},
|
||||
"remove_selected": {
|
||||
"button": "Remove selected",
|
||||
"confirm_title": "Do you want to remove {number} {number, plural,\n one {credential}\n other {credentialss}\n}?",
|
||||
"confirm_title": "Do you want to remove {number} {number, plural,\n one {credential}\n other {credentials}\n}?",
|
||||
"confirm_text": "Application Credentials in use by an integration may not be removed.",
|
||||
"error_title": "Removing Application Credential failed"
|
||||
},
|
||||
@@ -3045,7 +3049,46 @@
|
||||
"server_url": "Server URL",
|
||||
"devices": "{count} {count, plural,\n one {device}\n other {devices}\n}",
|
||||
"provisioned_devices": "Provisioned devices",
|
||||
"not_ready": "{count} not ready"
|
||||
"not_ready": "{count} not ready",
|
||||
"statistics": {
|
||||
"title": "Controller Statistics",
|
||||
"messages_tx": {
|
||||
"label": "Messages TX",
|
||||
"tooltip": "Number of messages successfully sent to the controller"
|
||||
},
|
||||
"messages_rx": {
|
||||
"label": "Messages RX",
|
||||
"tooltip": "Number of messages successfully received by the controller"
|
||||
},
|
||||
"messages_dropped_tx": {
|
||||
"label": "Dropped Messages TX",
|
||||
"tooltip": "Number of messages from the controller that were dropped by the host"
|
||||
},
|
||||
"messages_dropped_rx": {
|
||||
"label": "Dropped Messages RX",
|
||||
"tooltip": "Number of outgoing messages that were dropped because they could not be sent"
|
||||
},
|
||||
"nak": {
|
||||
"label": "NAK",
|
||||
"tooltip": "Number of messages that the controller did not accept"
|
||||
},
|
||||
"can": {
|
||||
"label": "CAN",
|
||||
"tooltip": "Number of collisions while sending a message to the controller"
|
||||
},
|
||||
"timeout_ack": {
|
||||
"label": "Timeout ACK",
|
||||
"tooltip": "Number of transmission attempts where an ACK was missing from the controller"
|
||||
},
|
||||
"timeout_response": {
|
||||
"label": "Timeout Response",
|
||||
"tooltip": "Number of transmission attempts where the controller response did not come in time"
|
||||
},
|
||||
"timeout_callback": {
|
||||
"label": "Timeout Callback",
|
||||
"tooltip": "Number of transmission attempts where the controller callback did not come in time"
|
||||
}
|
||||
}
|
||||
},
|
||||
"device_info": {
|
||||
"zwave_info": "Z-Wave Info",
|
||||
|
@@ -5,9 +5,21 @@ export interface BrandsOptions {
|
||||
darkOptimized?: boolean;
|
||||
}
|
||||
|
||||
export interface HardwareBrandsOptions {
|
||||
category: string;
|
||||
model?: string;
|
||||
manufacturer: string;
|
||||
darkOptimized?: boolean;
|
||||
}
|
||||
|
||||
export const brandsUrl = (options: BrandsOptions): string =>
|
||||
`https://brands.home-assistant.io/${options.useFallback ? "_/" : ""}${
|
||||
options.domain
|
||||
}/${options.darkOptimized ? "dark_" : ""}${options.type}.png`;
|
||||
|
||||
export const hardwareBrandsUrl = (options: HardwareBrandsOptions): string =>
|
||||
`https://brands.home-assistant.io/hardware/${options.category}/${
|
||||
options.darkOptimized ? "dark_" : ""
|
||||
}${options.manufacturer}${options.model ? `_${options.model}` : ""}.png`;
|
||||
|
||||
export const extractDomainFromBrandUrl = (url: string) => url.split("/")[4];
|
||||
|
Reference in New Issue
Block a user