|
@ -74,11 +74,33 @@ jobs:
|
|||
version=$(echo "${{ github.ref }}" | awk -F"/" '{print $NF}' )
|
||||
echo "home-assistant-frontend==$version" > ./requirements.txt
|
||||
|
||||
- name: Build wheels
|
||||
uses: home-assistant/wheels@2022.06.7
|
||||
- name: Upload requirements.txt
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
abi: cp310
|
||||
tag: musllinux_1_2
|
||||
arch: amd64
|
||||
name: requirements
|
||||
path: ./requirements.txt
|
||||
|
||||
build-wheels:
|
||||
name: Build wheels for ${{ matrix.arch }}
|
||||
needs: wheels-init
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
arch: ["aarch64", "armhf", "armv7", "amd64", "i386"]
|
||||
tag:
|
||||
- "3.9-alpine3.14"
|
||||
steps:
|
||||
- name: Download requirements.txt
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: requirements
|
||||
|
||||
- name: Build wheels
|
||||
uses: home-assistant/wheels@master
|
||||
with:
|
||||
tag: ${{ matrix.tag }}
|
||||
arch: ${{ matrix.arch }}
|
||||
wheels-host: ${{ secrets.WHEELS_HOST }}
|
||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
||||
wheels-user: wheels
|
||||
requirements: "requirements.txt"
|
||||
|
|
|
@ -156,12 +156,3 @@ gulp.task("gen-icons-json", (done) => {
|
|||
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task("gen-dummy-icons-json", (done) => {
|
||||
if (!fs.existsSync(OUTPUT_DIR)) {
|
||||
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
fs.writeFileSync(path.resolve(OUTPUT_DIR, "iconList.json"), "[]");
|
||||
done();
|
||||
});
|
||||
|
|
|
@ -9,7 +9,6 @@ require("./compress.js");
|
|||
require("./rollup.js");
|
||||
require("./gather-static.js");
|
||||
require("./translations.js");
|
||||
require("./gen-icons-json.js");
|
||||
|
||||
gulp.task(
|
||||
"develop-hassio",
|
||||
|
@ -18,7 +17,6 @@ gulp.task(
|
|||
process.env.NODE_ENV = "development";
|
||||
},
|
||||
"clean-hassio",
|
||||
"gen-dummy-icons-json",
|
||||
"gen-index-hassio-dev",
|
||||
"build-supervisor-translations",
|
||||
"copy-translations-supervisor",
|
||||
|
@ -35,7 +33,6 @@ gulp.task(
|
|||
process.env.NODE_ENV = "production";
|
||||
},
|
||||
"clean-hassio",
|
||||
"gen-dummy-icons-json",
|
||||
"build-supervisor-translations",
|
||||
"copy-translations-supervisor",
|
||||
"build-locale-data",
|
||||
|
|
|
@ -59,7 +59,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
|||
attributes: {
|
||||
hidden: true,
|
||||
radius: 50,
|
||||
friendly_name: "School",
|
||||
friendly_name: "Skolan",
|
||||
icon: "mdi:school",
|
||||
},
|
||||
},
|
||||
|
@ -137,7 +137,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
|||
state: "73",
|
||||
attributes: {
|
||||
unit_of_measurement: "%",
|
||||
friendly_name: "Oskar battery",
|
||||
friendly_name: "oskar batteri",
|
||||
device_class: "battery",
|
||||
},
|
||||
},
|
||||
|
@ -146,7 +146,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
|||
state: "88",
|
||||
attributes: {
|
||||
unit_of_measurement: "%",
|
||||
friendly_name: "Bella battery",
|
||||
friendly_name: "bella batteri",
|
||||
device_class: "battery",
|
||||
},
|
||||
},
|
||||
|
@ -154,7 +154,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
|||
entity_id: "binary_sensor.unifi_camera",
|
||||
state: "off",
|
||||
attributes: {
|
||||
friendly_name: "Motion sensor camera",
|
||||
friendly_name: "R\u00f6relsesensor kamera",
|
||||
icon: "mdi:walk",
|
||||
},
|
||||
},
|
||||
|
@ -707,7 +707,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
|||
},
|
||||
],
|
||||
cloudiness: 25,
|
||||
friendly_name: "Weather",
|
||||
friendly_name: "V\u00e4der",
|
||||
},
|
||||
},
|
||||
"binary_sensor.ubiquiti_switch": {
|
||||
|
@ -731,7 +731,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
|||
round_trip_time_max: "0.626",
|
||||
round_trip_time_mdev: "",
|
||||
round_trip_time_min: "0.358",
|
||||
friendly_name: "Entrance camera",
|
||||
friendly_name: "Entr\u00e9 kamera",
|
||||
device_class: "connectivity",
|
||||
icon: "mdi:cctv",
|
||||
},
|
||||
|
@ -807,7 +807,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
|||
attributes: {
|
||||
battery_level: 88,
|
||||
on: true,
|
||||
friendly_name: "Back door sensor",
|
||||
friendly_name: "Altand\u00f6rren sensor",
|
||||
device_class: "opening",
|
||||
icon: "mdi:door",
|
||||
},
|
||||
|
@ -841,7 +841,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
|||
battery_level: 60,
|
||||
on: true,
|
||||
dark: true,
|
||||
friendly_name: "Laundy room motion sensor",
|
||||
friendly_name: "R\u00f6relsesensor tv\u00e4ttstugan",
|
||||
device_class: "motion",
|
||||
icon: "mdi:walk",
|
||||
},
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||
|
||||
export const mockConfig = (hass: MockHomeAssistant) => {
|
||||
hass.mockAPI("config/config_entries/entry?domain=co2signal", () => [
|
||||
hass.mockAPI("config/config_entries/entry", () => [
|
||||
{
|
||||
entry_id: "co2signal",
|
||||
domain: "co2signal",
|
||||
|
|
|
@ -466,7 +466,6 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
|
|||
return results;
|
||||
}
|
||||
);
|
||||
mockHass.mockWS("recorder/get_statistics_metadata", () => []);
|
||||
mockHass.mockWS("history/list_statistic_ids", () => []);
|
||||
mockHass.mockWS(
|
||||
"history/statistics_during_period",
|
||||
|
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 94 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 32 KiB |
|
@ -6,8 +6,10 @@ import { atLeastVersion } from "../../../src/common/config/version";
|
|||
import { navigate } from "../../../src/common/navigate";
|
||||
import { caseInsensitiveStringCompare } from "../../../src/common/string/compare";
|
||||
import "../../../src/components/ha-card";
|
||||
import { HassioAddonRepository } from "../../../src/data/hassio/addon";
|
||||
import { StoreAddon } from "../../../src/data/supervisor/store";
|
||||
import {
|
||||
HassioAddonInfo,
|
||||
HassioAddonRepository,
|
||||
} from "../../../src/data/hassio/addon";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import "../components/hassio-card-content";
|
||||
|
@ -21,16 +23,20 @@ class HassioAddonRepositoryEl extends LitElement {
|
|||
|
||||
@property({ attribute: false }) public repo!: HassioAddonRepository;
|
||||
|
||||
@property({ attribute: false }) public addons!: StoreAddon[];
|
||||
@property({ attribute: false }) public addons!: HassioAddonInfo[];
|
||||
|
||||
@property() public filter!: string;
|
||||
|
||||
private _getAddons = memoizeOne((addons: StoreAddon[], filter?: string) => {
|
||||
if (filter) {
|
||||
return filterAndSort(addons, filter);
|
||||
private _getAddons = memoizeOne(
|
||||
(addons: HassioAddonInfo[], filter?: string) => {
|
||||
if (filter) {
|
||||
return filterAndSort(addons, filter);
|
||||
}
|
||||
return addons.sort((a, b) =>
|
||||
caseInsensitiveStringCompare(a.name, b.name)
|
||||
);
|
||||
}
|
||||
return addons.sort((a, b) => caseInsensitiveStringCompare(a.name, b.name));
|
||||
});
|
||||
);
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const repo = this.repo;
|
||||
|
|
|
@ -14,15 +14,15 @@ import memoizeOne from "memoize-one";
|
|||
import { atLeastVersion } from "../../../src/common/config/version";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import { navigate } from "../../../src/common/navigate";
|
||||
import "../../../src/components/search-input";
|
||||
import { extractSearchParam } from "../../../src/common/url/search-params";
|
||||
import "../../../src/components/ha-button-menu";
|
||||
import "../../../src/components/ha-icon-button";
|
||||
import "../../../src/components/search-input";
|
||||
import {
|
||||
HassioAddonInfo,
|
||||
HassioAddonRepository,
|
||||
reloadHassioAddons,
|
||||
} from "../../../src/data/hassio/addon";
|
||||
import { StoreAddon } from "../../../src/data/supervisor/store";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
import "../../../src/layouts/hass-loading-screen";
|
||||
import "../../../src/layouts/hass-subpage";
|
||||
|
@ -66,10 +66,10 @@ class HassioAddonStore extends LitElement {
|
|||
protected render(): TemplateResult {
|
||||
let repos: TemplateResult[] = [];
|
||||
|
||||
if (this.supervisor.store.repositories) {
|
||||
if (this.supervisor.addon.repositories) {
|
||||
repos = this.addonRepositories(
|
||||
this.supervisor.store.repositories,
|
||||
this.supervisor.store.addons,
|
||||
this.supervisor.addon.repositories,
|
||||
this.supervisor.addon.addons,
|
||||
this._filter
|
||||
);
|
||||
}
|
||||
|
@ -145,7 +145,7 @@ class HassioAddonStore extends LitElement {
|
|||
private addonRepositories = memoizeOne(
|
||||
(
|
||||
repositories: HassioAddonRepository[],
|
||||
addons: StoreAddon[],
|
||||
addons: HassioAddonInfo[],
|
||||
filter?: string
|
||||
) =>
|
||||
repositories.sort(sortRepos).map((repo) => {
|
||||
|
|
|
@ -12,17 +12,15 @@ import { navigate } from "../../../src/common/navigate";
|
|||
import { extractSearchParam } from "../../../src/common/url/search-params";
|
||||
import "../../../src/components/ha-circular-progress";
|
||||
import {
|
||||
fetchAddonInfo,
|
||||
fetchHassioAddonInfo,
|
||||
fetchHassioAddonsInfo,
|
||||
HassioAddonDetails,
|
||||
} from "../../../src/data/hassio/addon";
|
||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||
import {
|
||||
addStoreRepository,
|
||||
fetchSupervisorStore,
|
||||
StoreAddonDetails,
|
||||
} from "../../../src/data/supervisor/store";
|
||||
fetchHassioSupervisorInfo,
|
||||
setSupervisorOption,
|
||||
} from "../../../src/data/hassio/supervisor";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
import { showConfirmationDialog } from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import "../../../src/layouts/hass-error-screen";
|
||||
|
@ -47,9 +45,7 @@ class HassioAddonDashboard extends LitElement {
|
|||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
@property({ attribute: false }) public addon?:
|
||||
| HassioAddonDetails
|
||||
| StoreAddonDetails;
|
||||
@property({ attribute: false }) public addon?: HassioAddonDetails;
|
||||
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
|
@ -177,10 +173,10 @@ class HassioAddonDashboard extends LitElement {
|
|||
const requestedAddon = extractSearchParam("addon");
|
||||
const requestedAddonRepository = extractSearchParam("repository_url");
|
||||
if (requestedAddonRepository) {
|
||||
const storeInfo = await fetchSupervisorStore(this.hass);
|
||||
const supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
|
||||
if (
|
||||
!storeInfo.repositories.find(
|
||||
(repo) => repo.source === requestedAddonRepository
|
||||
!supervisorInfo.addons_repositories.find(
|
||||
(repo) => repo === requestedAddonRepository
|
||||
)
|
||||
) {
|
||||
if (
|
||||
|
@ -201,7 +197,12 @@ class HassioAddonDashboard extends LitElement {
|
|||
}
|
||||
|
||||
try {
|
||||
await addStoreRepository(this.hass, requestedAddonRepository);
|
||||
await setSupervisorOption(this.hass, {
|
||||
addons_repositories: [
|
||||
...supervisorInfo.addons_repositories,
|
||||
requestedAddonRepository,
|
||||
],
|
||||
});
|
||||
} catch (err: any) {
|
||||
this._error = extractApiErrorMessage(err);
|
||||
}
|
||||
|
@ -244,8 +245,6 @@ class HassioAddonDashboard extends LitElement {
|
|||
|
||||
if (path === "uninstall") {
|
||||
window.history.back();
|
||||
} else if (path === "install") {
|
||||
this.addon = await fetchHassioAddonInfo(this.hass, this.addon!.slug);
|
||||
} else {
|
||||
await this._routeDataChanged();
|
||||
}
|
||||
|
@ -263,7 +262,8 @@ class HassioAddonDashboard extends LitElement {
|
|||
return;
|
||||
}
|
||||
try {
|
||||
this.addon = await fetchAddonInfo(this.hass, this.supervisor, addon);
|
||||
const addoninfo = await fetchHassioAddonInfo(this.hass, addon);
|
||||
this.addon = addoninfo;
|
||||
} catch (err: any) {
|
||||
this._error = `Error fetching addon info: ${extractApiErrorMessage(err)}`;
|
||||
this.addon = undefined;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { customElement, property } from "lit/decorators";
|
||||
import { HassioAddonDetails } from "../../../src/data/hassio/addon";
|
||||
import { StoreAddonDetails } from "../../../src/data/supervisor/store";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
import {
|
||||
HassRouterPage,
|
||||
|
@ -21,9 +20,7 @@ class HassioAddonRouter extends HassRouterPage {
|
|||
|
||||
@property({ attribute: false }) public supervisor!: Supervisor;
|
||||
|
||||
@property({ attribute: false }) public addon!:
|
||||
| HassioAddonDetails
|
||||
| StoreAddonDetails;
|
||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||
|
||||
protected routerOptions: RouterOptions = {
|
||||
defaultPage: "info",
|
||||
|
|
|
@ -59,10 +59,7 @@ import {
|
|||
fetchHassioStats,
|
||||
HassioStats,
|
||||
} from "../../../../src/data/hassio/common";
|
||||
import {
|
||||
StoreAddon,
|
||||
StoreAddonDetails,
|
||||
} from "../../../../src/data/supervisor/store";
|
||||
import { StoreAddon } from "../../../../src/data/supervisor/store";
|
||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
import {
|
||||
showAlertDialog,
|
||||
|
@ -103,9 +100,7 @@ class HassioAddonInfo extends LitElement {
|
|||
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public addon!:
|
||||
| HassioAddonDetails
|
||||
| StoreAddonDetails;
|
||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||
|
||||
@property({ attribute: false }) public supervisor!: Supervisor;
|
||||
|
||||
|
@ -148,7 +143,7 @@ class HassioAddonInfo extends LitElement {
|
|||
></update-available-card>
|
||||
`
|
||||
: ""}
|
||||
${"protected" in this.addon && !this.addon.protected
|
||||
${!this.addon.protected
|
||||
? html`
|
||||
<ha-alert
|
||||
alert-type="error"
|
||||
|
@ -523,7 +518,7 @@ class HassioAddonInfo extends LitElement {
|
|||
: ""}
|
||||
</div>
|
||||
<div>
|
||||
${this.addon.version && this.addon.state === "started"
|
||||
${this.addon.state === "started"
|
||||
? html`<ha-settings-row ?three-line=${this.narrow}>
|
||||
<span slot="heading">
|
||||
${this.supervisor.localize("addon.dashboard.hostname")}
|
||||
|
@ -674,7 +669,7 @@ class HassioAddonInfo extends LitElement {
|
|||
}
|
||||
|
||||
private async _loadData(): Promise<void> {
|
||||
if ("state" in this.addon && this.addon.state === "started") {
|
||||
if (this.addon.state === "started") {
|
||||
this._metrics = await fetchHassioStats(
|
||||
this.hass,
|
||||
`addons/${this.addon.slug}`
|
||||
|
@ -722,22 +717,18 @@ class HassioAddonInfo extends LitElement {
|
|||
}
|
||||
|
||||
private get _computeIsRunning(): boolean {
|
||||
return (this.addon as HassioAddonDetails)?.state === "started";
|
||||
return this.addon?.state === "started";
|
||||
}
|
||||
|
||||
private get _pathWebui(): string | null {
|
||||
return (this.addon as HassioAddonDetails).webui!.replace(
|
||||
"[HOST]",
|
||||
document.location.hostname
|
||||
return (
|
||||
this.addon.webui &&
|
||||
this.addon.webui.replace("[HOST]", document.location.hostname)
|
||||
);
|
||||
}
|
||||
|
||||
private get _computeShowWebUI(): boolean | "" | null {
|
||||
return (
|
||||
!this.addon.ingress &&
|
||||
(this.addon as HassioAddonDetails).webui &&
|
||||
this._computeIsRunning
|
||||
);
|
||||
return !this.addon.ingress && this.addon.webui && this._computeIsRunning;
|
||||
}
|
||||
|
||||
private _openIngress(): void {
|
||||
|
@ -763,8 +754,7 @@ class HassioAddonInfo extends LitElement {
|
|||
private async _startOnBootToggled(): Promise<void> {
|
||||
this._error = undefined;
|
||||
const data: HassioAddonSetOptionParams = {
|
||||
boot:
|
||||
(this.addon as HassioAddonDetails).boot === "auto" ? "manual" : "auto",
|
||||
boot: this.addon.boot === "auto" ? "manual" : "auto",
|
||||
};
|
||||
try {
|
||||
await setHassioAddonOption(this.hass, this.addon.slug, data);
|
||||
|
@ -786,7 +776,7 @@ class HassioAddonInfo extends LitElement {
|
|||
private async _watchdogToggled(): Promise<void> {
|
||||
this._error = undefined;
|
||||
const data: HassioAddonSetOptionParams = {
|
||||
watchdog: !(this.addon as HassioAddonDetails).watchdog,
|
||||
watchdog: !this.addon.watchdog,
|
||||
};
|
||||
try {
|
||||
await setHassioAddonOption(this.hass, this.addon.slug, data);
|
||||
|
@ -808,7 +798,7 @@ class HassioAddonInfo extends LitElement {
|
|||
private async _autoUpdateToggled(): Promise<void> {
|
||||
this._error = undefined;
|
||||
const data: HassioAddonSetOptionParams = {
|
||||
auto_update: !(this.addon as HassioAddonDetails).auto_update,
|
||||
auto_update: !this.addon.auto_update,
|
||||
};
|
||||
try {
|
||||
await setHassioAddonOption(this.hass, this.addon.slug, data);
|
||||
|
@ -830,7 +820,7 @@ class HassioAddonInfo extends LitElement {
|
|||
private async _protectionToggled(): Promise<void> {
|
||||
this._error = undefined;
|
||||
const data: HassioAddonSetSecurityParams = {
|
||||
protected: !(this.addon as HassioAddonDetails).protected,
|
||||
protected: !this.addon.protected,
|
||||
};
|
||||
try {
|
||||
await setHassioAddonSecurity(this.hass, this.addon.slug, data);
|
||||
|
@ -852,7 +842,7 @@ class HassioAddonInfo extends LitElement {
|
|||
private async _panelToggled(): Promise<void> {
|
||||
this._error = undefined;
|
||||
const data: HassioAddonSetOptionParams = {
|
||||
ingress_panel: !(this.addon as HassioAddonDetails).ingress_panel,
|
||||
ingress_panel: !this.addon.ingress_panel,
|
||||
};
|
||||
try {
|
||||
await setHassioAddonOption(this.hass, this.addon.slug, data);
|
||||
|
@ -880,7 +870,7 @@ class HassioAddonInfo extends LitElement {
|
|||
|
||||
showHassioMarkdownDialog(this, {
|
||||
title: this.supervisor.localize("addon.dashboard.changelog"),
|
||||
content: extractChangelog(this.addon as HassioAddonDetails, content),
|
||||
content: extractChangelog(this.addon, content),
|
||||
});
|
||||
} catch (err: any) {
|
||||
showAlertDialog(this, {
|
||||
|
|
|
@ -98,8 +98,9 @@ export class HassioBackups extends LitElement {
|
|||
if (backup.content.addons.length !== 0) {
|
||||
for (const addon of backup.content.addons) {
|
||||
content.push(
|
||||
this.supervisor.addon.addons.find((entry) => entry.slug === addon)
|
||||
?.name || addon
|
||||
this.supervisor.supervisor.addons.find(
|
||||
(entry) => entry.slug === addon
|
||||
)?.name || addon
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import Fuse from "fuse.js";
|
||||
import { StoreAddon } from "../../../src/data/supervisor/store";
|
||||
import { HassioAddonInfo } from "../../../src/data/hassio/addon";
|
||||
|
||||
export function filterAndSort(addons: StoreAddon[], filter: string) {
|
||||
const options: Fuse.IFuseOptions<StoreAddon> = {
|
||||
export function filterAndSort(addons: HassioAddonInfo[], filter: string) {
|
||||
const options: Fuse.IFuseOptions<HassioAddonInfo> = {
|
||||
keys: ["name", "description", "slug"],
|
||||
isCaseSensitive: false,
|
||||
minMatchCharLength: 2,
|
||||
|
|
|
@ -96,7 +96,7 @@ export class SupervisorBackupContent extends LitElement {
|
|||
: ["ssl", "share", "media", "addons/local"]
|
||||
);
|
||||
this.addons = _computeAddons(
|
||||
this.backup ? this.backup.addons : this.supervisor?.addon.addons
|
||||
this.backup ? this.backup.addons : this.supervisor?.supervisor.addons
|
||||
);
|
||||
this.backupType = this.backup?.type || "full";
|
||||
this.backupName = this.backup?.name || "";
|
||||
|
|
|
@ -24,7 +24,7 @@ class HassioAddons extends LitElement {
|
|||
? html` <h1>${this.supervisor.localize("dashboard.addons")}</h1> `
|
||||
: ""}
|
||||
<div class="card-group">
|
||||
${!this.supervisor.addon.addons.length
|
||||
${!this.supervisor.supervisor.addons?.length
|
||||
? html`
|
||||
<ha-card outlined>
|
||||
<div class="card-content">
|
||||
|
@ -34,7 +34,7 @@ class HassioAddons extends LitElement {
|
|||
</div>
|
||||
</ha-card>
|
||||
`
|
||||
: this.supervisor.addon.addons
|
||||
: this.supervisor.supervisor.addons
|
||||
.sort((a, b) => caseInsensitiveStringCompare(a.name, b.name))
|
||||
.map(
|
||||
(addon) => html`
|
||||
|
|
|
@ -15,18 +15,15 @@ import "../../../../src/components/ha-circular-progress";
|
|||
import { createCloseHeading } from "../../../../src/components/ha-dialog";
|
||||
import "../../../../src/components/ha-icon-button";
|
||||
import {
|
||||
fetchHassioAddonsInfo,
|
||||
HassioAddonInfo,
|
||||
HassioAddonRepository,
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
import { setSupervisorOption } from "../../../../src/data/hassio/supervisor";
|
||||
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
import { HassioRepositoryDialogParams } from "./show-dialog-repositories";
|
||||
import {
|
||||
addStoreRepository,
|
||||
fetchStoreRepositories,
|
||||
removeStoreRepository,
|
||||
} from "../../../../src/data/supervisor/store";
|
||||
|
||||
@customElement("dialog-hassio-repositories")
|
||||
class HassioRepositoriesDialog extends LitElement {
|
||||
|
@ -61,13 +58,7 @@ class HassioRepositoriesDialog extends LitElement {
|
|||
|
||||
private _filteredRepositories = memoizeOne((repos: HassioAddonRepository[]) =>
|
||||
repos
|
||||
.filter(
|
||||
(repo) =>
|
||||
repo.slug !== "core" && // The core add-ons repository
|
||||
repo.slug !== "local" && // Locally managed add-ons
|
||||
repo.slug !== "a0d7b954" && // Home Assistant Community Add-ons
|
||||
repo.slug !== "5c53de3b" // The ESPHome repository
|
||||
)
|
||||
.filter((repo) => repo.slug !== "core" && repo.slug !== "local")
|
||||
.sort((a, b) => caseInsensitiveStringCompare(a.name, b.name))
|
||||
);
|
||||
|
||||
|
@ -87,7 +78,7 @@ class HassioRepositoriesDialog extends LitElement {
|
|||
const repositories = this._filteredRepositories(this._repositories);
|
||||
const usedRepositories = this._filteredUsedRepositories(
|
||||
repositories,
|
||||
this._dialogParams.supervisor.addon.addons
|
||||
this._dialogParams.supervisor.supervisor.addons
|
||||
);
|
||||
return html`
|
||||
<ha-dialog
|
||||
|
@ -224,7 +215,9 @@ class HassioRepositoriesDialog extends LitElement {
|
|||
|
||||
private async _loadData(): Promise<void> {
|
||||
try {
|
||||
this._repositories = await fetchStoreRepositories(this.hass);
|
||||
const addonsinfo = await fetchHassioAddonsInfo(this.hass);
|
||||
|
||||
this._repositories = addonsinfo.repositories;
|
||||
|
||||
fireEvent(this, "supervisor-collection-refresh", { collection: "addon" });
|
||||
} catch (err: any) {
|
||||
|
@ -238,9 +231,14 @@ class HassioRepositoriesDialog extends LitElement {
|
|||
return;
|
||||
}
|
||||
this._processing = true;
|
||||
const repositories = this._filteredRepositories(this._repositories!);
|
||||
const newRepositories = repositories.map((repo) => repo.source);
|
||||
newRepositories.push(input.value);
|
||||
|
||||
try {
|
||||
await addStoreRepository(this.hass, input.value);
|
||||
await setSupervisorOption(this.hass, {
|
||||
addons_repositories: newRepositories,
|
||||
});
|
||||
await this._loadData();
|
||||
|
||||
input.value = "";
|
||||
|
@ -252,8 +250,19 @@ class HassioRepositoriesDialog extends LitElement {
|
|||
|
||||
private async _removeRepository(ev: Event) {
|
||||
const slug = (ev.currentTarget as any).slug;
|
||||
const repositories = this._filteredRepositories(this._repositories!);
|
||||
const repository = repositories.find((repo) => repo.slug === slug);
|
||||
if (!repository) {
|
||||
return;
|
||||
}
|
||||
const newRepositories = repositories
|
||||
.map((repo) => repo.source)
|
||||
.filter((repo) => repo !== repository.source);
|
||||
|
||||
try {
|
||||
await removeStoreRepository(this.hass, slug);
|
||||
await setSupervisorOption(this.hass, {
|
||||
addons_repositories: newRepositories,
|
||||
});
|
||||
await this._loadData();
|
||||
} catch (err: any) {
|
||||
this._error = extractApiErrorMessage(err);
|
||||
|
|
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20220629.0"
|
||||
version = "20220601.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
# Setuptools v62.3 doesn't support editable installs with just 'pyproject.toml' (PEP 660).
|
||||
# Keep this file until it does!
|
|
@ -8,7 +8,6 @@ import {
|
|||
mdiCalendar,
|
||||
mdiCast,
|
||||
mdiCastConnected,
|
||||
mdiChartSankey,
|
||||
mdiCheckCircleOutline,
|
||||
mdiClock,
|
||||
mdiCloseCircleOutline,
|
||||
|
@ -25,7 +24,6 @@ import {
|
|||
mdiPowerPlug,
|
||||
mdiPowerPlugOff,
|
||||
mdiRestart,
|
||||
mdiSwapHorizontal,
|
||||
mdiToggleSwitchVariant,
|
||||
mdiToggleSwitchVariantOff,
|
||||
mdiWeatherNight,
|
||||
|
@ -155,12 +153,6 @@ export const domainIconWithoutDefault = (
|
|||
? FIXED_DOMAIN_ICONS[domain]
|
||||
: mdiWeatherNight;
|
||||
|
||||
case "switch_as_x":
|
||||
return mdiSwapHorizontal;
|
||||
|
||||
case "threshold":
|
||||
return mdiChartSankey;
|
||||
|
||||
case "update":
|
||||
return compareState === "on"
|
||||
? updateIsInstalling(stateObj as UpdateEntity)
|
||||
|
|
|
@ -5,6 +5,6 @@ export const clamp = (value: number, min: number, max: number) =>
|
|||
export const conditionalClamp = (value: number, min?: number, max?: number) => {
|
||||
let result: number;
|
||||
result = min ? Math.max(value, min) : value;
|
||||
result = max ? Math.min(result, max) : result;
|
||||
result = max ? Math.min(value, max) : value;
|
||||
return result;
|
||||
};
|
||||
|
|
|
@ -11,8 +11,6 @@ import { classMap } from "lit/directives/class-map";
|
|||
import { styleMap } from "lit/directives/style-map";
|
||||
import { clamp } from "../../common/number/clamp";
|
||||
|
||||
export const MIN_TIME_BETWEEN_UPDATES = 60 * 5 * 1000;
|
||||
|
||||
interface Tooltip extends TooltipModel<any> {
|
||||
top: string;
|
||||
left: string;
|
||||
|
@ -326,9 +324,6 @@ export default class HaChartBase extends LitElement {
|
|||
width: 16px;
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
margin-inline-end: 6px;
|
||||
margin-inline-start: initial;
|
||||
direction: var(--direction);
|
||||
}
|
||||
.chartTooltip .bullet {
|
||||
align-self: baseline;
|
||||
|
@ -337,9 +332,6 @@ export default class HaChartBase extends LitElement {
|
|||
:host([rtl]) .chartTooltip .bullet {
|
||||
margin-right: inherit;
|
||||
margin-left: 6px;
|
||||
margin-inline-end: inherit;
|
||||
margin-inline-start: 6px;
|
||||
direction: var(--direction);
|
||||
}
|
||||
.chartTooltip {
|
||||
padding: 8px;
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
} from "../../common/number/format_number";
|
||||
import { LineChartEntity, LineChartState } from "../../data/history";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { MIN_TIME_BETWEEN_UPDATES } from "./ha-chart-base";
|
||||
import "./ha-chart-base";
|
||||
|
||||
const safeParseFloat = (value) => {
|
||||
const parsed = parseFloat(value);
|
||||
|
@ -34,8 +34,6 @@ class StateHistoryChartLine extends LitElement {
|
|||
|
||||
@state() private _chartOptions?: ChartOptions;
|
||||
|
||||
private _chartTime: Date = new Date();
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-chart-base
|
||||
|
@ -123,13 +121,7 @@ class StateHistoryChartLine extends LitElement {
|
|||
locale: numberFormatToLocale(this.hass.locale),
|
||||
};
|
||||
}
|
||||
if (
|
||||
changedProps.has("data") ||
|
||||
this._chartTime <
|
||||
new Date(this.endTime.getTime() - MIN_TIME_BETWEEN_UPDATES)
|
||||
) {
|
||||
// If the line is more than 5 minutes old, re-gen it
|
||||
// so the X axis grows even if there is no new data
|
||||
if (changedProps.has("data")) {
|
||||
this._generateData();
|
||||
}
|
||||
}
|
||||
|
@ -143,7 +135,6 @@ class StateHistoryChartLine extends LitElement {
|
|||
return;
|
||||
}
|
||||
|
||||
this._chartTime = new Date();
|
||||
const endTime = this.endTime;
|
||||
const names = this.names || {};
|
||||
entityStates.forEach((states) => {
|
||||
|
|
|
@ -9,7 +9,7 @@ import { numberFormatToLocale } from "../../common/number/format_number";
|
|||
import { computeRTL } from "../../common/util/compute_rtl";
|
||||
import { TimelineEntity } from "../../data/history";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { MIN_TIME_BETWEEN_UPDATES } from "./ha-chart-base";
|
||||
import "./ha-chart-base";
|
||||
import type { TimeLineData } from "./timeline-chart/const";
|
||||
|
||||
/** Binary sensor device classes for which the static colors for on/off are NOT inverted.
|
||||
|
@ -103,8 +103,6 @@ export class StateHistoryChartTimeline extends LitElement {
|
|||
|
||||
@state() private _chartOptions?: ChartOptions<"timeline">;
|
||||
|
||||
private _chartTime: Date = new Date();
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-chart-base
|
||||
|
@ -213,13 +211,7 @@ export class StateHistoryChartTimeline extends LitElement {
|
|||
locale: numberFormatToLocale(this.hass.locale),
|
||||
};
|
||||
}
|
||||
if (
|
||||
changedProps.has("data") ||
|
||||
this._chartTime <
|
||||
new Date(this.endTime.getTime() - MIN_TIME_BETWEEN_UPDATES)
|
||||
) {
|
||||
// If the line is more than 5 minutes old, re-gen it
|
||||
// so the X axis grows even if there is no new data
|
||||
if (changedProps.has("data")) {
|
||||
this._generateData();
|
||||
}
|
||||
}
|
||||
|
@ -232,7 +224,6 @@ export class StateHistoryChartTimeline extends LitElement {
|
|||
stateHistory = [];
|
||||
}
|
||||
|
||||
this._chartTime = new Date();
|
||||
const startTime = this.startTime;
|
||||
const endTime = this.endTime;
|
||||
const labels: string[] = [];
|
||||
|
|
|
@ -12,10 +12,8 @@ import { property, state } from "lit/decorators";
|
|||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { computeActiveState } from "../../common/entity/compute_active_state";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import { iconColorCSS } from "../../common/style/icon_color_css";
|
||||
import { cameraUrlWithWidthHeight } from "../../data/camera";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-state-icon";
|
||||
|
||||
|
@ -95,9 +93,6 @@ export class StateBadge extends LitElement {
|
|||
if (this.hass) {
|
||||
imageUrl = this.hass.hassUrl(imageUrl);
|
||||
}
|
||||
if (computeDomain(stateObj.entity_id) === "camera") {
|
||||
imageUrl = cameraUrlWithWidthHeight(imageUrl, 80, 80);
|
||||
}
|
||||
hostStyle.backgroundImage = `url(${imageUrl})`;
|
||||
this._showIcon = false;
|
||||
} else if (stateObj.state === "on") {
|
||||
|
|
|
@ -4,7 +4,8 @@ import { customElement, property, query, state } from "lit/decorators";
|
|||
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { stringCompare } from "../common/string/compare";
|
||||
import { fetchHassioAddonsInfo, HassioAddonInfo } from "../data/hassio/addon";
|
||||
import { HassioAddonInfo } from "../data/hassio/addon";
|
||||
import { fetchHassioSupervisorInfo } from "../data/hassio/supervisor";
|
||||
import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
|
||||
import { PolymerChangedEvent } from "../polymer-types";
|
||||
import { HomeAssistant } from "../types";
|
||||
|
@ -77,10 +78,10 @@ class HaAddonPicker extends LitElement {
|
|||
private async _getAddons() {
|
||||
try {
|
||||
if (isComponentLoaded(this.hass, "hassio")) {
|
||||
const addonsInfo = await fetchHassioAddonsInfo(this.hass);
|
||||
this._addons = addonsInfo.addons
|
||||
.filter((addon) => addon.version)
|
||||
.sort((a, b) => stringCompare(a.name, b.name));
|
||||
const supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
|
||||
this._addons = supervisorInfo.addons.sort((a, b) =>
|
||||
stringCompare(a.name, b.name)
|
||||
);
|
||||
} else {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
|
|
|
@ -67,7 +67,8 @@ export class HaChip extends LitElement {
|
|||
color: var(--ha-chip-icon-color, var(--ha-chip-text-color));
|
||||
}
|
||||
.mdc-chip.mdc-chip--selected .mdc-chip__checkmark,
|
||||
.mdc-chip .mdc-chip__icon--leading:not(.mdc-chip__icon--leading-hidden) {
|
||||
.mdc-chip.no-text
|
||||
.mdc-chip__icon--leading:not(.mdc-chip__icon--leading-hidden) {
|
||||
margin-right: -4px;
|
||||
margin-inline-start: -4px;
|
||||
margin-inline-end: 4px;
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
import { css, CSSResultGroup, html } from "lit";
|
||||
import { ListItemBase } from "@material/mwc-list/mwc-list-item-base";
|
||||
import { styles } from "@material/mwc-list/mwc-list-item.css";
|
||||
import { css, CSSResult, html } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { HaListItem } from "./ha-list-item";
|
||||
|
||||
@customElement("ha-clickable-list-item")
|
||||
export class HaClickableListItem extends HaListItem {
|
||||
export class HaClickableListItem extends ListItemBase {
|
||||
@property() public href?: string;
|
||||
|
||||
@property({ type: Boolean }) public disableHref = false;
|
||||
|
||||
// property used only in css
|
||||
@property({ type: Boolean, reflect: true }) public rtl = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public openNewTab = false;
|
||||
|
||||
@query("a") private _anchor!: HTMLAnchorElement;
|
||||
|
@ -35,10 +39,18 @@ export class HaClickableListItem extends HaListItem {
|
|||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
super.styles,
|
||||
styles,
|
||||
css`
|
||||
:host {
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
:host([graphic="avatar"]:not([twoLine])),
|
||||
:host([graphic="icon"]:not([twoLine])) {
|
||||
height: 48px;
|
||||
}
|
||||
a {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
@ -48,6 +60,19 @@ export class HaClickableListItem extends HaListItem {
|
|||
padding-right: var(--mdc-list-side-padding, 20px);
|
||||
overflow: hidden;
|
||||
}
|
||||
span.material-icons:first-of-type {
|
||||
margin-inline-start: 0px !important;
|
||||
margin-inline-end: var(
|
||||
--mdc-list-item-graphic-margin,
|
||||
16px
|
||||
) !important;
|
||||
direction: var(--direction);
|
||||
}
|
||||
span.material-icons:last-of-type {
|
||||
margin-inline-start: auto !important;
|
||||
margin-inline-end: 0px !important;
|
||||
direction: var(--direction);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ import memoizeOne from "memoize-one";
|
|||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { loadCodeMirror } from "../resources/codemirror.ondemand";
|
||||
import { HomeAssistant } from "../types";
|
||||
import "./ha-icon";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
|
@ -27,12 +26,6 @@ const saveKeyBinding: KeyBinding = {
|
|||
},
|
||||
};
|
||||
|
||||
const renderIcon = (completion: Completion) => {
|
||||
const icon = document.createElement("ha-icon");
|
||||
icon.icon = completion.label;
|
||||
return icon;
|
||||
};
|
||||
|
||||
@customElement("ha-code-editor")
|
||||
export class HaCodeEditor extends ReactiveElement {
|
||||
public codemirror?: EditorView;
|
||||
|
@ -54,8 +47,6 @@ export class HaCodeEditor extends ReactiveElement {
|
|||
|
||||
private _loadedCodeMirror?: typeof import("../resources/codemirror");
|
||||
|
||||
private _iconList?: Completion[];
|
||||
|
||||
public set value(value: string) {
|
||||
this._value = value;
|
||||
}
|
||||
|
@ -163,10 +154,7 @@ export class HaCodeEditor extends ReactiveElement {
|
|||
if (!this.readOnly && this.autocompleteEntities && this.hass) {
|
||||
extensions.push(
|
||||
this._loadedCodeMirror.autocompletion({
|
||||
override: [
|
||||
this._entityCompletions.bind(this),
|
||||
this._mdiCompletions.bind(this),
|
||||
],
|
||||
override: [this._entityCompletions.bind(this)],
|
||||
maxRenderedOptions: 10,
|
||||
})
|
||||
);
|
||||
|
@ -221,47 +209,6 @@ export class HaCodeEditor extends ReactiveElement {
|
|||
};
|
||||
}
|
||||
|
||||
private _getIconItems = async (): Promise<Completion[]> => {
|
||||
if (!this._iconList) {
|
||||
let iconList: {
|
||||
name: string;
|
||||
keywords: string[];
|
||||
}[];
|
||||
if (__SUPERVISOR__) {
|
||||
iconList = [];
|
||||
} else {
|
||||
iconList = (await import("../../build/mdi/iconList.json")).default;
|
||||
}
|
||||
|
||||
this._iconList = iconList.map((icon) => ({
|
||||
type: "variable",
|
||||
label: `mdi:${icon.name}`,
|
||||
detail: icon.keywords.join(", "),
|
||||
info: renderIcon,
|
||||
}));
|
||||
}
|
||||
|
||||
return this._iconList;
|
||||
};
|
||||
|
||||
private async _mdiCompletions(
|
||||
context: CompletionContext
|
||||
): Promise<CompletionResult | null> {
|
||||
const match = context.matchBefore(/mdi:/);
|
||||
|
||||
if (!match || (match.from === match.to && !context.explicit)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const iconItems = await this._getIconItems();
|
||||
|
||||
return {
|
||||
from: Number(match.from),
|
||||
options: iconItems,
|
||||
span: /^\w*.\w*$/,
|
||||
};
|
||||
}
|
||||
|
||||
private _blockKeyboardShortcuts() {
|
||||
this.addEventListener("keydown", (ev) => ev.stopPropagation());
|
||||
}
|
||||
|
|
|
@ -91,7 +91,6 @@ export class HaDialog extends DialogBase {
|
|||
.header_title {
|
||||
margin-right: 40px;
|
||||
margin-inline-end: 40px;
|
||||
margin-inline-start: initial;
|
||||
direction: var(--direction);
|
||||
}
|
||||
.header_button {
|
||||
|
|
|
@ -19,14 +19,6 @@ export class HaFab extends FabBase {
|
|||
direction: var(--direction);
|
||||
}
|
||||
`,
|
||||
// safari workaround - must be explicit
|
||||
document.dir === "rtl"
|
||||
? css`
|
||||
:host .mdc-fab--extended .mdc-fab__icon {
|
||||
direction: rtl;
|
||||
}
|
||||
`
|
||||
: css``,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -205,9 +205,6 @@ export class HaFormMultiSelect extends LitElement implements HaFormElement {
|
|||
ha-formfield {
|
||||
display: block;
|
||||
padding-right: 16px;
|
||||
padding-inline-end: 16px;
|
||||
padding-inline-start: initial;
|
||||
direction: var(--direction);
|
||||
}
|
||||
ha-textfield {
|
||||
display: block;
|
||||
|
@ -219,9 +216,6 @@ export class HaFormMultiSelect extends LitElement implements HaFormElement {
|
|||
right: 1em;
|
||||
top: 1em;
|
||||
cursor: pointer;
|
||||
inset-inline-end: 1em;
|
||||
inset-inline-start: initial;
|
||||
direction: var(--direction);
|
||||
}
|
||||
:host([opened]) ha-svg-icon {
|
||||
color: var(--primary-color);
|
||||
|
|
|
@ -14,7 +14,6 @@ const getAngle = (value: number, min: number, max: number) => {
|
|||
export interface LevelDefinition {
|
||||
level: number;
|
||||
stroke: string;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
@customElement("ha-gauge")
|
||||
|
@ -39,31 +38,22 @@ export class Gauge extends LitElement {
|
|||
|
||||
@state() private _updated = false;
|
||||
|
||||
@state() private _segment_label? = "";
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
// Wait for the first render for the initial animation to work
|
||||
afterNextRender(() => {
|
||||
this._updated = true;
|
||||
this._angle = getAngle(this.value, this.min, this.max);
|
||||
this._segment_label = this.getSegmentLabel();
|
||||
this._rescale_svg();
|
||||
});
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
super.updated(changedProperties);
|
||||
if (
|
||||
!this._updated ||
|
||||
(!changedProperties.has("value") &&
|
||||
!changedProperties.has("label") &&
|
||||
!changedProperties.has("_segment_label"))
|
||||
) {
|
||||
if (!this._updated || !changedProperties.has("value")) {
|
||||
return;
|
||||
}
|
||||
this._angle = getAngle(this.value, this.min, this.max);
|
||||
this._segment_label = this.getSegmentLabel();
|
||||
this._rescale_svg();
|
||||
}
|
||||
|
||||
|
@ -128,11 +118,9 @@ export class Gauge extends LitElement {
|
|||
</svg>
|
||||
<svg class="text">
|
||||
<text class="value-text">
|
||||
${
|
||||
this._segment_label
|
||||
? this._segment_label
|
||||
: this.valueText || formatNumber(this.value, this.locale)
|
||||
} ${this._segment_label ? "" : this.label}
|
||||
${this.valueText || formatNumber(this.value, this.locale)} ${
|
||||
this.label
|
||||
}
|
||||
</text>
|
||||
</svg>`;
|
||||
}
|
||||
|
@ -149,18 +137,6 @@ export class Gauge extends LitElement {
|
|||
);
|
||||
}
|
||||
|
||||
private getSegmentLabel() {
|
||||
if (this.levels) {
|
||||
this.levels.sort((a, b) => a.level - b.level);
|
||||
for (let i = this.levels.length - 1; i >= 0; i--) {
|
||||
if (this.value >= this.levels[i].level) {
|
||||
return this.levels[i].label;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
:host {
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
import { ListItemBase } from "@material/mwc-list/mwc-list-item-base";
|
||||
import { styles } from "@material/mwc-list/mwc-list-item.css";
|
||||
import { css, CSSResultGroup } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
|
||||
@customElement("ha-list-item")
|
||||
export class HaListItem extends ListItemBase {
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
styles,
|
||||
css`
|
||||
:host {
|
||||
padding-left: var(--mdc-list-side-padding, 20px);
|
||||
padding-right: var(--mdc-list-side-padding, 20px);
|
||||
}
|
||||
:host([graphic="avatar"]:not([twoLine])),
|
||||
:host([graphic="icon"]:not([twoLine])) {
|
||||
height: 48px;
|
||||
}
|
||||
span.material-icons:first-of-type {
|
||||
margin-inline-start: 0px !important;
|
||||
margin-inline-end: var(
|
||||
--mdc-list-item-graphic-margin,
|
||||
16px
|
||||
) !important;
|
||||
direction: var(--direction);
|
||||
}
|
||||
span.material-icons:last-of-type {
|
||||
margin-inline-start: auto !important;
|
||||
margin-inline-end: 0px !important;
|
||||
direction: var(--direction);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-list-item": HaListItem;
|
||||
}
|
||||
}
|
|
@ -79,8 +79,6 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public horizontal = false;
|
||||
|
||||
@state() private _areas?: { [areaId: string]: AreaRegistryEntry };
|
||||
|
||||
@state() private _devices?: {
|
||||
|
@ -119,55 +117,45 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||
if (!this._areas || !this._devices || !this._entities) {
|
||||
return html``;
|
||||
}
|
||||
return html`<div class=${this.horizontal ? "horizontal-container" : ""}>
|
||||
${this.horizontal ? this._renderChips() : this._renderItems()}
|
||||
${this._renderPicker()}
|
||||
${this.horizontal ? this._renderItems() : this._renderChips()}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
private _renderItems() {
|
||||
return html`<div class="mdc-chip-set items">
|
||||
${this.value?.area_id
|
||||
? ensureArray(this.value.area_id).map((area_id) => {
|
||||
const area = this._areas![area_id];
|
||||
return this._renderChip(
|
||||
"area_id",
|
||||
area_id,
|
||||
area?.name || area_id,
|
||||
undefined,
|
||||
mdiSofa
|
||||
);
|
||||
})
|
||||
: ""}
|
||||
${this.value?.device_id
|
||||
? ensureArray(this.value.device_id).map((device_id) => {
|
||||
const device = this._devices![device_id];
|
||||
return this._renderChip(
|
||||
"device_id",
|
||||
device_id,
|
||||
device ? computeDeviceName(device, this.hass) : device_id,
|
||||
undefined,
|
||||
mdiDevices
|
||||
);
|
||||
})
|
||||
: ""}
|
||||
${this.value?.entity_id
|
||||
? ensureArray(this.value.entity_id).map((entity_id) => {
|
||||
const entity = this.hass.states[entity_id];
|
||||
return this._renderChip(
|
||||
"entity_id",
|
||||
entity_id,
|
||||
entity ? computeStateName(entity) : entity_id,
|
||||
entity
|
||||
);
|
||||
})
|
||||
: ""}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
private _renderChips() {
|
||||
return html`<div class="mdc-chip-set">
|
||||
${this.value?.area_id
|
||||
? ensureArray(this.value.area_id).map((area_id) => {
|
||||
const area = this._areas![area_id];
|
||||
return this._renderChip(
|
||||
"area_id",
|
||||
area_id,
|
||||
area?.name || area_id,
|
||||
undefined,
|
||||
mdiSofa
|
||||
);
|
||||
})
|
||||
: ""}
|
||||
${this.value?.device_id
|
||||
? ensureArray(this.value.device_id).map((device_id) => {
|
||||
const device = this._devices![device_id];
|
||||
return this._renderChip(
|
||||
"device_id",
|
||||
device_id,
|
||||
device ? computeDeviceName(device, this.hass) : device_id,
|
||||
undefined,
|
||||
mdiDevices
|
||||
);
|
||||
})
|
||||
: ""}
|
||||
${this.value?.entity_id
|
||||
? ensureArray(this.value.entity_id).map((entity_id) => {
|
||||
const entity = this.hass.states[entity_id];
|
||||
return this._renderChip(
|
||||
"entity_id",
|
||||
entity_id,
|
||||
entity ? computeStateName(entity) : entity_id,
|
||||
entity
|
||||
);
|
||||
})
|
||||
: ""}
|
||||
</div>
|
||||
${this._renderPicker()}
|
||||
<div class="mdc-chip-set">
|
||||
<div
|
||||
class="mdc-chip area_id add"
|
||||
.type=${"area_id"}
|
||||
|
@ -229,6 +217,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${this.helper
|
||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
||||
: ""} `;
|
||||
|
@ -332,7 +321,6 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||
.entityFilter=${this.entityRegFilter}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
.includeDomains=${this.includeDomains}
|
||||
class=${this.horizontal ? "hidden-picker" : ""}
|
||||
@value-changed=${this._targetPicked}
|
||||
></ha-area-picker>`;
|
||||
case "device_id":
|
||||
|
@ -347,7 +335,6 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||
.entityFilter=${this.entityRegFilter}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
.includeDomains=${this.includeDomains}
|
||||
class=${this.horizontal ? "hidden-picker" : ""}
|
||||
@value-changed=${this._targetPicked}
|
||||
></ha-device-picker>`;
|
||||
case "entity_id":
|
||||
|
@ -361,7 +348,6 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||
.entityFilter=${this.entityFilter}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
.includeDomains=${this.includeDomains}
|
||||
class=${this.horizontal ? "hidden-picker" : ""}
|
||||
@value-changed=${this._targetPicked}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>`;
|
||||
|
@ -553,16 +539,6 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
${unsafeCSS(chipStyles)}
|
||||
.hidden-picker {
|
||||
height: 0px;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
}
|
||||
.horizontal-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.mdc-chip {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
|
|
@ -61,11 +61,6 @@ export class HaTextField extends TextFieldBase {
|
|||
padding-inline-end: var(--text-field-suffix-padding-right, 0px);
|
||||
direction: var(--direction);
|
||||
}
|
||||
.mdc-text-field--with-leading-icon {
|
||||
padding-inline-start: var(--text-field-suffix-padding-left, 0px);
|
||||
padding-inline-end: var(--text-field-suffix-padding-right, 16px);
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
.mdc-text-field:not(.mdc-text-field--disabled)
|
||||
.mdc-text-field__affix--suffix {
|
||||
|
@ -76,12 +71,6 @@ export class HaTextField extends TextFieldBase {
|
|||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.mdc-text-field__icon--leading {
|
||||
margin-inline-start: 16px;
|
||||
margin-inline-end: 8px;
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
input {
|
||||
text-align: var(--text-field-text-align);
|
||||
}
|
||||
|
@ -121,25 +110,7 @@ export class HaTextField extends TextFieldBase {
|
|||
inset-inline-end: initial !important;
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
.mdc-text-field__input[type="number"] {
|
||||
direction: var(--direction);
|
||||
}
|
||||
`,
|
||||
// safari workaround - must be explicit
|
||||
document.dir === "rtl"
|
||||
? css`
|
||||
.mdc-text-field__affix--suffix,
|
||||
.mdc-text-field--with-leading-icon,
|
||||
.mdc-text-field__icon--leading,
|
||||
.mdc-floating-label,
|
||||
.mdc-text-field--with-leading-icon.mdc-text-field--filled
|
||||
.mdc-floating-label,
|
||||
.mdc-text-field__input[type="number"] {
|
||||
direction: rtl;
|
||||
}
|
||||
`
|
||||
: css``,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ declare global {
|
|||
class BrowseMediaTTS extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public item!: MediaPlayerItem;
|
||||
@property() public item;
|
||||
|
||||
@property() public action!: MediaPlayerBrowseAction;
|
||||
|
||||
|
|
|
@ -116,6 +116,9 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||
|
||||
private _resizeObserver?: ResizeObserver;
|
||||
|
||||
// @ts-ignore
|
||||
private _intersectionObserver?: IntersectionObserver;
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.updateComplete.then(() => this._attachResizeObserver());
|
||||
|
@ -125,6 +128,9 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||
if (this._resizeObserver) {
|
||||
this._resizeObserver.disconnect();
|
||||
}
|
||||
if (this._intersectionObserver) {
|
||||
this._intersectionObserver.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
public async refresh() {
|
||||
|
@ -479,10 +485,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||
.layout=${grid({
|
||||
itemSize: {
|
||||
width: "175px",
|
||||
height:
|
||||
childrenMediaClass.thumbnail_ratio === "portrait"
|
||||
? "312px"
|
||||
: "225px",
|
||||
height: "225px",
|
||||
},
|
||||
gap: "16px",
|
||||
flex: { preserve: "aspect-ratio" },
|
||||
|
|
|
@ -8,7 +8,6 @@ import { fetchUsers, User } from "../../data/user";
|
|||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-select";
|
||||
import "./ha-user-badge";
|
||||
import "../ha-list-item";
|
||||
|
||||
class HaUserPicker extends LitElement {
|
||||
public hass?: HomeAssistant;
|
||||
|
@ -49,14 +48,14 @@ class HaUserPicker extends LitElement {
|
|||
: ""}
|
||||
${this._sortedUsers(this.users).map(
|
||||
(user) => html`
|
||||
<ha-list-item graphic="avatar" .value=${user.id}>
|
||||
<mwc-list-item graphic="avatar" .value=${user.id}>
|
||||
<ha-user-badge
|
||||
.hass=${this.hass}
|
||||
.user=${user}
|
||||
slot="graphic"
|
||||
></ha-user-badge>
|
||||
${user.name}
|
||||
</ha-list-item>
|
||||
</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-select>
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface ApplicationCredentialsDomainConfig {
|
||||
description_placeholders: string;
|
||||
}
|
||||
|
||||
export interface ApplicationCredentialsConfig {
|
||||
integrations: Record<string, ApplicationCredentialsDomainConfig>;
|
||||
domains: string[];
|
||||
}
|
||||
|
||||
export interface ApplicationCredential {
|
||||
|
|
|
@ -158,14 +158,8 @@ export const getRecentWithCache = (
|
|||
}
|
||||
const stateHistory = computeHistory(hass, fetchedHistory, localize);
|
||||
if (appendingToCache) {
|
||||
if (stateHistory.line.length) {
|
||||
mergeLine(stateHistory.line, cache.data.line);
|
||||
}
|
||||
if (stateHistory.timeline.length) {
|
||||
mergeTimeline(stateHistory.timeline, cache.data.timeline);
|
||||
// Replace the timeline array to force an update
|
||||
cache.data.timeline = [...cache.data.timeline];
|
||||
}
|
||||
mergeLine(stateHistory.line, cache.data.line);
|
||||
mergeTimeline(stateHistory.timeline, cache.data.timeline);
|
||||
pruneStartTime(startTime, cache.data);
|
||||
} else {
|
||||
cache.data = stateHistory;
|
||||
|
@ -197,8 +191,6 @@ const mergeLine = (
|
|||
oldLine.data.push(entity);
|
||||
}
|
||||
});
|
||||
// Replace the cached line data to force an update
|
||||
oldLine.data = [...oldLine.data];
|
||||
} else {
|
||||
cacheLines.push(line);
|
||||
}
|
||||
|
|
|
@ -41,12 +41,6 @@ export interface WebRtcAnswer {
|
|||
answer: string;
|
||||
}
|
||||
|
||||
export const cameraUrlWithWidthHeight = (
|
||||
base_url: string,
|
||||
width: number,
|
||||
height: number
|
||||
) => `${base_url}&width=${width}&height=${height}`;
|
||||
|
||||
export const computeMJPEGStreamUrl = (entity: CameraEntity) =>
|
||||
`/api/camera_proxy_stream/${entity.entity_id}?token=${entity.attributes.access_token}`;
|
||||
|
||||
|
@ -63,7 +57,7 @@ export const fetchThumbnailUrlWithCache = async (
|
|||
hass,
|
||||
entityId
|
||||
);
|
||||
return cameraUrlWithWidthHeight(base_url, width, height);
|
||||
return `${base_url}&width=${width}&height=${height}`;
|
||||
};
|
||||
|
||||
export const fetchThumbnailUrl = async (
|
||||
|
|
|
@ -38,19 +38,19 @@ export const getConfigEntries = (
|
|||
hass: HomeAssistant,
|
||||
filters?: { type?: "helper" | "integration"; domain?: string }
|
||||
): Promise<ConfigEntry[]> => {
|
||||
const params: any = {};
|
||||
const params = new URLSearchParams();
|
||||
if (filters) {
|
||||
if (filters.type) {
|
||||
params.type_filter = filters.type;
|
||||
params.append("type", filters.type);
|
||||
}
|
||||
if (filters.domain) {
|
||||
params.domain = filters.domain;
|
||||
params.append("domain", filters.domain);
|
||||
}
|
||||
}
|
||||
return hass.callWS<ConfigEntry[]>({
|
||||
type: "config_entries/get",
|
||||
...params,
|
||||
});
|
||||
return hass.callApi<ConfigEntry[]>(
|
||||
"GET",
|
||||
`config/config_entries/entry?${params.toString()}`
|
||||
);
|
||||
};
|
||||
|
||||
export const updateConfigEntry = (
|
||||
|
|
|
@ -33,18 +33,6 @@ export interface UpdateEntityRegistryEntryResult {
|
|||
require_restart?: boolean;
|
||||
}
|
||||
|
||||
export interface SensorEntityOptions {
|
||||
unit_of_measurement?: string | null;
|
||||
}
|
||||
|
||||
export interface WeatherEntityOptions {
|
||||
precipitation_unit?: string | null;
|
||||
pressure_unit?: string | null;
|
||||
temperature_unit?: string | null;
|
||||
visibility_unit?: string | null;
|
||||
wind_speed_unit?: string | null;
|
||||
}
|
||||
|
||||
export interface EntityRegistryEntryUpdateParams {
|
||||
name?: string | null;
|
||||
icon?: string | null;
|
||||
|
@ -54,7 +42,9 @@ export interface EntityRegistryEntryUpdateParams {
|
|||
hidden_by: string | null;
|
||||
new_entity_id?: string;
|
||||
options_domain?: string;
|
||||
options?: SensorEntityOptions | WeatherEntityOptions;
|
||||
options?: {
|
||||
unit_of_measurement?: string | null;
|
||||
};
|
||||
}
|
||||
|
||||
export const findBatteryEntity = (
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import { atLeastVersion } from "../../common/config/version";
|
||||
import type { HaFormSchema } from "../../components/ha-form/types";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { supervisorApiCall } from "../supervisor/common";
|
||||
import { StoreAddonDetails } from "../supervisor/store";
|
||||
import { Supervisor, SupervisorArch } from "../supervisor/supervisor";
|
||||
import { SupervisorArch } from "../supervisor/supervisor";
|
||||
import {
|
||||
extractApiErrorMessage,
|
||||
hassioApiResultExtractor,
|
||||
|
@ -365,15 +363,3 @@ export const uninstallHassioAddon = async (
|
|||
`hassio/addons/${slug}/uninstall`
|
||||
);
|
||||
};
|
||||
|
||||
export const fetchAddonInfo = (
|
||||
hass: HomeAssistant,
|
||||
supervisor: Supervisor,
|
||||
addonSlug: string
|
||||
): Promise<HassioAddonDetails | StoreAddonDetails> =>
|
||||
supervisorApiCall(
|
||||
hass,
|
||||
!supervisor.addon?.addons.find((addon) => addon.slug === addonSlug)
|
||||
? `/store/addons/${addonSlug}` // Use /store/addons when add-on is not installed
|
||||
: `/addons/${addonSlug}/info` // Use /addons when add-on is installed
|
||||
);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { atLeastVersion } from "../../common/config/version";
|
||||
import { HomeAssistant, PanelInfo } from "../../types";
|
||||
import { SupervisorArch } from "../supervisor/supervisor";
|
||||
import { HassioAddonInfo } from "./addon";
|
||||
import { hassioApiResultExtractor, HassioResponse } from "./common";
|
||||
|
||||
export type HassioHomeAssistantInfo = {
|
||||
|
@ -21,7 +22,7 @@ export type HassioHomeAssistantInfo = {
|
|||
};
|
||||
|
||||
export type HassioSupervisorInfo = {
|
||||
addons: string[];
|
||||
addons: HassioAddonInfo[];
|
||||
addons_repositories: string[];
|
||||
arch: SupervisorArch;
|
||||
channel: string;
|
||||
|
|
|
@ -223,12 +223,16 @@ export const fetchDate = (
|
|||
hass: HomeAssistant,
|
||||
startTime: Date,
|
||||
endTime: Date,
|
||||
entityIds: string[]
|
||||
entityId?: string
|
||||
): Promise<HassEntity[][]> =>
|
||||
hass.callApi(
|
||||
"GET",
|
||||
`history/period/${startTime.toISOString()}?end_time=${endTime.toISOString()}&minimal_response${
|
||||
entityIds ? `&filter_entity_id=${entityIds.join(",")}` : ``
|
||||
entityId ? `&filter_entity_id=${entityId}` : ``
|
||||
}${
|
||||
entityId && !entityIdHistoryNeedsAttributes(hass, entityId)
|
||||
? `&no_attributes`
|
||||
: ``
|
||||
}`
|
||||
);
|
||||
|
||||
|
@ -236,19 +240,19 @@ export const fetchDateWS = (
|
|||
hass: HomeAssistant,
|
||||
startTime: Date,
|
||||
endTime: Date,
|
||||
entityIds: string[]
|
||||
entityId?: string
|
||||
) => {
|
||||
const params = {
|
||||
type: "history/history_during_period",
|
||||
start_time: startTime.toISOString(),
|
||||
end_time: endTime.toISOString(),
|
||||
minimal_response: true,
|
||||
no_attributes: !entityIds
|
||||
.map((entityId) => entityIdHistoryNeedsAttributes(hass, entityId))
|
||||
.reduce((cur, next) => cur || next, false),
|
||||
no_attributes: !!(
|
||||
entityId && !entityIdHistoryNeedsAttributes(hass, entityId)
|
||||
),
|
||||
};
|
||||
if (entityIds.length !== 0) {
|
||||
return hass.callWS<HistoryStates>({ ...params, entity_ids: entityIds });
|
||||
if (entityId) {
|
||||
return hass.callWS<HistoryStates>({ ...params, entity_ids: [entityId] });
|
||||
}
|
||||
return hass.callWS<HistoryStates>(params);
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
BINARY_STATE_OFF,
|
||||
BINARY_STATE_ON,
|
||||
|
@ -6,14 +6,12 @@ import {
|
|||
} from "../common/const";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { computeStateDisplay } from "../common/entity/compute_state_display";
|
||||
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
||||
import { LocalizeFunc } from "../common/translations/localize";
|
||||
import { HaEntityPickerEntityFilterFunc } from "../components/entity/ha-entity-picker";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { UNAVAILABLE_STATES } from "./entity";
|
||||
|
||||
const LOGBOOK_LOCALIZE_PATH = "ui.components.logbook.messages";
|
||||
export const CONTINUOUS_DOMAINS = ["counter", "proximity", "sensor"];
|
||||
export const CONTINUOUS_DOMAINS = ["proximity", "sensor"];
|
||||
|
||||
export interface LogbookStreamMessage {
|
||||
events: LogbookEntry[];
|
||||
|
@ -177,7 +175,7 @@ export const subscribeLogbook = (
|
|||
endDate: string,
|
||||
entityIds?: string[],
|
||||
deviceIds?: string[]
|
||||
): Promise<() => Promise<void>> => {
|
||||
): Promise<UnsubscribeFunc> => {
|
||||
// If all specified filters are empty lists, we can return an empty list.
|
||||
if (
|
||||
(entityIds || deviceIds) &&
|
||||
|
@ -427,10 +425,3 @@ export const localizeStateMessage = (
|
|||
: state
|
||||
);
|
||||
};
|
||||
|
||||
export const filterLogbookCompatibleEntities: HaEntityPickerEntityFilterFunc = (
|
||||
entity
|
||||
) =>
|
||||
computeStateDomain(entity) !== "sensor" ||
|
||||
(entity.attributes.unit_of_measurement === undefined &&
|
||||
entity.attributes.state_class === undefined);
|
||||
|
|
|
@ -36,7 +36,6 @@ import { supportsFeature } from "../common/entity/supports-feature";
|
|||
import { MediaPlayerItemId } from "../components/media-player/ha-media-player-browse";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { UNAVAILABLE_STATES } from "./entity";
|
||||
import { isTTSMediaSource } from "./tts";
|
||||
|
||||
interface MediaPlayerEntityAttributes extends HassEntityAttributeBase {
|
||||
media_content_id?: string;
|
||||
|
@ -442,29 +441,3 @@ export const handleMediaControlClick = (
|
|||
entity_id: stateObj!.entity_id,
|
||||
}
|
||||
);
|
||||
|
||||
export const mediaPlayerPlayMedia = (
|
||||
hass: HomeAssistant,
|
||||
entity_id: string,
|
||||
media_content_id: string,
|
||||
media_content_type: string,
|
||||
extra: {
|
||||
enqueue?: "play" | "next" | "add" | "replace";
|
||||
announce?: boolean;
|
||||
} = {}
|
||||
) => {
|
||||
// We set text-to-speech to announce.
|
||||
if (
|
||||
!extra.enqueue &&
|
||||
extra.announce === undefined &&
|
||||
isTTSMediaSource(media_content_id)
|
||||
) {
|
||||
extra.announce = true;
|
||||
}
|
||||
return hass.callService("media_player", "play_media", {
|
||||
entity_id,
|
||||
media_content_id,
|
||||
media_content_type,
|
||||
...extra,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
import { atLeastVersion } from "../../common/config/version";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { hassioApiResultExtractor, HassioResponse } from "../hassio/common";
|
||||
|
||||
export interface SupervisorApiCallOptions {
|
||||
method?: "get" | "post" | "delete";
|
||||
data?: Record<string, any>;
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
export const supervisorApiCall = async <T>(
|
||||
hass: HomeAssistant,
|
||||
endpoint: string,
|
||||
options?: SupervisorApiCallOptions
|
||||
): Promise<T> => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
// Websockets was added in 2021.2.4
|
||||
return hass.callWS<T>({
|
||||
type: "supervisor/api",
|
||||
endpoint,
|
||||
method: options?.method || "get",
|
||||
timeout: options?.timeout ?? null,
|
||||
data: options?.data,
|
||||
});
|
||||
}
|
||||
return hassioApiResultExtractor(
|
||||
await hass.callApi<HassioResponse<T>>(
|
||||
// @ts-ignore
|
||||
(options.method || "get").toUpperCase(),
|
||||
`hassio${endpoint}`,
|
||||
options?.data
|
||||
)
|
||||
);
|
||||
};
|
|
@ -1,7 +1,7 @@
|
|||
import { atLeastVersion } from "../../common/config/version";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { AddonStage } from "../hassio/addon";
|
||||
import { supervisorApiCall } from "./common";
|
||||
import { SupervisorArch } from "./supervisor";
|
||||
import { AddonRepository, AddonStage } from "../hassio/addon";
|
||||
import { hassioApiResultExtractor, HassioResponse } from "../hassio/common";
|
||||
|
||||
export interface StoreAddon {
|
||||
advanced: boolean;
|
||||
|
@ -13,34 +13,14 @@ export interface StoreAddon {
|
|||
installed: boolean;
|
||||
logo: boolean;
|
||||
name: string;
|
||||
repository: string;
|
||||
repository: AddonRepository;
|
||||
slug: string;
|
||||
stage: AddonStage;
|
||||
update_available: boolean;
|
||||
url: string;
|
||||
version: string | null;
|
||||
version_latest: string;
|
||||
version: null;
|
||||
}
|
||||
|
||||
export interface StoreAddonDetails extends StoreAddon {
|
||||
apparmor: boolean;
|
||||
arch: SupervisorArch[];
|
||||
auth_api: boolean;
|
||||
detached: boolean;
|
||||
docker_api: boolean;
|
||||
documentation: boolean;
|
||||
full_access: boolean;
|
||||
hassio_api: boolean;
|
||||
hassio_role: string;
|
||||
homeassistant_api: boolean;
|
||||
host_network: boolean;
|
||||
host_pid: boolean;
|
||||
ingress: boolean;
|
||||
long_description: string;
|
||||
rating: number;
|
||||
signed: boolean;
|
||||
}
|
||||
|
||||
interface StoreRepository {
|
||||
maintainer: string;
|
||||
name: string;
|
||||
|
@ -56,25 +36,16 @@ export interface SupervisorStore {
|
|||
|
||||
export const fetchSupervisorStore = async (
|
||||
hass: HomeAssistant
|
||||
): Promise<SupervisorStore> => supervisorApiCall(hass, "/store");
|
||||
): Promise<SupervisorStore> => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: "/store",
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
export const fetchStoreRepositories = async (
|
||||
hass: HomeAssistant
|
||||
): Promise<StoreRepository[]> => supervisorApiCall(hass, "/store/repositories");
|
||||
|
||||
export const addStoreRepository = async (
|
||||
hass: HomeAssistant,
|
||||
repository: string
|
||||
): Promise<void> =>
|
||||
supervisorApiCall(hass, "/store/repositories", {
|
||||
method: "post",
|
||||
data: { repository },
|
||||
});
|
||||
|
||||
export const removeStoreRepository = async (
|
||||
hass: HomeAssistant,
|
||||
repository: string
|
||||
): Promise<void> =>
|
||||
supervisorApiCall(hass, `/store/repositories/${repository}`, {
|
||||
method: "delete",
|
||||
});
|
||||
return hassioApiResultExtractor(
|
||||
await hass.callApi<HassioResponse<SupervisorStore>>("GET", `hassio/store`)
|
||||
);
|
||||
};
|
||||
|
|
|
@ -38,8 +38,7 @@ export type TranslationCategory =
|
|||
| "device_automation"
|
||||
| "mfa_setup"
|
||||
| "system_health"
|
||||
| "device_class"
|
||||
| "application_credentials";
|
||||
| "device_class";
|
||||
|
||||
export const fetchTranslationPreferences = (hass: HomeAssistant) =>
|
||||
fetchFrontendUserData(hass.connection, "language");
|
||||
|
|
|
@ -37,24 +37,14 @@ interface ForecastAttribute {
|
|||
humidity?: number;
|
||||
condition?: string;
|
||||
daytime?: boolean;
|
||||
pressure?: number;
|
||||
wind_speed?: string;
|
||||
}
|
||||
|
||||
interface WeatherEntityAttributes extends HassEntityAttributeBase {
|
||||
attribution?: string;
|
||||
temperature: number;
|
||||
humidity?: number;
|
||||
forecast?: ForecastAttribute[];
|
||||
pressure?: number;
|
||||
temperature?: number;
|
||||
visibility?: number;
|
||||
wind_bearing?: number | string;
|
||||
wind_speed?: number;
|
||||
precipitation_unit: string;
|
||||
pressure_unit: string;
|
||||
temperature_unit: string;
|
||||
visibility_unit: string;
|
||||
wind_speed_unit: string;
|
||||
wind_speed: string;
|
||||
wind_bearing: string;
|
||||
}
|
||||
|
||||
export interface WeatherEntity extends HassEntityBase {
|
||||
|
@ -148,16 +138,16 @@ const cardinalDirections = [
|
|||
"N",
|
||||
];
|
||||
|
||||
const getWindBearingText = (degree: number | string): string => {
|
||||
const degreenum = typeof degree === "number" ? degree : parseInt(degree, 10);
|
||||
const getWindBearingText = (degree: string): string => {
|
||||
const degreenum = parseInt(degree, 10);
|
||||
if (isFinite(degreenum)) {
|
||||
// eslint-disable-next-line no-bitwise
|
||||
return cardinalDirections[(((degreenum + 11.25) / 22.5) | 0) % 16];
|
||||
}
|
||||
return typeof degree === "number" ? degree.toString() : degree;
|
||||
return degree;
|
||||
};
|
||||
|
||||
const getWindBearing = (bearing: number | string): string => {
|
||||
const getWindBearing = (bearing: string): string => {
|
||||
if (bearing != null) {
|
||||
return getWindBearingText(bearing);
|
||||
}
|
||||
|
@ -166,19 +156,14 @@ const getWindBearing = (bearing: number | string): string => {
|
|||
|
||||
export const getWind = (
|
||||
hass: HomeAssistant,
|
||||
stateObj: WeatherEntity,
|
||||
speed?: number,
|
||||
bearing?: number | string
|
||||
speed: string,
|
||||
bearing: string
|
||||
): string => {
|
||||
const speedText =
|
||||
speed !== undefined && speed !== null
|
||||
? `${formatNumber(speed, hass.locale)} ${getWeatherUnit(
|
||||
hass!,
|
||||
stateObj,
|
||||
"wind_speed"
|
||||
)}`
|
||||
: "-";
|
||||
if (bearing !== undefined && bearing !== null) {
|
||||
const speedText = `${formatNumber(speed, hass.locale)} ${getWeatherUnit(
|
||||
hass!,
|
||||
"wind_speed"
|
||||
)}`;
|
||||
if (bearing !== null) {
|
||||
const cardinalDirection = getWindBearing(bearing);
|
||||
return `${speedText} (${
|
||||
hass.localize(
|
||||
|
@ -191,28 +176,13 @@ export const getWind = (
|
|||
|
||||
export const getWeatherUnit = (
|
||||
hass: HomeAssistant,
|
||||
stateObj: WeatherEntity,
|
||||
measure: string
|
||||
): string => {
|
||||
const lengthUnit = hass.config.unit_system.length || "";
|
||||
switch (measure) {
|
||||
case "visibility":
|
||||
return stateObj.attributes.visibility_unit || lengthUnit;
|
||||
return hass.config.unit_system.length || "";
|
||||
case "precipitation":
|
||||
return stateObj.attributes.precipitation_unit || lengthUnit === "km"
|
||||
? "mm"
|
||||
: "in";
|
||||
case "pressure":
|
||||
return stateObj.attributes.pressure_unit || lengthUnit === "km"
|
||||
? "hPa"
|
||||
: "inHg";
|
||||
case "temperature":
|
||||
return (
|
||||
stateObj.attributes.temperature_unit ||
|
||||
hass.config.unit_system.temperature
|
||||
);
|
||||
case "wind_speed":
|
||||
return stateObj.attributes.wind_speed_unit || `${lengthUnit}/h`;
|
||||
return hass.config.unit_system.accumulated_precipitation || "";
|
||||
case "humidity":
|
||||
case "precipitation_probability":
|
||||
return "%";
|
||||
|
@ -257,7 +227,7 @@ export const getSecondaryWeatherAttribute = (
|
|||
`
|
||||
: hass!.localize(`ui.card.weather.attributes.${attribute}`)}
|
||||
${formatNumber(value, hass.locale, { maximumFractionDigits: 1 })}
|
||||
${getWeatherUnit(hass!, stateObj, attribute)}
|
||||
${getWeatherUnit(hass!, attribute)}
|
||||
`;
|
||||
};
|
||||
|
||||
|
@ -292,7 +262,7 @@ const getWeatherExtrema = (
|
|||
return undefined;
|
||||
}
|
||||
|
||||
const unit = getWeatherUnit(hass!, stateObj, "temperature");
|
||||
const unit = getWeatherUnit(hass!, "temperature");
|
||||
|
||||
return html`
|
||||
${tempHigh ? `${formatNumber(tempHigh, hass.locale)} ${unit}` : ""}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { DeviceRegistryEntry } from "./device_registry";
|
||||
|
||||
export enum InclusionState {
|
||||
/** The controller isn't doing anything regarding inclusion. */
|
||||
|
@ -84,23 +85,6 @@ enum Protocols {
|
|||
ZWave = 0,
|
||||
ZWaveLongRange = 1,
|
||||
}
|
||||
|
||||
export enum FirmwareUpdateStatus {
|
||||
Error_Timeout = -1,
|
||||
Error_Checksum = 0,
|
||||
Error_TransmissionFailed = 1,
|
||||
Error_InvalidManufacturerID = 2,
|
||||
Error_InvalidFirmwareID = 3,
|
||||
Error_InvalidFirmwareTarget = 4,
|
||||
Error_InvalidHeaderInformation = 5,
|
||||
Error_InvalidHeaderFormat = 6,
|
||||
Error_InsufficientMemory = 7,
|
||||
Error_InvalidHardwareVersion = 8,
|
||||
OK_WaitingForActivation = 0xfd,
|
||||
OK_NoRestart = 0xfe,
|
||||
OK_RestartPending = 0xff,
|
||||
}
|
||||
|
||||
export interface QRProvisioningInformation {
|
||||
version: QRCodeVersion;
|
||||
securityClasses: SecurityClass[];
|
||||
|
@ -125,6 +109,10 @@ export interface PlannedProvisioningEntry {
|
|||
|
||||
export const MINIMUM_QR_STRING_LENGTH = 52;
|
||||
|
||||
export interface ZWaveJSNodeIdentifiers {
|
||||
home_id: string;
|
||||
node_id: number;
|
||||
}
|
||||
export interface ZWaveJSNetwork {
|
||||
client: ZWaveJSClient;
|
||||
controller: ZWaveJSController;
|
||||
|
@ -163,7 +151,7 @@ export interface ZWaveJSController {
|
|||
export interface ZWaveJSNodeStatus {
|
||||
node_id: number;
|
||||
ready: boolean;
|
||||
status: NodeStatus;
|
||||
status: number;
|
||||
is_secure: boolean | string;
|
||||
is_routing: boolean | null;
|
||||
zwave_plus_version: number | null;
|
||||
|
@ -256,68 +244,6 @@ export interface ZWaveJSControllerStatisticsUpdatedMessage {
|
|||
timeout_callback: number;
|
||||
}
|
||||
|
||||
export enum RssiError {
|
||||
NotAvailable = 127,
|
||||
ReceiverSaturated = 126,
|
||||
NoSignalDetected = 125,
|
||||
}
|
||||
|
||||
export enum ProtocolDataRate {
|
||||
ZWave_9k6 = 0x01,
|
||||
ZWave_40k = 0x02,
|
||||
ZWave_100k = 0x03,
|
||||
LongRange_100k = 0x04,
|
||||
}
|
||||
|
||||
export interface ZWaveJSNodeStatisticsUpdatedMessage {
|
||||
event: "statistics updated";
|
||||
source: "node";
|
||||
commands_tx: number;
|
||||
commands_rx: number;
|
||||
commands_dropped_tx: number;
|
||||
commands_dropped_rx: number;
|
||||
timeout_response: number;
|
||||
rtt: number | null;
|
||||
rssi: RssiError | number | null;
|
||||
lwr: ZWaveJSRouteStatistics | null;
|
||||
nlwr: ZWaveJSRouteStatistics | null;
|
||||
}
|
||||
|
||||
export interface ZWaveJSRouteStatistics {
|
||||
protocol_data_rate: number;
|
||||
repeaters: string[];
|
||||
rssi: RssiError | number | null;
|
||||
repeater_rssi: (RssiError | number)[];
|
||||
route_failed_between: [string, string] | null;
|
||||
}
|
||||
|
||||
export interface ZWaveJSNodeStatusUpdatedMessage {
|
||||
event: "ready" | "wake up" | "sleep" | "dead" | "alive";
|
||||
ready: boolean;
|
||||
status: NodeStatus;
|
||||
}
|
||||
|
||||
export interface ZWaveJSNodeFirmwareUpdateProgressMessage {
|
||||
event: "firmware update progress";
|
||||
sent_fragments: number;
|
||||
total_fragments: number;
|
||||
}
|
||||
|
||||
export interface ZWaveJSNodeFirmwareUpdateFinishedMessage {
|
||||
event: "firmware update finished";
|
||||
status: FirmwareUpdateStatus;
|
||||
wait_time: number;
|
||||
}
|
||||
|
||||
export type ZWaveJSNodeFirmwareUpdateCapabilities =
|
||||
| { firmware_upgradable: false }
|
||||
| {
|
||||
firmware_upgradable: true;
|
||||
firmware_targets: number[];
|
||||
continues_to_function: boolean | null;
|
||||
supports_activation: boolean | null;
|
||||
};
|
||||
|
||||
export interface ZWaveJSRemovedNode {
|
||||
node_id: number;
|
||||
manufacturer: string;
|
||||
|
@ -354,6 +280,25 @@ export interface RequestedGrant {
|
|||
|
||||
export const nodeStatus = ["unknown", "asleep", "awake", "dead", "alive"];
|
||||
|
||||
export interface ZWaveJsMigrationData {
|
||||
migration_device_map: Record<string, string>;
|
||||
zwave_entity_ids: string[];
|
||||
zwave_js_entity_ids: string[];
|
||||
migration_entity_map: Record<string, string>;
|
||||
migrated: boolean;
|
||||
}
|
||||
|
||||
export const migrateZwave = (
|
||||
hass: HomeAssistant,
|
||||
entry_id: string,
|
||||
dry_run = true
|
||||
): Promise<ZWaveJsMigrationData> =>
|
||||
hass.callWS({
|
||||
type: "zwave_js/migrate_zwave",
|
||||
entry_id,
|
||||
dry_run,
|
||||
});
|
||||
|
||||
export const fetchZwaveNetworkStatus = (
|
||||
hass: HomeAssistant,
|
||||
device_or_entry_id: {
|
||||
|
@ -516,19 +461,6 @@ export const fetchZwaveNodeStatus = (
|
|||
device_id,
|
||||
});
|
||||
|
||||
export const subscribeZwaveNodeStatus = (
|
||||
hass: HomeAssistant,
|
||||
device_id: string,
|
||||
callbackFunction: (message: ZWaveJSNodeStatusUpdatedMessage) => void
|
||||
): Promise<UnsubscribeFunc> =>
|
||||
hass.connection.subscribeMessage(
|
||||
(message: any) => callbackFunction(message),
|
||||
{
|
||||
type: "zwave_js/subscribe_node_status",
|
||||
device_id,
|
||||
}
|
||||
);
|
||||
|
||||
export const fetchZwaveNodeMetadata = (
|
||||
hass: HomeAssistant,
|
||||
device_id: string
|
||||
|
@ -626,6 +558,19 @@ export const stopHealZwaveNetwork = (
|
|||
entry_id,
|
||||
});
|
||||
|
||||
export const subscribeZwaveNodeReady = (
|
||||
hass: HomeAssistant,
|
||||
device_id: string,
|
||||
callbackFunction: (message) => void
|
||||
): Promise<UnsubscribeFunc> =>
|
||||
hass.connection.subscribeMessage(
|
||||
(message: any) => callbackFunction(message),
|
||||
{
|
||||
type: "zwave_js/node_ready",
|
||||
device_id,
|
||||
}
|
||||
);
|
||||
|
||||
export const subscribeHealZwaveNetworkProgress = (
|
||||
hass: HomeAssistant,
|
||||
entry_id: string,
|
||||
|
@ -652,96 +597,27 @@ export const subscribeZwaveControllerStatistics = (
|
|||
}
|
||||
);
|
||||
|
||||
export const subscribeZwaveNodeStatistics = (
|
||||
hass: HomeAssistant,
|
||||
device_id: string,
|
||||
callbackFunction: (message: ZWaveJSNodeStatisticsUpdatedMessage) => void
|
||||
): Promise<UnsubscribeFunc> =>
|
||||
hass.connection.subscribeMessage(
|
||||
(message: any) => callbackFunction(message),
|
||||
{
|
||||
type: "zwave_js/subscribe_node_statistics",
|
||||
device_id,
|
||||
}
|
||||
);
|
||||
|
||||
export const fetchZwaveNodeIsFirmwareUpdateInProgress = (
|
||||
hass: HomeAssistant,
|
||||
device_id: string
|
||||
): Promise<boolean> =>
|
||||
hass.callWS({
|
||||
type: "zwave_js/get_firmware_update_progress",
|
||||
device_id,
|
||||
});
|
||||
|
||||
export const fetchZwaveIsAnyFirmwareUpdateInProgress = (
|
||||
hass: HomeAssistant,
|
||||
entry_id: string
|
||||
): Promise<boolean> =>
|
||||
hass.callWS({
|
||||
type: "zwave_js/get_any_firmware_update_progress",
|
||||
entry_id,
|
||||
});
|
||||
|
||||
export const fetchZwaveNodeFirmwareUpdateCapabilities = (
|
||||
hass: HomeAssistant,
|
||||
device_id: string
|
||||
): Promise<ZWaveJSNodeFirmwareUpdateCapabilities> =>
|
||||
hass.callWS({
|
||||
type: "zwave_js/get_firmware_update_capabilities",
|
||||
device_id,
|
||||
});
|
||||
|
||||
export const uploadFirmwareAndBeginUpdate = async (
|
||||
hass: HomeAssistant,
|
||||
device_id: string,
|
||||
file: File,
|
||||
target?: number
|
||||
) => {
|
||||
const fd = new FormData();
|
||||
fd.append("file", file);
|
||||
if (target !== undefined) {
|
||||
fd.append("target", target.toString());
|
||||
export const getZwaveJsIdentifiersFromDevice = (
|
||||
device: DeviceRegistryEntry
|
||||
): ZWaveJSNodeIdentifiers | undefined => {
|
||||
if (!device) {
|
||||
return undefined;
|
||||
}
|
||||
const resp = await hass.fetchWithAuth(
|
||||
`/api/zwave_js/firmware/upload/${device_id}`,
|
||||
{
|
||||
method: "POST",
|
||||
body: fd,
|
||||
}
|
||||
);
|
||||
|
||||
if (resp.status !== 200) {
|
||||
throw new Error(resp.statusText);
|
||||
const zwaveJSIdentifier = device.identifiers.find(
|
||||
(identifier) => identifier[0] === "zwave_js"
|
||||
);
|
||||
if (!zwaveJSIdentifier) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const identifiers = zwaveJSIdentifier[1].split("-");
|
||||
return {
|
||||
node_id: parseInt(identifiers[1]),
|
||||
home_id: identifiers[0],
|
||||
};
|
||||
};
|
||||
|
||||
export const subscribeZwaveNodeFirmwareUpdate = (
|
||||
hass: HomeAssistant,
|
||||
device_id: string,
|
||||
callbackFunction: (
|
||||
message:
|
||||
| ZWaveJSNodeFirmwareUpdateFinishedMessage
|
||||
| ZWaveJSNodeFirmwareUpdateProgressMessage
|
||||
) => void
|
||||
): Promise<UnsubscribeFunc> =>
|
||||
hass.connection.subscribeMessage(
|
||||
(message: any) => callbackFunction(message),
|
||||
{
|
||||
type: "zwave_js/subscribe_firmware_update_status",
|
||||
device_id,
|
||||
}
|
||||
);
|
||||
|
||||
export const abortZwaveNodeFirmwareUpdate = (
|
||||
hass: HomeAssistant,
|
||||
device_id: string
|
||||
): Promise<UnsubscribeFunc> =>
|
||||
hass.callWS({
|
||||
type: "zwave_js/abort_firmware_update",
|
||||
device_id,
|
||||
});
|
||||
|
||||
export type ZWaveJSLogUpdate = ZWaveJSLogMessageUpdate | ZWaveJSLogConfigUpdate;
|
||||
|
||||
interface ZWaveJSLogMessageUpdate {
|
||||
|
|
|
@ -47,14 +47,10 @@ class DomainTogglerDialog
|
|||
hideActions
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this._params.title ||
|
||||
this.hass.localize("ui.dialogs.domain_toggler.title")
|
||||
this.hass.localize("ui.dialogs.domain_toggler.title")
|
||||
)}
|
||||
>
|
||||
${this._params.description
|
||||
? html`<div class="description">${this._params.description}</div>`
|
||||
: ""}
|
||||
<div class="domains">
|
||||
<div>
|
||||
${domains.map(
|
||||
(domain) =>
|
||||
html`
|
||||
|
@ -96,10 +92,7 @@ class DomainTogglerDialog
|
|||
ha-dialog {
|
||||
--mdc-dialog-max-width: 500px;
|
||||
}
|
||||
.description {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.domains {
|
||||
div {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
grid-row-gap: 8px;
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
|
||||
export interface HaDomainTogglerDialogParams {
|
||||
title?: string;
|
||||
description?: string;
|
||||
domains: string[];
|
||||
exposedDomains: string[] | null;
|
||||
toggleDomain: (domain: string, turnOn: boolean) => void;
|
||||
|
|
|
@ -8,6 +8,7 @@ import "../../components/ha-dialog";
|
|||
import "../../components/ha-svg-icon";
|
||||
import "../../components/ha-switch";
|
||||
import { HaTextField } from "../../components/ha-textfield";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { DialogBoxParams } from "./show-dialog-box";
|
||||
|
||||
|
@ -134,34 +135,34 @@ class DialogBox extends LitElement {
|
|||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host([inert]) {
|
||||
pointer-events: initial !important;
|
||||
cursor: initial !important;
|
||||
}
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
padding-top: 6px;
|
||||
padding-bottom: 24px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.no-bottom-padding {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.secondary {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
ha-dialog {
|
||||
--mdc-dialog-heading-ink-color: var(--primary-text-color);
|
||||
--mdc-dialog-content-ink-color: var(--primary-text-color);
|
||||
--justify-action-buttons: space-between;
|
||||
/* Place above other dialogs */
|
||||
--dialog-z-index: 104;
|
||||
}
|
||||
`;
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
:host([inert]) {
|
||||
pointer-events: initial !important;
|
||||
cursor: initial !important;
|
||||
}
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
padding-top: 6px;
|
||||
padding-bottom: 24px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.no-bottom-padding {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.secondary {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
ha-dialog {
|
||||
/* Place above other dialogs */
|
||||
--dialog-z-index: 104;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ export class MoreInfoAlarmControlPanel extends LitElement {
|
|||
id="alarmCode"
|
||||
.label=${this.hass.localize("ui.card.alarm_control_panel.code")}
|
||||
type="password"
|
||||
.inputMode=${this.stateObj.attributes.code_format ===
|
||||
.inputmode=${this.stateObj.attributes.code_format ===
|
||||
FORMAT_NUMBER
|
||||
? "numeric"
|
||||
: "text"}
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
} from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
||||
|
@ -25,8 +26,8 @@ import {
|
|||
handleMediaControlClick,
|
||||
MediaPickedEvent,
|
||||
MediaPlayerEntity,
|
||||
mediaPlayerPlayMedia,
|
||||
SUPPORT_BROWSE_MEDIA,
|
||||
SUPPORT_PLAY_MEDIA,
|
||||
SUPPORT_SELECT_SOUND_MODE,
|
||||
SUPPORT_SELECT_SOURCE,
|
||||
SUPPORT_VOLUME_BUTTONS,
|
||||
|
@ -190,6 +191,14 @@ class MoreInfoMediaPlayer extends LitElement {
|
|||
</div>
|
||||
`
|
||||
: ""}
|
||||
${isComponentLoaded(this.hass, "tts") &&
|
||||
supportsFeature(stateObj, SUPPORT_PLAY_MEDIA)
|
||||
? html`
|
||||
<div class="tts">
|
||||
Text to speech has moved to the media browser.
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
|
@ -296,14 +305,20 @@ class MoreInfoMediaPlayer extends LitElement {
|
|||
action: "play",
|
||||
entityId: this.stateObj!.entity_id,
|
||||
mediaPickedCallback: (pickedMedia: MediaPickedEvent) =>
|
||||
mediaPlayerPlayMedia(
|
||||
this.hass,
|
||||
this.stateObj!.entity_id,
|
||||
this._playMedia(
|
||||
pickedMedia.item.media_content_id,
|
||||
pickedMedia.item.media_content_type
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
private _playMedia(media_content_id: string, media_content_type: string) {
|
||||
this.hass!.callService("media_player", "play_media", {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
media_content_id,
|
||||
media_content_type,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
mdiWaterPercent,
|
||||
mdiWeatherWindy,
|
||||
} from "@mdi/js";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
|
@ -22,7 +23,6 @@ import {
|
|||
getWeatherUnit,
|
||||
getWind,
|
||||
isForecastHourly,
|
||||
WeatherEntity,
|
||||
weatherIcons,
|
||||
} from "../../../data/weather";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
|
@ -31,7 +31,7 @@ import { HomeAssistant } from "../../../types";
|
|||
class MoreInfoWeather extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public stateObj?: WeatherEntity;
|
||||
@property() public stateObj?: HassEntity;
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
if (changedProps.has("stateObj")) {
|
||||
|
@ -58,23 +58,19 @@ class MoreInfoWeather extends LitElement {
|
|||
const hourly = isForecastHourly(this.stateObj.attributes.forecast);
|
||||
|
||||
return html`
|
||||
${this._showValue(this.stateObj.attributes.temperature)
|
||||
? html`
|
||||
<div class="flex">
|
||||
<ha-svg-icon .path=${mdiThermometer}></ha-svg-icon>
|
||||
<div class="main">
|
||||
${this.hass.localize("ui.card.weather.attributes.temperature")}
|
||||
</div>
|
||||
<div>
|
||||
${formatNumber(
|
||||
this.stateObj.attributes.temperature!,
|
||||
this.hass.locale
|
||||
)}
|
||||
${getWeatherUnit(this.hass, this.stateObj, "temperature")}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<div class="flex">
|
||||
<ha-svg-icon .path=${mdiThermometer}></ha-svg-icon>
|
||||
<div class="main">
|
||||
${this.hass.localize("ui.card.weather.attributes.temperature")}
|
||||
</div>
|
||||
<div>
|
||||
${formatNumber(
|
||||
this.stateObj.attributes.temperature,
|
||||
this.hass.locale
|
||||
)}
|
||||
${getWeatherUnit(this.hass, "temperature")}
|
||||
</div>
|
||||
</div>
|
||||
${this._showValue(this.stateObj.attributes.pressure)
|
||||
? html`
|
||||
<div class="flex">
|
||||
|
@ -84,10 +80,10 @@ class MoreInfoWeather extends LitElement {
|
|||
</div>
|
||||
<div>
|
||||
${formatNumber(
|
||||
this.stateObj.attributes.pressure!,
|
||||
this.stateObj.attributes.pressure,
|
||||
this.hass.locale
|
||||
)}
|
||||
${getWeatherUnit(this.hass, this.stateObj, "pressure")}
|
||||
${getWeatherUnit(this.hass, "pressure")}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
@ -101,7 +97,7 @@ class MoreInfoWeather extends LitElement {
|
|||
</div>
|
||||
<div>
|
||||
${formatNumber(
|
||||
this.stateObj.attributes.humidity!,
|
||||
this.stateObj.attributes.humidity,
|
||||
this.hass.locale
|
||||
)}
|
||||
%
|
||||
|
@ -119,8 +115,7 @@ class MoreInfoWeather extends LitElement {
|
|||
<div>
|
||||
${getWind(
|
||||
this.hass,
|
||||
this.stateObj,
|
||||
this.stateObj.attributes.wind_speed!,
|
||||
this.stateObj.attributes.wind_speed,
|
||||
this.stateObj.attributes.wind_bearing
|
||||
)}
|
||||
</div>
|
||||
|
@ -136,10 +131,10 @@ class MoreInfoWeather extends LitElement {
|
|||
</div>
|
||||
<div>
|
||||
${formatNumber(
|
||||
this.stateObj.attributes.visibility!,
|
||||
this.stateObj.attributes.visibility,
|
||||
this.hass.locale
|
||||
)}
|
||||
${getWeatherUnit(this.hass, this.stateObj, "visibility")}
|
||||
${getWeatherUnit(this.hass, "length")}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
@ -178,24 +173,16 @@ class MoreInfoWeather extends LitElement {
|
|||
`}
|
||||
<div class="templow">
|
||||
${this._showValue(item.templow)
|
||||
? `${formatNumber(item.templow!, this.hass.locale)}
|
||||
${getWeatherUnit(
|
||||
this.hass,
|
||||
this.stateObj!,
|
||||
"temperature"
|
||||
)}`
|
||||
? `${formatNumber(item.templow, this.hass.locale)}
|
||||
${getWeatherUnit(this.hass, "temperature")}`
|
||||
: hourly
|
||||
? ""
|
||||
: "—"}
|
||||
</div>
|
||||
<div class="temp">
|
||||
${this._showValue(item.temperature)
|
||||
? `${formatNumber(item.temperature!, this.hass.locale)}
|
||||
${getWeatherUnit(
|
||||
this.hass,
|
||||
this.stateObj!,
|
||||
"temperature"
|
||||
)}`
|
||||
? `${formatNumber(item.temperature, this.hass.locale)}
|
||||
${getWeatherUnit(this.hass, "temperature")}`
|
||||
: "—"}
|
||||
</div>
|
||||
</div>`
|
||||
|
@ -253,7 +240,7 @@ class MoreInfoWeather extends LitElement {
|
|||
`;
|
||||
}
|
||||
|
||||
private _showValue(item: number | string | undefined): boolean {
|
||||
private _showValue(item: string): boolean {
|
||||
return typeof item !== "undefined" && item !== null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ import "../../components/ha-circular-progress";
|
|||
import "../../components/ha-header-bar";
|
||||
import "../../components/ha-icon-button";
|
||||
import "../../components/ha-textfield";
|
||||
import { fetchHassioAddonsInfo } from "../../data/hassio/addon";
|
||||
import { fetchHassioSupervisorInfo } from "../../data/hassio/supervisor";
|
||||
import { domainToName } from "../../data/integration";
|
||||
import { getPanelNameTranslationKey } from "../../data/panel";
|
||||
import { PageNavigation } from "../../layouts/hass-tabs-subpage";
|
||||
|
@ -586,7 +586,7 @@ export class QuickBar extends LitElement {
|
|||
const sectionItems = this._generateNavigationConfigSectionCommands();
|
||||
const supervisorItems: BaseNavigationCommand[] = [];
|
||||
if (isComponentLoaded(this.hass, "hassio")) {
|
||||
const addonsInfo = await fetchHassioAddonsInfo(this.hass);
|
||||
const supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
|
||||
supervisorItems.push({
|
||||
path: "/hassio/store",
|
||||
primaryText: this.hass.localize(
|
||||
|
@ -599,7 +599,7 @@ export class QuickBar extends LitElement {
|
|||
"ui.dialogs.quick-bar.commands.navigation.addon_dashboard"
|
||||
),
|
||||
});
|
||||
for (const addon of addonsInfo.addons.filter((a) => a.version)) {
|
||||
for (const addon of supervisorInfo.addons) {
|
||||
supervisorItems.push({
|
||||
path: `/hassio/addon/${addon.slug}`,
|
||||
primaryText: this.hass.localize(
|
||||
|
@ -803,9 +803,6 @@ export class QuickBar extends LitElement {
|
|||
|
||||
span.command-text {
|
||||
margin-left: 8px;
|
||||
margin-inline-start: 8px;
|
||||
margin-inline-end: initial;
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
mwc-list-item {
|
||||
|
|
|
@ -194,7 +194,6 @@ export const provideHass = (
|
|||
socket: {
|
||||
readyState: WebSocket.OPEN,
|
||||
},
|
||||
haVersion: "DEMO",
|
||||
} as any,
|
||||
connected: true,
|
||||
states: {},
|
||||
|
|
|
@ -7,12 +7,10 @@ import { fireEvent } from "../../../common/dom/fire_event";
|
|||
import "../../../components/ha-circular-progress";
|
||||
import "../../../components/ha-combo-box";
|
||||
import { createCloseHeading } from "../../../components/ha-dialog";
|
||||
import "../../../components/ha-markdown";
|
||||
import "../../../components/ha-textfield";
|
||||
import {
|
||||
fetchApplicationCredentialsConfig,
|
||||
createApplicationCredential,
|
||||
ApplicationCredentialsConfig,
|
||||
ApplicationCredential,
|
||||
} from "../../../data/application_credential";
|
||||
import { domainToName } from "../../../data/integration";
|
||||
|
@ -44,22 +42,17 @@ export class DialogAddApplicationCredential extends LitElement {
|
|||
|
||||
@state() private _name?: string;
|
||||
|
||||
@state() private _description?: string;
|
||||
|
||||
@state() private _clientId?: string;
|
||||
|
||||
@state() private _clientSecret?: string;
|
||||
|
||||
@state() private _domains?: Domain[];
|
||||
|
||||
@state() private _config?: ApplicationCredentialsConfig;
|
||||
|
||||
public showDialog(params: AddApplicationCredentialDialogParams) {
|
||||
this._params = params;
|
||||
this._domain =
|
||||
params.selectedDomain !== undefined ? params.selectedDomain : "";
|
||||
this._name = "";
|
||||
this._description = "";
|
||||
this._clientId = "";
|
||||
this._clientSecret = "";
|
||||
this._error = undefined;
|
||||
|
@ -68,15 +61,11 @@ export class DialogAddApplicationCredential extends LitElement {
|
|||
}
|
||||
|
||||
private async _fetchConfig() {
|
||||
this._config = await fetchApplicationCredentialsConfig(this.hass);
|
||||
this._domains = Object.keys(this._config.integrations).map((domain) => ({
|
||||
const config = await fetchApplicationCredentialsConfig(this.hass);
|
||||
this._domains = config.domains.map((domain) => ({
|
||||
id: domain,
|
||||
name: domainToName(this.hass.localize, domain),
|
||||
}));
|
||||
await this.hass.loadBackendTranslation("application_credentials");
|
||||
if (this._domain !== "") {
|
||||
this._updateDescription();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
|
@ -114,12 +103,6 @@ export class DialogAddApplicationCredential extends LitElement {
|
|||
required
|
||||
@value-changed=${this._handleDomainPicked}
|
||||
></ha-combo-box>
|
||||
${this._description
|
||||
? html`<ha-markdown
|
||||
breaks
|
||||
.content=${this._description}
|
||||
></ha-markdown>`
|
||||
: ""}
|
||||
<ha-textfield
|
||||
class="name"
|
||||
name="name"
|
||||
|
@ -185,18 +168,9 @@ export class DialogAddApplicationCredential extends LitElement {
|
|||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
private _handleDomainPicked(ev: CustomEvent) {
|
||||
private async _handleDomainPicked(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
this._domain = ev.detail.value;
|
||||
this._updateDescription();
|
||||
}
|
||||
|
||||
private _updateDescription() {
|
||||
const info = this._config!.integrations[this._domain!];
|
||||
this._description = this.hass.localize(
|
||||
`component.${this._domain}.application_credentials.description`,
|
||||
info.description_placeholders
|
||||
);
|
||||
}
|
||||
|
||||
private _handleValueChanged(ev: CustomEvent) {
|
||||
|
|
|
@ -232,7 +232,7 @@ class CloudAlexa extends SubscribeMixin(LitElement) {
|
|||
slot="toolbar-icon"
|
||||
@click=${this._openDomainToggler}
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.cloud.alexa.manage_defaults"
|
||||
"ui.panel.config.cloud.alexa.manage_domains"
|
||||
)}</mwc-button
|
||||
>
|
||||
`
|
||||
|
@ -402,10 +402,6 @@ class CloudAlexa extends SubscribeMixin(LitElement) {
|
|||
|
||||
private _openDomainToggler() {
|
||||
showDomainTogglerDialog(this, {
|
||||
title: this.hass!.localize("ui.panel.config.cloud.alexa.manage_defaults"),
|
||||
description: this.hass!.localize(
|
||||
"ui.panel.config.cloud.alexa.manage_defaults_dialog_description"
|
||||
),
|
||||
domains: this._entities!.map((entity) =>
|
||||
computeDomain(entity.entity_id)
|
||||
).filter((value, idx, self) => self.indexOf(value) === idx),
|
||||
|
|
|
@ -256,7 +256,7 @@ class CloudGoogleAssistant extends SubscribeMixin(LitElement) {
|
|||
slot="toolbar-icon"
|
||||
@click=${this._openDomainToggler}
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.cloud.google.manage_defaults"
|
||||
"ui.panel.config.cloud.google.manage_domains"
|
||||
)}</mwc-button
|
||||
>
|
||||
`
|
||||
|
@ -442,12 +442,6 @@ class CloudGoogleAssistant extends SubscribeMixin(LitElement) {
|
|||
|
||||
private _openDomainToggler() {
|
||||
showDomainTogglerDialog(this, {
|
||||
title: this.hass!.localize(
|
||||
"ui.panel.config.cloud.google.manage_defaults"
|
||||
),
|
||||
description: this.hass!.localize(
|
||||
"ui.panel.config.cloud.google.manage_defaults_dialog_description"
|
||||
),
|
||||
domains: this._entities!.map((entity) =>
|
||||
computeDomain(entity.entity_id)
|
||||
).filter((value, idx, self) => self.indexOf(value) === idx),
|
||||
|
|
|
@ -273,15 +273,6 @@ class HaConfigSectionGeneral extends LitElement {
|
|||
}
|
||||
button.progress = true;
|
||||
|
||||
let locationConfig;
|
||||
|
||||
if (this._location) {
|
||||
locationConfig = {
|
||||
latitude: this._location[0],
|
||||
longitude: this._location[1],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
await saveCoreConfig(this.hass, {
|
||||
currency: this._currency,
|
||||
|
@ -289,7 +280,6 @@ class HaConfigSectionGeneral extends LitElement {
|
|||
unit_system: this._unitSystem,
|
||||
time_zone: this._timeZone,
|
||||
location_name: this._name,
|
||||
...locationConfig,
|
||||
});
|
||||
button.actionSuccess();
|
||||
} catch (err: any) {
|
||||
|
|
|
@ -139,6 +139,12 @@ class HaConfigSystemNavigation extends LitElement {
|
|||
hasSecondary
|
||||
></ha-navigation-list>
|
||||
</ha-card>
|
||||
${this.hass.userData?.showAdvanced
|
||||
? html`<ha-tip>
|
||||
Looking for YAML Configuration? It has moved to
|
||||
<a href="/developer-tools/yaml">Developer Tools</a>
|
||||
</ha-tip>`
|
||||
: ""}
|
||||
</ha-config-section>
|
||||
</hass-subpage>
|
||||
`;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-list/mwc-list";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
|
@ -9,7 +10,6 @@ import "../../../components/ha-icon-next";
|
|||
import type { UpdateEntity } from "../../../data/update";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "../../../components/ha-circular-progress";
|
||||
import "../../../components/ha-list-item";
|
||||
|
||||
@customElement("ha-config-updates")
|
||||
class HaConfigUpdates extends LitElement {
|
||||
|
@ -39,7 +39,7 @@ class HaConfigUpdates extends LitElement {
|
|||
<mwc-list>
|
||||
${updates.map(
|
||||
(entity) => html`
|
||||
<ha-list-item
|
||||
<mwc-list-item
|
||||
twoline
|
||||
graphic="avatar"
|
||||
class=${entity.attributes.skipped_version ? "skipped" : ""}
|
||||
|
@ -87,7 +87,7 @@ class HaConfigUpdates extends LitElement {
|
|||
></ha-circular-progress>`
|
||||
: html`<ha-icon-next slot="meta"></ha-icon-next>`
|
||||
: ""}
|
||||
</ha-list-item>
|
||||
</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</mwc-list>
|
||||
|
@ -135,7 +135,7 @@ class HaConfigUpdates extends LitElement {
|
|||
outline: none;
|
||||
text-decoration: underline;
|
||||
}
|
||||
ha-list-item {
|
||||
mwc-list-item {
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
|
|
@ -1,18 +1,10 @@
|
|||
import { getConfigEntries } from "../../../../../../data/config_entries";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import {
|
||||
fetchZwaveIsAnyFirmwareUpdateInProgress,
|
||||
fetchZwaveNodeFirmwareUpdateCapabilities,
|
||||
fetchZwaveNodeIsFirmwareUpdateInProgress,
|
||||
fetchZwaveNodeStatus,
|
||||
} from "../../../../../../data/zwave_js";
|
||||
import { showConfirmationDialog } from "../../../../../../dialogs/generic/show-dialog-box";
|
||||
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 { showZWaveJSNodeStatisticsDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-node-statistics";
|
||||
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 { showZWaveJUpdateFirmwareNodeDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-update-firmware-node";
|
||||
import type { DeviceAction } from "../../../ha-config-device-page";
|
||||
|
||||
export const getZwaveDeviceActions = async (
|
||||
|
@ -34,13 +26,13 @@ export const getZwaveDeviceActions = async (
|
|||
|
||||
const entryId = configEntry.entry_id;
|
||||
|
||||
const nodeStatus = await fetchZwaveNodeStatus(hass, device.id);
|
||||
const node = await fetchZwaveNodeStatus(hass, device.id);
|
||||
|
||||
if (!nodeStatus || nodeStatus.is_controller_node) {
|
||||
if (!node || node.is_controller_node) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const actions = [
|
||||
return [
|
||||
{
|
||||
label: hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.device_config"
|
||||
|
@ -60,7 +52,7 @@ export const getZwaveDeviceActions = async (
|
|||
label: hass.localize("ui.panel.config.zwave_js.device_info.heal_node"),
|
||||
action: () =>
|
||||
showZWaveJSHealNodeDialog(el, {
|
||||
device,
|
||||
device: device,
|
||||
}),
|
||||
},
|
||||
{
|
||||
|
@ -72,57 +64,5 @@ export const getZwaveDeviceActions = async (
|
|||
device_id: device.id,
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.node_statistics"
|
||||
),
|
||||
action: () =>
|
||||
showZWaveJSNodeStatisticsDialog(el, {
|
||||
device,
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
if (!nodeStatus.ready) {
|
||||
return actions;
|
||||
}
|
||||
|
||||
const [
|
||||
firmwareUpdateCapabilities,
|
||||
isAnyFirmwareUpdateInProgress,
|
||||
isNodeFirmwareUpdateInProgress,
|
||||
] = await Promise.all([
|
||||
fetchZwaveNodeFirmwareUpdateCapabilities(hass, device.id),
|
||||
fetchZwaveIsAnyFirmwareUpdateInProgress(hass, entryId),
|
||||
fetchZwaveNodeIsFirmwareUpdateInProgress(hass, device.id),
|
||||
]);
|
||||
|
||||
if (
|
||||
firmwareUpdateCapabilities.firmware_upgradable &&
|
||||
(!isAnyFirmwareUpdateInProgress || isNodeFirmwareUpdateInProgress)
|
||||
) {
|
||||
actions.push({
|
||||
label: hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.update_firmware"
|
||||
),
|
||||
action: async () => {
|
||||
if (
|
||||
await showConfirmationDialog(el, {
|
||||
text: hass.localize(
|
||||
"ui.panel.config.zwave_js.update_firmware.warning"
|
||||
),
|
||||
dismissText: hass.localize("ui.common.no"),
|
||||
confirmText: hass.localize("ui.common.yes"),
|
||||
})
|
||||
) {
|
||||
showZWaveJUpdateFirmwareNodeDialog(el, {
|
||||
device,
|
||||
firmwareUpdateCapabilities,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return actions;
|
||||
};
|
||||
|
|
|
@ -8,7 +8,6 @@ import {
|
|||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../../../../components/ha-expansion-panel";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
ConfigEntry,
|
||||
getConfigEntries,
|
||||
|
@ -18,15 +17,13 @@ import {
|
|||
fetchZwaveNodeStatus,
|
||||
nodeStatus,
|
||||
SecurityClass,
|
||||
subscribeZwaveNodeStatus,
|
||||
ZWaveJSNodeStatus,
|
||||
} from "../../../../../../data/zwave_js";
|
||||
import { haStyle } from "../../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../../types";
|
||||
import { SubscribeMixin } from "../../../../../../mixins/subscribe-mixin";
|
||||
|
||||
@customElement("ha-device-info-zwave_js")
|
||||
export class HaDeviceInfoZWaveJS extends SubscribeMixin(LitElement) {
|
||||
export class HaDeviceInfoZWaveJS extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public device!: DeviceRegistryEntry;
|
||||
|
@ -44,21 +41,6 @@ export class HaDeviceInfoZWaveJS extends SubscribeMixin(LitElement) {
|
|||
}
|
||||
}
|
||||
|
||||
public hassSubscribe(): Array<UnsubscribeFunc | Promise<UnsubscribeFunc>> {
|
||||
return [
|
||||
subscribeZwaveNodeStatus(this.hass, this.device!.id, (message) => {
|
||||
if (!this._node) {
|
||||
return;
|
||||
}
|
||||
this._node = {
|
||||
...this._node,
|
||||
status: message.status,
|
||||
ready: message.ready,
|
||||
};
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected async _fetchNodeDetails() {
|
||||
if (!this.device) {
|
||||
return;
|
||||
|
|
|
@ -181,9 +181,6 @@ class DialogDeviceRegistryDetail extends LitElement {
|
|||
}
|
||||
ha-switch {
|
||||
margin-right: 16px;
|
||||
margin-inline-end: 16px;
|
||||
margin-inline-start: initial;
|
||||
direction: var(--direction);
|
||||
}
|
||||
.row {
|
||||
margin-top: 8px;
|
||||
|
|
|
@ -1265,11 +1265,8 @@ export class HaConfigDevicePage extends LitElement {
|
|||
|
||||
.card-header ha-icon-button {
|
||||
margin-right: -8px;
|
||||
margin-inline-end: -8px;
|
||||
margin-inline-start: initial;
|
||||
color: var(--primary-color);
|
||||
height: auto;
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
.device-info {
|
||||
|
@ -1335,9 +1332,6 @@ export class HaConfigDevicePage extends LitElement {
|
|||
|
||||
.header-right > *:not(:first-child) {
|
||||
margin-left: 16px;
|
||||
margin-inline-start: 16px;
|
||||
margin-inline-end: initial;
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
.battery {
|
||||
|
|
|
@ -110,14 +110,6 @@ const OVERRIDE_SENSOR_UNITS = {
|
|||
pressure: ["hPa", "Pa", "kPa", "bar", "cbar", "mbar", "mmHg", "inHg", "psi"],
|
||||
};
|
||||
|
||||
const OVERRIDE_WEATHER_UNITS = {
|
||||
precipitation: ["mm", "in"],
|
||||
pressure: ["hPa", "mbar", "mmHg", "inHg"],
|
||||
temperature: ["°C", "°F"],
|
||||
visibility: ["km", "mi"],
|
||||
wind_speed: ["ft/s", "km/h", "kn", "mph", "m/s"],
|
||||
};
|
||||
|
||||
const SWITCH_AS_DOMAINS = ["cover", "fan", "light", "lock", "siren"];
|
||||
|
||||
@customElement("entity-registry-settings")
|
||||
|
@ -148,16 +140,6 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
|||
|
||||
@state() private _unit_of_measurement?: string | null;
|
||||
|
||||
@state() private _precipitation_unit?: string | null;
|
||||
|
||||
@state() private _pressure_unit?: string | null;
|
||||
|
||||
@state() private _temperature_unit?: string | null;
|
||||
|
||||
@state() private _visibility_unit?: string | null;
|
||||
|
||||
@state() private _wind_speed_unit?: string | null;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@state() private _submitting?: boolean;
|
||||
|
@ -241,16 +223,6 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
|||
this._unit_of_measurement = stateObj?.attributes?.unit_of_measurement;
|
||||
}
|
||||
|
||||
if (domain === "weather") {
|
||||
const stateObj: HassEntity | undefined =
|
||||
this.hass.states[this.entry.entity_id];
|
||||
this._precipitation_unit = stateObj?.attributes?.precipitation_unit;
|
||||
this._pressure_unit = stateObj?.attributes?.pressure_unit;
|
||||
this._temperature_unit = stateObj?.attributes?.temperature_unit;
|
||||
this._visibility_unit = stateObj?.attributes?.visibility_unit;
|
||||
this._wind_speed_unit = stateObj?.attributes?.wind_speed_unit;
|
||||
}
|
||||
|
||||
const deviceClasses: string[][] = OVERRIDE_DEVICE_CLASSES[domain];
|
||||
|
||||
if (!deviceClasses) {
|
||||
|
@ -361,8 +333,7 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
|||
</ha-select>
|
||||
`
|
||||
: ""}
|
||||
${domain === "sensor" &&
|
||||
this._deviceClass &&
|
||||
${this._deviceClass &&
|
||||
stateObj?.attributes.unit_of_measurement &&
|
||||
OVERRIDE_SENSOR_UNITS[this._deviceClass]?.includes(
|
||||
stateObj?.attributes.unit_of_measurement
|
||||
|
@ -386,90 +357,6 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
|||
</ha-select>
|
||||
`
|
||||
: ""}
|
||||
${domain === "weather"
|
||||
? html`
|
||||
<ha-select
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.precipitation_unit"
|
||||
)}
|
||||
.value=${this._precipitation_unit}
|
||||
naturalMenuWidth
|
||||
fixedMenuPosition
|
||||
@selected=${this._precipitationUnitChanged}
|
||||
@closed=${stopPropagation}
|
||||
>
|
||||
${OVERRIDE_WEATHER_UNITS.precipitation.map(
|
||||
(unit: string) => html`
|
||||
<mwc-list-item .value=${unit}>${unit}</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-select>
|
||||
<ha-select
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.pressure_unit"
|
||||
)}
|
||||
.value=${this._pressure_unit}
|
||||
naturalMenuWidth
|
||||
fixedMenuPosition
|
||||
@selected=${this._pressureUnitChanged}
|
||||
@closed=${stopPropagation}
|
||||
>
|
||||
${OVERRIDE_WEATHER_UNITS.pressure.map(
|
||||
(unit: string) => html`
|
||||
<mwc-list-item .value=${unit}>${unit}</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-select>
|
||||
<ha-select
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.temperature_unit"
|
||||
)}
|
||||
.value=${this._temperature_unit}
|
||||
naturalMenuWidth
|
||||
fixedMenuPosition
|
||||
@selected=${this._temperatureUnitChanged}
|
||||
@closed=${stopPropagation}
|
||||
>
|
||||
${OVERRIDE_WEATHER_UNITS.temperature.map(
|
||||
(unit: string) => html`
|
||||
<mwc-list-item .value=${unit}>${unit}</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-select>
|
||||
<ha-select
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.visibility_unit"
|
||||
)}
|
||||
.value=${this._visibility_unit}
|
||||
naturalMenuWidth
|
||||
fixedMenuPosition
|
||||
@selected=${this._visibilityUnitChanged}
|
||||
@closed=${stopPropagation}
|
||||
>
|
||||
${OVERRIDE_WEATHER_UNITS.visibility.map(
|
||||
(unit: string) => html`
|
||||
<mwc-list-item .value=${unit}>${unit}</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-select>
|
||||
<ha-select
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.wind_speed_unit"
|
||||
)}
|
||||
.value=${this._wind_speed_unit}
|
||||
naturalMenuWidth
|
||||
fixedMenuPosition
|
||||
@selected=${this._windSpeedUnitChanged}
|
||||
@closed=${stopPropagation}
|
||||
>
|
||||
${OVERRIDE_WEATHER_UNITS.wind_speed.map(
|
||||
(unit: string) => html`
|
||||
<mwc-list-item .value=${unit}>${unit}</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-select>
|
||||
`
|
||||
: ""}
|
||||
${domain === "switch"
|
||||
? html`<ha-select
|
||||
.label=${this.hass.localize(
|
||||
|
@ -740,31 +627,6 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
|||
this._unit_of_measurement = ev.target.value;
|
||||
}
|
||||
|
||||
private _precipitationUnitChanged(ev): void {
|
||||
this._error = undefined;
|
||||
this._precipitation_unit = ev.target.value;
|
||||
}
|
||||
|
||||
private _pressureUnitChanged(ev): void {
|
||||
this._error = undefined;
|
||||
this._pressure_unit = ev.target.value;
|
||||
}
|
||||
|
||||
private _temperatureUnitChanged(ev): void {
|
||||
this._error = undefined;
|
||||
this._temperature_unit = ev.target.value;
|
||||
}
|
||||
|
||||
private _visibilityUnitChanged(ev): void {
|
||||
this._error = undefined;
|
||||
this._visibility_unit = ev.target.value;
|
||||
}
|
||||
|
||||
private _windSpeedUnitChanged(ev): void {
|
||||
this._error = undefined;
|
||||
this._wind_speed_unit = ev.target.value;
|
||||
}
|
||||
|
||||
private _switchAsChanged(ev): void {
|
||||
if (ev.target.value === "") {
|
||||
return;
|
||||
|
@ -867,23 +729,6 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
|||
params.options_domain = "sensor";
|
||||
params.options = { unit_of_measurement: this._unit_of_measurement };
|
||||
}
|
||||
if (
|
||||
domain === "weather" &&
|
||||
(stateObj?.attributes?.precipitation_unit !== this._precipitation_unit ||
|
||||
stateObj?.attributes?.pressure_unit !== this._pressure_unit ||
|
||||
stateObj?.attributes?.temperature_unit !== this._temperature_unit ||
|
||||
stateObj?.attributes?.visbility_unit !== this._visibility_unit ||
|
||||
stateObj?.attributes?.wind_speed_unit !== this._wind_speed_unit)
|
||||
) {
|
||||
params.options_domain = "weather";
|
||||
params.options = {
|
||||
precipitation_unit: this._precipitation_unit,
|
||||
pressure_unit: this._pressure_unit,
|
||||
temperature_unit: this._temperature_unit,
|
||||
visibility_unit: this._visibility_unit,
|
||||
wind_speed_unit: this._wind_speed_unit,
|
||||
};
|
||||
}
|
||||
try {
|
||||
const result = await updateEntityRegistryEntry(
|
||||
this.hass!,
|
||||
|
|
|
@ -133,54 +133,45 @@ export class DialogHelperDetail extends LitElement {
|
|||
items.sort((a, b) => a[1].localeCompare(b[1]));
|
||||
|
||||
content = html`
|
||||
<mwc-list
|
||||
innerRole="listbox"
|
||||
itemRoles="option"
|
||||
innerAriaLabel=${this.hass.localize(
|
||||
"ui.panel.config.helpers.dialog.create_helper"
|
||||
)}
|
||||
rootTabbable
|
||||
dialogInitialFocus
|
||||
>
|
||||
${items.map(([domain, label]) => {
|
||||
// Only OG helpers need to be loaded prior adding one
|
||||
const isLoaded =
|
||||
!(domain in HELPERS) || isComponentLoaded(this.hass, domain);
|
||||
return html`
|
||||
<mwc-list-item
|
||||
.disabled=${!isLoaded}
|
||||
.domain=${domain}
|
||||
@request-selected=${this._domainPicked}
|
||||
graphic="icon"
|
||||
>
|
||||
<img
|
||||
slot="graphic"
|
||||
loading="lazy"
|
||||
src=${brandsUrl({
|
||||
domain,
|
||||
type: "icon",
|
||||
useFallback: true,
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
})}
|
||||
aria-hidden="true"
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
<span class="item-text"> ${label} </span>
|
||||
</mwc-list-item>
|
||||
${!isLoaded
|
||||
? html`
|
||||
<paper-tooltip animation-delay="0"
|
||||
>${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.platform_not_loaded",
|
||||
"platform",
|
||||
domain
|
||||
)}</paper-tooltip
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
})}
|
||||
</mwc-list>
|
||||
${items.map(([domain, label]) => {
|
||||
// Only OG helpers need to be loaded prior adding one
|
||||
const isLoaded =
|
||||
!(domain in HELPERS) || isComponentLoaded(this.hass, domain);
|
||||
return html`
|
||||
<mwc-list-item
|
||||
.disabled=${!isLoaded}
|
||||
.domain=${domain}
|
||||
@click=${this._domainPicked}
|
||||
@keydown=${this._handleEnter}
|
||||
dialogInitialFocus
|
||||
graphic="icon"
|
||||
>
|
||||
<img
|
||||
slot="graphic"
|
||||
loading="lazy"
|
||||
src=${brandsUrl({
|
||||
domain,
|
||||
type: "icon",
|
||||
useFallback: true,
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
})}
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
<span class="item-text"> ${label} </span>
|
||||
</mwc-list-item>
|
||||
${!isLoaded
|
||||
? html`
|
||||
<paper-tooltip animation-delay="0"
|
||||
>${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.platform_not_loaded",
|
||||
"platform",
|
||||
domain
|
||||
)}</paper-tooltip
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
})}
|
||||
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
|
||||
${this.hass!.localize("ui.common.cancel")}
|
||||
</mwc-button>
|
||||
|
@ -229,6 +220,15 @@ export class DialogHelperDetail extends LitElement {
|
|||
}
|
||||
}
|
||||
|
||||
private _handleEnter(ev: KeyboardEvent) {
|
||||
if (ev.keyCode !== 13) {
|
||||
return;
|
||||
}
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this._domainPicked(ev);
|
||||
}
|
||||
|
||||
private _domainPicked(ev: Event): void {
|
||||
const domain = (ev.currentTarget! as any).domain;
|
||||
|
||||
|
|
|
@ -1,18 +1,6 @@
|
|||
import "@material/mwc-list/mwc-list";
|
||||
import {
|
||||
mdiBug,
|
||||
mdiFileDocument,
|
||||
mdiHandsPray,
|
||||
mdiHelp,
|
||||
mdiHomeAssistant,
|
||||
mdiPower,
|
||||
mdiTshirtCrew,
|
||||
} from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-clickable-list-item";
|
||||
import "../../../components/ha-logo-svg";
|
||||
import {
|
||||
fetchHassioHassOsInfo,
|
||||
|
@ -21,61 +9,12 @@ import {
|
|||
import { fetchHassioInfo, HassioInfo } from "../../../data/hassio/supervisor";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant, Route } from "../../../types";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
|
||||
const JS_TYPE = __BUILD__;
|
||||
const JS_VERSION = __VERSION__;
|
||||
|
||||
const PAGES: Array<{
|
||||
name: string;
|
||||
path: string;
|
||||
iconPath: string;
|
||||
iconColor: string;
|
||||
}> = [
|
||||
{
|
||||
name: "change_log",
|
||||
path: "/latest-release-notes/",
|
||||
iconPath: mdiPower,
|
||||
iconColor: "#4A5963",
|
||||
},
|
||||
{
|
||||
name: "thanks",
|
||||
path: "/developers/credits/",
|
||||
iconPath: mdiHandsPray,
|
||||
iconColor: "#3B808E",
|
||||
},
|
||||
{
|
||||
name: "merch",
|
||||
path: "/merch",
|
||||
iconPath: mdiTshirtCrew,
|
||||
iconColor: "#C65326",
|
||||
},
|
||||
{
|
||||
name: "feature",
|
||||
path: "/feature-requests",
|
||||
iconPath: mdiHomeAssistant,
|
||||
iconColor: "#0D47A1",
|
||||
},
|
||||
{
|
||||
name: "bug",
|
||||
path: "/issues",
|
||||
iconPath: mdiBug,
|
||||
iconColor: "#F1C447",
|
||||
},
|
||||
{
|
||||
name: "help",
|
||||
path: "/community",
|
||||
iconPath: mdiHelp,
|
||||
iconColor: "#B1345C",
|
||||
},
|
||||
{
|
||||
name: "license",
|
||||
path: "/developers/license/",
|
||||
iconPath: mdiFileDocument,
|
||||
iconColor: "#518C43",
|
||||
},
|
||||
];
|
||||
|
||||
class HaConfigInfo extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
|
@ -103,76 +42,96 @@ class HaConfigInfo extends LitElement {
|
|||
back-path="/config"
|
||||
.header=${this.hass.localize("ui.panel.config.info.caption")}
|
||||
>
|
||||
<div class="content">
|
||||
<ha-card outlined>
|
||||
<div class="logo-versions">
|
||||
<a
|
||||
href=${documentationUrl(this.hass, "")}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<ha-logo-svg
|
||||
title=${this.hass.localize(
|
||||
"ui.panel.config.info.home_assistant_logo"
|
||||
)}
|
||||
>
|
||||
</ha-logo-svg>
|
||||
</a>
|
||||
<div class="versions">
|
||||
<span class="ha-version"
|
||||
>Home Assistant ${hass.connection.haVersion}</span
|
||||
>
|
||||
${this._hassioInfo
|
||||
? html`<span>Supervisor ${this._hassioInfo.supervisor}</span>`
|
||||
: ""}
|
||||
${this._osInfo?.version
|
||||
? html`<span>Operating System ${this._osInfo.version}</span>`
|
||||
: ""}
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.info.frontend_version",
|
||||
"version",
|
||||
JS_VERSION
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<mwc-list>
|
||||
${PAGES.map(
|
||||
(page) => html`
|
||||
<ha-clickable-list-item
|
||||
graphic="avatar"
|
||||
openNewTab
|
||||
href=${documentationUrl(this.hass, page.path)}
|
||||
@click=${this._entryClicked}
|
||||
>
|
||||
<div
|
||||
slot="graphic"
|
||||
class="icon-background"
|
||||
.style="background-color: ${page.iconColor}"
|
||||
>
|
||||
<ha-svg-icon .path=${page.iconPath}></ha-svg-icon>
|
||||
</div>
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.info.items.${page.name}`
|
||||
)}
|
||||
</span>
|
||||
</ha-clickable-list-item>
|
||||
`
|
||||
<div class="about">
|
||||
<a
|
||||
href=${documentationUrl(this.hass, "")}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<ha-logo-svg
|
||||
title=${this.hass.localize(
|
||||
"ui.panel.config.info.home_assistant_logo"
|
||||
)}
|
||||
</mwc-list>
|
||||
<p class="config-path">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.info.path_configuration",
|
||||
"path",
|
||||
hass.config.config_dir
|
||||
)}
|
||||
</p>
|
||||
${!customUiList.length
|
||||
? ""
|
||||
: html`
|
||||
<div class="custom-ui">
|
||||
>
|
||||
</ha-logo-svg>
|
||||
</a>
|
||||
<br />
|
||||
<h3>Home Assistant Core ${hass.connection.haVersion}</h3>
|
||||
${this._hassioInfo
|
||||
? html`
|
||||
<h3>
|
||||
Home Assistant Supervisor ${this._hassioInfo.supervisor}
|
||||
</h3>
|
||||
`
|
||||
: ""}
|
||||
${this._osInfo?.version
|
||||
? html`<h3>Home Assistant OS ${this._osInfo.version}</h3>`
|
||||
: ""}
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.info.path_configuration",
|
||||
"path",
|
||||
hass.config.config_dir
|
||||
)}
|
||||
</p>
|
||||
<p class="develop">
|
||||
<a
|
||||
href=${documentationUrl(this.hass, "/developers/credits/")}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.info.developed_by")}
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
${this.hass.localize("ui.panel.config.info.license")}<br />
|
||||
${this.hass.localize("ui.panel.config.info.source")}
|
||||
<a
|
||||
href="https://github.com/home-assistant/core"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>${this.hass.localize("ui.panel.config.info.server")}</a
|
||||
>
|
||||
—
|
||||
<a
|
||||
href="https://github.com/home-assistant/frontend"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>${this.hass.localize("ui.panel.config.info.frontend")}</a
|
||||
>
|
||||
</p>
|
||||
<p>
|
||||
${this.hass.localize("ui.panel.config.info.built_using")}
|
||||
<a href="https://www.python.org" target="_blank" rel="noreferrer"
|
||||
>Python 3</a
|
||||
>,
|
||||
<a href="https://lit.dev" target="_blank" rel="noreferrer">Lit</a>,
|
||||
${this.hass.localize("ui.panel.config.info.icons_by")}
|
||||
<a
|
||||
href="https://fonts.google.com/icons?selected=Material+Icons"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>Google</a
|
||||
>
|
||||
${this.hass.localize("ui.common.and")}
|
||||
<a
|
||||
href="https://materialdesignicons.com/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>Material Design Icons</a
|
||||
>.
|
||||
</p>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.info.frontend_version",
|
||||
"version",
|
||||
JS_VERSION,
|
||||
"type",
|
||||
JS_TYPE
|
||||
)}
|
||||
${customUiList.length > 0
|
||||
? html`
|
||||
<div>
|
||||
${this.hass.localize("ui.panel.config.info.custom_uis")}
|
||||
${customUiList.map(
|
||||
(item) => html`
|
||||
|
@ -183,8 +142,9 @@ class HaConfigInfo extends LitElement {
|
|||
`
|
||||
)}
|
||||
</div>
|
||||
`}
|
||||
</ha-card>
|
||||
`
|
||||
: ""}
|
||||
</p>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
|
@ -216,87 +176,40 @@ class HaConfigInfo extends LitElement {
|
|||
this._osInfo = osInfo;
|
||||
}
|
||||
|
||||
private _entryClicked(ev) {
|
||||
ev.currentTarget.blur();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.content {
|
||||
padding: 28px 20px 0;
|
||||
max-width: 1040px;
|
||||
margin: 0 auto;
|
||||
:host {
|
||||
-ms-user-select: initial;
|
||||
-webkit-user-select: initial;
|
||||
-moz-user-select: initial;
|
||||
}
|
||||
|
||||
.about {
|
||||
text-align: center;
|
||||
line-height: 2em;
|
||||
}
|
||||
|
||||
.version {
|
||||
@apply --paper-font-headline;
|
||||
}
|
||||
|
||||
.develop {
|
||||
@apply --paper-font-subhead;
|
||||
}
|
||||
|
||||
.about a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
ha-logo-svg {
|
||||
padding: 12px;
|
||||
height: 150px;
|
||||
width: 150px;
|
||||
height: 180px;
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
ha-card {
|
||||
padding: 16px;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 24px;
|
||||
margin-bottom: max(24px, env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.logo-versions {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.versions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: var(--secondary-text-color);
|
||||
padding: 12px 0;
|
||||
align-self: stretch;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.ha-version {
|
||||
color: var(--primary-text-color);
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
mwc-list {
|
||||
--mdc-list-side-padding: 4px;
|
||||
}
|
||||
|
||||
ha-svg-icon {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
display: block;
|
||||
padding: 8px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.icon-background {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
@media all and (max-width: 500px), all and (max-height: 500px) {
|
||||
ha-logo-svg {
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
.config-path {
|
||||
color: var(--secondary-text-color);
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.custom-ui {
|
||||
color: var(--secondary-text-color);
|
||||
text-align: center;
|
||||
h4 {
|
||||
font-weight: 400;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
|
|
@ -1,477 +0,0 @@
|
|||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import "@material/mwc-list/mwc-list";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "../../../../../components/ha-expansion-panel";
|
||||
import "../../../../../components/ha-help-tooltip";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import { mdiSwapHorizontal } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import {
|
||||
DeviceRegistryEntry,
|
||||
computeDeviceName,
|
||||
subscribeDeviceRegistry,
|
||||
} from "../../../../../data/device_registry";
|
||||
import {
|
||||
subscribeZwaveNodeStatistics,
|
||||
ProtocolDataRate,
|
||||
ZWaveJSNodeStatisticsUpdatedMessage,
|
||||
ZWaveJSRouteStatistics,
|
||||
RssiError,
|
||||
} from "../../../../../data/zwave_js";
|
||||
import { haStyleDialog } from "../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { ZWaveJSNodeStatisticsDialogParams } from "./show-dialog-zwave_js-node-statistics";
|
||||
import { createCloseHeading } from "../../../../../components/ha-dialog";
|
||||
|
||||
type WorkingRouteStatistics =
|
||||
| (ZWaveJSRouteStatistics & {
|
||||
repeater_rssi_table?: TemplateResult;
|
||||
rssi_translated?: TemplateResult | string;
|
||||
route_failed_between_translated?: [string, string];
|
||||
})
|
||||
| undefined;
|
||||
|
||||
@customElement("dialog-zwave_js-node-statistics")
|
||||
class DialogZWaveJSNodeStatistics extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private device?: DeviceRegistryEntry;
|
||||
|
||||
@state() private _nodeStatistics?: ZWaveJSNodeStatisticsUpdatedMessage & {
|
||||
rssi_translated?: TemplateResult | string;
|
||||
};
|
||||
|
||||
@state() private _deviceIDsToName: { [key: string]: string } = {};
|
||||
|
||||
@state() private _workingRoutes: {
|
||||
lwr?: WorkingRouteStatistics;
|
||||
nlwr?: WorkingRouteStatistics;
|
||||
} = {};
|
||||
|
||||
private _subscribedNodeStatistics?: Promise<UnsubscribeFunc>;
|
||||
|
||||
private _subscribedDeviceRegistry?: UnsubscribeFunc;
|
||||
|
||||
public showDialog(params: ZWaveJSNodeStatisticsDialogParams): void {
|
||||
this.device = params.device;
|
||||
this._subscribeDeviceRegistry();
|
||||
this._subscribeNodeStatistics();
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._nodeStatistics = undefined;
|
||||
this.device = undefined;
|
||||
|
||||
this._unsubscribe();
|
||||
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.device) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this.hass.localize("ui.panel.config.zwave_js.node_statistics.title")
|
||||
)}
|
||||
>
|
||||
<mwc-list noninteractive>
|
||||
<mwc-list-item twoline hasmeta>
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.node_statistics.commands_tx.label"
|
||||
)}</span
|
||||
>
|
||||
<span slot="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.node_statistics.commands_tx.tooltip"
|
||||
)}
|
||||
</span>
|
||||
<span slot="meta">${this._nodeStatistics?.commands_tx}</span>
|
||||
</mwc-list-item>
|
||||
<mwc-list-item twoline hasmeta>
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.node_statistics.commands_rx.label"
|
||||
)}</span
|
||||
>
|
||||
<span slot="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.node_statistics.commands_rx.tooltip"
|
||||
)}
|
||||
</span>
|
||||
<span slot="meta">${this._nodeStatistics?.commands_rx}</span>
|
||||
</mwc-list-item>
|
||||
<mwc-list-item twoline hasmeta>
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.node_statistics.commands_dropped_tx.label"
|
||||
)}</span
|
||||
>
|
||||
<span slot="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.node_statistics.commands_dropped_tx.tooltip"
|
||||
)}
|
||||
</span>
|
||||
<span slot="meta"
|
||||
>${this._nodeStatistics?.commands_dropped_tx}</span
|
||||
>
|
||||
</mwc-list-item>
|
||||
<mwc-list-item twoline hasmeta>
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.node_statistics.commands_dropped_rx.label"
|
||||
)}</span
|
||||
>
|
||||
<span slot="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.node_statistics.commands_dropped_rx.tooltip"
|
||||
)}
|
||||
</span>
|
||||
<span slot="meta"
|
||||
>${this._nodeStatistics?.commands_dropped_rx}</span
|
||||
>
|
||||
</mwc-list-item>
|
||||
<mwc-list-item twoline hasmeta>
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.node_statistics.timeout_response.label"
|
||||
)}</span
|
||||
>
|
||||
<span slot="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.node_statistics.timeout_response.tooltip"
|
||||
)}
|
||||
</span>
|
||||
<span slot="meta">${this._nodeStatistics?.timeout_response}</span>
|
||||
</mwc-list-item>
|
||||
${this._nodeStatistics?.rtt
|
||||
? html`<mwc-list-item twoline hasmeta>
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.node_statistics.rtt.label"
|
||||
)}</span
|
||||
>
|
||||
<span slot="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.node_statistics.rtt.tooltip"
|
||||
)}
|
||||
</span>
|
||||
<span slot="meta">${this._nodeStatistics.rtt}</span>
|
||||
</mwc-list-item>`
|
||||
: ``}
|
||||
${this._nodeStatistics?.rssi_translated
|
||||
? html`<mwc-list-item twoline hasmeta>
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.node_statistics.rssi.label"
|
||||
)}</span
|
||||
>
|
||||
<span slot="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.node_statistics.rssi.tooltip"
|
||||
)}
|
||||
</span>
|
||||
<span slot="meta">${this._nodeStatistics.rssi_translated}</span>
|
||||
</mwc-list-item>`
|
||||
: ``}
|
||||
</mwc-list>
|
||||
${Object.entries(this._workingRoutes).map(([wrKey, wrValue]) =>
|
||||
wrValue
|
||||
? html`
|
||||
<ha-expansion-panel
|
||||
.header=${this.hass.localize(
|
||||
`ui.panel.config.zwave_js.node_statistics.${wrKey}`
|
||||
)}
|
||||
>
|
||||
<div class="row">
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.route_statistics.protocol.label"
|
||||
)}<ha-help-tooltip
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.route_statistics.protocol.tooltip"
|
||||
)}
|
||||
>
|
||||
</ha-help-tooltip
|
||||
></span>
|
||||
<span
|
||||
>${this.hass.localize(
|
||||
`ui.panel.config.zwave_js.route_statistics.protocol.protocol_data_rate.${
|
||||
ProtocolDataRate[wrValue.protocol_data_rate]
|
||||
}`
|
||||
)}</span
|
||||
>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.route_statistics.data_rate.label"
|
||||
)}<ha-help-tooltip
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.route_statistics.data_rate.tooltip"
|
||||
)}
|
||||
>
|
||||
</ha-help-tooltip
|
||||
></span>
|
||||
<span
|
||||
>${this.hass.localize(
|
||||
`ui.panel.config.zwave_js.route_statistics.data_rate.protocol_data_rate.${
|
||||
ProtocolDataRate[wrValue.protocol_data_rate]
|
||||
}`
|
||||
)}</span
|
||||
>
|
||||
</div>
|
||||
${wrValue.rssi_translated
|
||||
? html`<div class="row">
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.route_statistics.rssi.label"
|
||||
)}<ha-help-tooltip
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.route_statistics.rssi.tooltip"
|
||||
)}
|
||||
>
|
||||
</ha-help-tooltip
|
||||
></span>
|
||||
<span>${wrValue.rssi_translated}</span>
|
||||
</div>`
|
||||
: ``}
|
||||
<div class="row">
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.route_statistics.route_failed_between.label"
|
||||
)}<ha-help-tooltip
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.route_statistics.route_failed_between.tooltip"
|
||||
)}
|
||||
>
|
||||
</ha-help-tooltip
|
||||
></span>
|
||||
<span>
|
||||
${wrValue.route_failed_between_translated
|
||||
? html`${wrValue
|
||||
.route_failed_between_translated[0]}<ha-svg-icon
|
||||
.path=${mdiSwapHorizontal}
|
||||
></ha-svg-icon
|
||||
>${wrValue.route_failed_between_translated[1]}`
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.zwave_js.route_statistics.route_failed_between.not_applicable"
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.route_statistics.repeaters.label"
|
||||
)}<ha-help-tooltip
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.route_statistics.repeaters.tooltip"
|
||||
)}
|
||||
>
|
||||
</ha-help-tooltip></span
|
||||
><span>
|
||||
${wrValue.repeater_rssi_table
|
||||
? html`<div class="row">
|
||||
<span class="key-cell"
|
||||
><b
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.route_statistics.repeaters.repeaters"
|
||||
)}:</b
|
||||
></span
|
||||
>
|
||||
<span class="value-cell"
|
||||
><b
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.route_statistics.repeaters.rssi"
|
||||
)}:</b
|
||||
></span
|
||||
>
|
||||
</div>
|
||||
${wrValue.repeater_rssi_table}`
|
||||
: html`${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.route_statistics.repeaters.direct"
|
||||
)}`}</span
|
||||
>
|
||||
</div>
|
||||
</ha-expansion-panel>
|
||||
`
|
||||
: ``
|
||||
)}
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _computeRSSI(
|
||||
rssi: number,
|
||||
includeUnit: boolean
|
||||
): TemplateResult | string {
|
||||
if (Object.values(RssiError).includes(rssi)) {
|
||||
return html`<ha-help-tooltip
|
||||
.label=${this.hass.localize(
|
||||
`ui.panel.config.zwave_js.rssi.rssi_error.${RssiError[rssi]}`
|
||||
)}
|
||||
></ha-help-tooltip>`;
|
||||
}
|
||||
if (includeUnit) {
|
||||
return `${rssi}
|
||||
${this.hass.localize("ui.panel.config.zwave_js.rssi.unit")}`;
|
||||
}
|
||||
return rssi.toString();
|
||||
}
|
||||
|
||||
private _computeDeviceNameById(device_id: string): "unknown device" | string {
|
||||
if (!this._deviceIDsToName) {
|
||||
return "unknown device";
|
||||
}
|
||||
const device = this._deviceIDsToName[device_id];
|
||||
if (!device) {
|
||||
return "unknown device";
|
||||
}
|
||||
|
||||
return this._deviceIDsToName[device_id] || "unknown device";
|
||||
}
|
||||
|
||||
private _subscribeNodeStatistics(): void {
|
||||
if (!this.hass) {
|
||||
return;
|
||||
}
|
||||
this._subscribedNodeStatistics = subscribeZwaveNodeStatistics(
|
||||
this.hass,
|
||||
this.device!.id,
|
||||
(message: ZWaveJSNodeStatisticsUpdatedMessage) => {
|
||||
this._nodeStatistics = {
|
||||
...message,
|
||||
rssi_translated: message.rssi
|
||||
? this._computeRSSI(message.rssi, false)
|
||||
: undefined,
|
||||
};
|
||||
|
||||
const workingRoutesValueMap: [
|
||||
string,
|
||||
WorkingRouteStatistics | null | undefined
|
||||
][] = [
|
||||
["lwr", this._nodeStatistics?.lwr],
|
||||
["nlwr", this._nodeStatistics?.nlwr],
|
||||
];
|
||||
|
||||
const workingRoutes: {
|
||||
lwr?: WorkingRouteStatistics;
|
||||
nlwr?: WorkingRouteStatistics;
|
||||
} = {};
|
||||
workingRoutesValueMap.forEach(([wrKey, wrValue]) => {
|
||||
workingRoutes[wrKey] = wrValue;
|
||||
|
||||
if (wrValue) {
|
||||
if (wrValue.rssi) {
|
||||
wrValue.rssi_translated = this._computeRSSI(wrValue.rssi, true);
|
||||
}
|
||||
|
||||
if (wrValue.route_failed_between) {
|
||||
wrValue.route_failed_between_translated = [
|
||||
this._computeDeviceNameById(wrValue.route_failed_between[0]),
|
||||
this._computeDeviceNameById(wrValue.route_failed_between[1]),
|
||||
];
|
||||
}
|
||||
|
||||
if (wrValue.repeaters && wrValue.repeaters.length) {
|
||||
wrValue.repeater_rssi_table = html`${wrValue.repeaters.map(
|
||||
(_, idx) =>
|
||||
html`<div class="row">
|
||||
<span class="key-cell"
|
||||
>${this._computeDeviceNameById(
|
||||
wrValue.repeaters[idx]
|
||||
)}:</span
|
||||
>
|
||||
<span class="value-cell"
|
||||
>${this._computeRSSI(
|
||||
wrValue.repeater_rssi[idx],
|
||||
true
|
||||
)}</span
|
||||
>
|
||||
</div>`
|
||||
)}`;
|
||||
}
|
||||
}
|
||||
});
|
||||
this._workingRoutes = workingRoutes;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _subscribeDeviceRegistry(): void {
|
||||
if (!this.hass) {
|
||||
return;
|
||||
}
|
||||
this._subscribedDeviceRegistry = subscribeDeviceRegistry(
|
||||
this.hass.connection,
|
||||
(devices: DeviceRegistryEntry[]) => {
|
||||
const devicesIdToName = {};
|
||||
devices.forEach((device) => {
|
||||
devicesIdToName[device.id] = computeDeviceName(device, this.hass);
|
||||
});
|
||||
this._deviceIDsToName = devicesIdToName;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _unsubscribe(): void {
|
||||
if (this._subscribedNodeStatistics) {
|
||||
this._subscribedNodeStatistics.then((unsub) => unsub());
|
||||
this._subscribedNodeStatistics = undefined;
|
||||
}
|
||||
if (this._subscribedDeviceRegistry) {
|
||||
this._subscribedDeviceRegistry();
|
||||
this._subscribedDeviceRegistry = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
mwc-list-item {
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.table {
|
||||
display: table;
|
||||
}
|
||||
|
||||
.key-cell {
|
||||
display: table-cell;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.value-cell {
|
||||
display: table-cell;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
span[slot="meta"] {
|
||||
font-size: 0.95em;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-zwave_js-node-statistics": DialogZWaveJSNodeStatistics;
|
||||
}
|
||||
}
|
|
@ -1,461 +0,0 @@
|
|||
import "../../../../../components/ha-file-upload";
|
||||
import "../../../../../components/ha-form/ha-form";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-linear-progress/mwc-linear-progress";
|
||||
import { mdiCheckCircle, mdiCloseCircle, mdiFileUpload } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { createCloseHeading } from "../../../../../components/ha-dialog";
|
||||
import {
|
||||
DeviceRegistryEntry,
|
||||
computeDeviceName,
|
||||
} from "../../../../../data/device_registry";
|
||||
import {
|
||||
abortZwaveNodeFirmwareUpdate,
|
||||
fetchZwaveNodeIsFirmwareUpdateInProgress,
|
||||
fetchZwaveNodeStatus,
|
||||
FirmwareUpdateStatus,
|
||||
NodeStatus,
|
||||
subscribeZwaveNodeStatus,
|
||||
subscribeZwaveNodeFirmwareUpdate,
|
||||
uploadFirmwareAndBeginUpdate,
|
||||
ZWaveJSNodeFirmwareUpdateFinishedMessage,
|
||||
ZWaveJSNodeFirmwareUpdateProgressMessage,
|
||||
ZWaveJSNodeStatusUpdatedMessage,
|
||||
ZWaveJSNodeFirmwareUpdateCapabilities,
|
||||
ZWaveJSNodeStatus,
|
||||
} from "../../../../../data/zwave_js";
|
||||
import { haStyleDialog } from "../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { ZWaveJSUpdateFirmwareNodeDialogParams } from "./show-dialog-zwave_js-update-firmware-node";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../../../dialogs/generic/show-dialog-box";
|
||||
import { HaFormIntegerSchema } from "../../../../../components/ha-form/types";
|
||||
|
||||
@customElement("dialog-zwave_js-update-firmware-node")
|
||||
class DialogZWaveJSUpdateFirmwareNode extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private device?: DeviceRegistryEntry;
|
||||
|
||||
@state() private _uploading = false;
|
||||
|
||||
@state()
|
||||
private _updateFinishedMessage?: ZWaveJSNodeFirmwareUpdateFinishedMessage;
|
||||
|
||||
@state()
|
||||
private _updateProgressMessage?: ZWaveJSNodeFirmwareUpdateProgressMessage;
|
||||
|
||||
@state() private _updateInProgress = false;
|
||||
|
||||
@state() private _firmwareFile?: File;
|
||||
|
||||
@state() private _nodeStatus?: ZWaveJSNodeStatus;
|
||||
|
||||
@state() private _firmwareTarget? = 0;
|
||||
|
||||
private _subscribedNodeStatus?: Promise<UnsubscribeFunc>;
|
||||
|
||||
private _subscribedNodeFirmwareUpdate?: Promise<UnsubscribeFunc>;
|
||||
|
||||
private _deviceName?: string;
|
||||
|
||||
private _firmwareUpdateCapabilities?: ZWaveJSNodeFirmwareUpdateCapabilities;
|
||||
|
||||
public showDialog(params: ZWaveJSUpdateFirmwareNodeDialogParams): void {
|
||||
this._deviceName = computeDeviceName(params.device, this.hass!);
|
||||
this.device = params.device;
|
||||
this._firmwareUpdateCapabilities = params.firmwareUpdateCapabilities;
|
||||
this._fetchData();
|
||||
this._subscribeNodeStatus();
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._unsubscribeNodeFirmwareUpdate();
|
||||
this._unsubscribeNodeStatus();
|
||||
this.device =
|
||||
this._updateProgressMessage =
|
||||
this._updateFinishedMessage =
|
||||
this._firmwareFile =
|
||||
this._nodeStatus =
|
||||
this._firmwareUpdateCapabilities =
|
||||
undefined;
|
||||
this._firmwareTarget = 0;
|
||||
this._uploading = this._updateInProgress = false;
|
||||
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(
|
||||
firmwareUpdateCapabilities: ZWaveJSNodeFirmwareUpdateCapabilities
|
||||
): HaFormIntegerSchema => {
|
||||
if (!firmwareUpdateCapabilities.firmware_upgradable) {
|
||||
// We should never get here, this is to pass type checks
|
||||
throw new Error();
|
||||
}
|
||||
return {
|
||||
name: "firmware_target",
|
||||
type: "integer",
|
||||
valueMin: Math.min(...firmwareUpdateCapabilities.firmware_targets),
|
||||
valueMax: Math.max(...firmwareUpdateCapabilities.firmware_targets),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (
|
||||
!this.device ||
|
||||
!this._nodeStatus ||
|
||||
!this._firmwareUpdateCapabilities ||
|
||||
!this._firmwareUpdateCapabilities.firmware_upgradable ||
|
||||
this._updateInProgress === undefined
|
||||
) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const beginFirmwareUpdateHTML = html`<ha-file-upload
|
||||
.hass=${this.hass}
|
||||
.uploading=${this._uploading}
|
||||
.icon=${mdiFileUpload}
|
||||
label=${this._firmwareFile?.name ??
|
||||
this.hass.localize(
|
||||
"ui.panel.config.zwave_js.update_firmware.upload_firmware"
|
||||
)}
|
||||
@file-picked=${this._uploadFile}
|
||||
></ha-file-upload>
|
||||
${this._firmwareUpdateCapabilities.firmware_targets.length > 1
|
||||
? html`<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.update_firmware.firmware_target_intro"
|
||||
)}
|
||||
</p>
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${{ firmware_target: this._firmwareTarget }}
|
||||
.schema=${[this._schema(this._firmwareUpdateCapabilities)]}
|
||||
@value-changed=${this._firmwareTargetChanged}
|
||||
></ha-form>`
|
||||
: ""}
|
||||
<mwc-button
|
||||
slot="primaryAction"
|
||||
@click=${this._beginFirmwareUpdate}
|
||||
.disabled=${this._firmwareFile === undefined}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.update_firmware.begin_update"
|
||||
)}
|
||||
</mwc-button>`;
|
||||
|
||||
const abortFirmwareUpdateButton = html`
|
||||
<mwc-button slot="primaryAction" @click=${this._abortFirmwareUpdate}>
|
||||
${this.hass.localize("ui.panel.config.zwave_js.update_firmware.abort")}
|
||||
</mwc-button>
|
||||
`;
|
||||
|
||||
const status = this._updateFinishedMessage
|
||||
? FirmwareUpdateStatus[this._updateFinishedMessage.status]
|
||||
.split("_")[0]
|
||||
.toLowerCase()
|
||||
: undefined;
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this.hass.localize("ui.panel.config.zwave_js.update_firmware.title")
|
||||
)}
|
||||
>
|
||||
${!this._updateProgressMessage && !this._updateFinishedMessage
|
||||
? !this._updateInProgress
|
||||
? html`
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.update_firmware.introduction",
|
||||
{
|
||||
device: html`<strong>${this._deviceName}</strong>`,
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
${beginFirmwareUpdateHTML}
|
||||
`
|
||||
: html`
|
||||
<p>
|
||||
${this._nodeStatus.status === NodeStatus.Asleep
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.zwave_js.update_firmware.queued",
|
||||
{
|
||||
device: html`<strong>${this._deviceName}</strong>`,
|
||||
}
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.zwave_js.update_firmware.awake",
|
||||
{
|
||||
device: html`<strong>${this._deviceName}</strong>`,
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
${this._nodeStatus.status === NodeStatus.Asleep
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.zwave_js.update_firmware.close_queued",
|
||||
{
|
||||
device: html`<strong>${this._deviceName}</strong>`,
|
||||
}
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.zwave_js.update_firmware.close",
|
||||
{
|
||||
device: html`<strong>${this._deviceName}</strong>`,
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
${abortFirmwareUpdateButton}
|
||||
`
|
||||
: this._updateProgressMessage && !this._updateFinishedMessage
|
||||
? html`
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.update_firmware.in_progress",
|
||||
{
|
||||
device: html`<strong>${this._deviceName}</strong>`,
|
||||
progress: (
|
||||
(this._updateProgressMessage.sent_fragments * 100) /
|
||||
this._updateProgressMessage.total_fragments
|
||||
).toFixed(2),
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
<mwc-linear-progress
|
||||
determinate
|
||||
.progress=${this._updateProgressMessage.sent_fragments /
|
||||
this._updateProgressMessage.total_fragments}
|
||||
></mwc-linear-progress>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.update_firmware.close",
|
||||
{
|
||||
device: html`<strong>${this._deviceName}</strong>`,
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
${abortFirmwareUpdateButton}
|
||||
`
|
||||
: html`
|
||||
<div class="flex-container">
|
||||
<ha-svg-icon
|
||||
.path=${status === "ok" ? mdiCheckCircle : mdiCloseCircle}
|
||||
.class=${status}
|
||||
></ha-svg-icon>
|
||||
<div class="status">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.zwave_js.update_firmware.finished_status.${status}`,
|
||||
{
|
||||
device: html`<strong>${this._deviceName}</strong>`,
|
||||
message: this.hass.localize(
|
||||
`ui.panel.config.zwave_js.update_firmware.finished_status.${
|
||||
FirmwareUpdateStatus[
|
||||
this._updateFinishedMessage!.status
|
||||
]
|
||||
}`
|
||||
),
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
${status === "ok"
|
||||
? html`<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.update_firmware.finished_status.done"
|
||||
)}
|
||||
</p>`
|
||||
: html`<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.update_firmware.finished_status.try_again"
|
||||
)}
|
||||
</p>
|
||||
${beginFirmwareUpdateHTML}`}
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.update_firmware.finished_status.try_again"
|
||||
)}
|
||||
</p>
|
||||
${beginFirmwareUpdateHTML}
|
||||
`}
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _fetchData(): Promise<void> {
|
||||
[this._nodeStatus, this._updateInProgress] = await Promise.all([
|
||||
fetchZwaveNodeStatus(this.hass, this.device!.id),
|
||||
fetchZwaveNodeIsFirmwareUpdateInProgress(this.hass, this.device!.id),
|
||||
]);
|
||||
if (this._updateInProgress) {
|
||||
this._subscribeNodeFirmwareUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
private async _beginFirmwareUpdate(): Promise<void> {
|
||||
this._uploading = true;
|
||||
this._updateProgressMessage = this._updateFinishedMessage = undefined;
|
||||
try {
|
||||
this._subscribeNodeFirmwareUpdate();
|
||||
await uploadFirmwareAndBeginUpdate(
|
||||
this.hass,
|
||||
this.device!.id,
|
||||
this._firmwareFile!,
|
||||
this._firmwareTarget
|
||||
);
|
||||
this._updateInProgress = true;
|
||||
this._uploading = false;
|
||||
} catch (err: any) {
|
||||
this._unsubscribeNodeFirmwareUpdate();
|
||||
this._uploading = false;
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.zwave_js.update_firmware.upload_failed"
|
||||
),
|
||||
text: err.message,
|
||||
confirmText: this.hass!.localize("ui.common.close"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _abortFirmwareUpdate(): Promise<void> {
|
||||
if (
|
||||
await showConfirmationDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.zwave_js.update_firmware.confirm_abort",
|
||||
{
|
||||
device: html`<strong>${this._deviceName}</strong>`,
|
||||
}
|
||||
),
|
||||
dismissText: this.hass!.localize("ui.common.no"),
|
||||
confirmText: this.hass!.localize("ui.common.yes"),
|
||||
})
|
||||
) {
|
||||
this._unsubscribeNodeFirmwareUpdate();
|
||||
try {
|
||||
await abortZwaveNodeFirmwareUpdate(this.hass, this.device!.id);
|
||||
} catch (err: any) {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.zwave_js.update_firmware.abort_failed"
|
||||
),
|
||||
text: err.message,
|
||||
confirmText: this.hass!.localize("ui.common.close"),
|
||||
});
|
||||
}
|
||||
this._firmwareFile = undefined;
|
||||
this._updateFinishedMessage = undefined;
|
||||
this._updateProgressMessage = undefined;
|
||||
this._updateInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
private _subscribeNodeStatus(): void {
|
||||
if (!this.hass || !this.device || this._subscribedNodeStatus) {
|
||||
return;
|
||||
}
|
||||
this._subscribedNodeStatus = subscribeZwaveNodeStatus(
|
||||
this.hass,
|
||||
this.device.id,
|
||||
(message: ZWaveJSNodeStatusUpdatedMessage) => {
|
||||
this._nodeStatus!.status = message.status;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _unsubscribeNodeStatus(): void {
|
||||
if (!this._subscribedNodeStatus) {
|
||||
return;
|
||||
}
|
||||
this._subscribedNodeStatus.then((unsub) => unsub());
|
||||
this._subscribedNodeStatus = undefined;
|
||||
}
|
||||
|
||||
private _subscribeNodeFirmwareUpdate(): void {
|
||||
if (!this.hass || !this.device || this._subscribedNodeFirmwareUpdate) {
|
||||
return;
|
||||
}
|
||||
this._subscribedNodeFirmwareUpdate = subscribeZwaveNodeFirmwareUpdate(
|
||||
this.hass,
|
||||
this.device.id,
|
||||
(
|
||||
message:
|
||||
| ZWaveJSNodeFirmwareUpdateFinishedMessage
|
||||
| ZWaveJSNodeFirmwareUpdateProgressMessage
|
||||
) => {
|
||||
if (message.event === "firmware update progress") {
|
||||
if (!this._updateFinishedMessage) {
|
||||
this._updateProgressMessage = message;
|
||||
}
|
||||
} else {
|
||||
this._unsubscribeNodeFirmwareUpdate();
|
||||
this._updateProgressMessage = undefined;
|
||||
this._updateInProgress = false;
|
||||
this._updateFinishedMessage = message;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _unsubscribeNodeFirmwareUpdate(): void {
|
||||
if (!this._subscribedNodeFirmwareUpdate) {
|
||||
return;
|
||||
}
|
||||
this._subscribedNodeFirmwareUpdate.then((unsub) => unsub());
|
||||
this._subscribedNodeFirmwareUpdate = undefined;
|
||||
}
|
||||
|
||||
private async _firmwareTargetChanged(ev) {
|
||||
this._firmwareTarget = ev.detail.value.firmware_target;
|
||||
}
|
||||
|
||||
private async _uploadFile(ev) {
|
||||
this._firmwareFile = ev.detail.files[0];
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
.ok {
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.error {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
.flex-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
ha-svg-icon {
|
||||
width: 68px;
|
||||
height: 48px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-zwave_js-update-firmware-node": DialogZWaveJSUpdateFirmwareNode;
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { DeviceRegistryEntry } from "../../../../../data/device_registry";
|
||||
|
||||
export interface ZWaveJSNodeStatisticsDialogParams {
|
||||
device: DeviceRegistryEntry;
|
||||
}
|
||||
|
||||
export const loadNodeStatisticsDialog = () =>
|
||||
import("./dialog-zwave_js-node-statistics");
|
||||
|
||||
export const showZWaveJSNodeStatisticsDialog = (
|
||||
element: HTMLElement,
|
||||
nodeStatisticsDialogParams: ZWaveJSNodeStatisticsDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-zwave_js-node-statistics",
|
||||
dialogImport: loadNodeStatisticsDialog,
|
||||
dialogParams: nodeStatisticsDialogParams,
|
||||
});
|
||||
};
|
|
@ -1,22 +0,0 @@
|
|||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { DeviceRegistryEntry } from "../../../../../data/device_registry";
|
||||
import { ZWaveJSNodeFirmwareUpdateCapabilities } from "../../../../../data/zwave_js";
|
||||
|
||||
export interface ZWaveJSUpdateFirmwareNodeDialogParams {
|
||||
device: DeviceRegistryEntry;
|
||||
firmwareUpdateCapabilities: ZWaveJSNodeFirmwareUpdateCapabilities;
|
||||
}
|
||||
|
||||
export const loadUpdateFirmwareNodeDialog = () =>
|
||||
import("./dialog-zwave_js-update-firmware-node");
|
||||
|
||||
export const showZWaveJUpdateFirmwareNodeDialog = (
|
||||
element: HTMLElement,
|
||||
updateFirmwareNodeDialogParams: ZWaveJSUpdateFirmwareNodeDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-zwave_js-update-firmware-node",
|
||||
dialogImport: loadUpdateFirmwareNodeDialog,
|
||||
dialogParams: updateFirmwareNodeDialogParams,
|
||||
});
|
||||
};
|
|
@ -6,7 +6,7 @@ import { extractSearchParam } from "../../../common/url/search-params";
|
|||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/search-input";
|
||||
import { LogProvider } from "../../../data/error_log";
|
||||
import { fetchHassioAddonsInfo } from "../../../data/hassio/addon";
|
||||
import { fetchHassioSupervisorInfo } from "../../../data/hassio/supervisor";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import "../../../layouts/hass-tabs-subpage";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
|
@ -167,15 +167,13 @@ export class HaConfigLogs extends LitElement {
|
|||
|
||||
private async _getInstalledAddons() {
|
||||
try {
|
||||
const addonsInfo = await fetchHassioAddonsInfo(this.hass);
|
||||
const supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
|
||||
this._logProviders = [
|
||||
...this._logProviders,
|
||||
...addonsInfo.addons
|
||||
.filter((addon) => addon.version)
|
||||
.map((addon) => ({
|
||||
key: addon.slug,
|
||||
name: addon.name,
|
||||
})),
|
||||
...supervisorInfo.addons.map((addon) => ({
|
||||
key: addon.slug,
|
||||
name: addon.name,
|
||||
})),
|
||||
];
|
||||
} catch (err) {
|
||||
// Ignore, nothing the user can do anyway
|
||||
|
|
|
@ -12,7 +12,6 @@ import {
|
|||
} from "date-fns/esm";
|
||||
import { css, html, LitElement, PropertyValues } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket/dist/types";
|
||||
import { navigate } from "../../common/navigate";
|
||||
import {
|
||||
createSearchParam,
|
||||
|
@ -20,7 +19,7 @@ import {
|
|||
} from "../../common/url/search-params";
|
||||
import { computeRTL } from "../../common/util/compute_rtl";
|
||||
import "../../components/chart/state-history-charts";
|
||||
import "../../components/ha-target-picker";
|
||||
import "../../components/entity/ha-entity-picker";
|
||||
import "../../components/ha-circular-progress";
|
||||
import "../../components/ha-date-range-picker";
|
||||
import type { DateRangePickerRanges } from "../../components/ha-date-range-picker";
|
||||
|
@ -30,15 +29,8 @@ import { computeHistory, fetchDateWS } from "../../data/history";
|
|||
import "../../layouts/ha-app-layout";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
} from "../../data/entity_registry";
|
||||
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
|
||||
class HaPanelHistory extends SubscribeMixin(LitElement) {
|
||||
class HaPanelHistory extends LitElement {
|
||||
@property() hass!: HomeAssistant;
|
||||
|
||||
@property({ reflect: true, type: Boolean }) narrow!: boolean;
|
||||
|
@ -47,7 +39,7 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
|||
|
||||
@property() _endDate: Date;
|
||||
|
||||
@property() _targetPickerValue?;
|
||||
@property() _entityId = "";
|
||||
|
||||
@property() _isLoading = false;
|
||||
|
||||
|
@ -57,10 +49,6 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
|||
|
||||
@state() private _ranges?: DateRangePickerRanges;
|
||||
|
||||
@state() private _entities?: EntityRegistryEntry[];
|
||||
|
||||
@state() private _stateEntities?: EntityRegistryEntry[];
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
|
||||
|
@ -73,14 +61,6 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
|||
this._endDate = end;
|
||||
}
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
||||
this._entities = entities;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-app-layout>
|
||||
|
@ -100,40 +80,25 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
|||
</app-toolbar>
|
||||
</app-header>
|
||||
|
||||
<div class="flex content">
|
||||
<div class="filters flex layout horizontal narrow-wrap">
|
||||
<ha-date-range-picker
|
||||
.hass=${this.hass}
|
||||
?disabled=${this._isLoading}
|
||||
.startDate=${this._startDate}
|
||||
.endDate=${this._endDate}
|
||||
.ranges=${this._ranges}
|
||||
@change=${this._dateRangeChanged}
|
||||
></ha-date-range-picker>
|
||||
<ha-target-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this._targetPickerValue}
|
||||
.disabled=${this._isLoading}
|
||||
horizontal
|
||||
@value-changed=${this._entitiesChanged}
|
||||
></ha-target-picker>
|
||||
</div>
|
||||
${this._isLoading
|
||||
? html`<div class="progress-wrapper">
|
||||
<ha-circular-progress
|
||||
active
|
||||
alt=${this.hass.localize("ui.common.loading")}
|
||||
></ha-circular-progress>
|
||||
</div>`
|
||||
: html`
|
||||
<state-history-charts
|
||||
.hass=${this.hass}
|
||||
.historyData=${this._stateHistory}
|
||||
.endTime=${this._endDate}
|
||||
no-single
|
||||
>
|
||||
</state-history-charts>
|
||||
`}
|
||||
<div class="filters">
|
||||
<ha-date-range-picker
|
||||
.hass=${this.hass}
|
||||
?disabled=${this._isLoading}
|
||||
.startDate=${this._startDate}
|
||||
.endDate=${this._endDate}
|
||||
.ranges=${this._ranges}
|
||||
@change=${this._dateRangeChanged}
|
||||
></ha-date-range-picker>
|
||||
|
||||
<ha-entity-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this._entityId}
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.entity.entity-picker.entity"
|
||||
)}
|
||||
.disabled=${this._isLoading}
|
||||
@change=${this._entityPicked}
|
||||
></ha-entity-picker>
|
||||
</div>
|
||||
${this._isLoading
|
||||
? html`<div class="progress-wrapper">
|
||||
|
@ -177,13 +142,7 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
|||
[addDays(weekStart, -7), addDays(weekEnd, -7)],
|
||||
};
|
||||
|
||||
const entityIds = extractSearchParam("entity_id");
|
||||
if (entityIds) {
|
||||
const splitEntityIds = entityIds.split(",");
|
||||
this._targetPickerValue = {
|
||||
entity_id: splitEntityIds,
|
||||
};
|
||||
}
|
||||
this._entityId = extractSearchParam("entity_id") ?? "";
|
||||
|
||||
const startDate = extractSearchParam("start_date");
|
||||
if (startDate) {
|
||||
|
@ -199,41 +158,16 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
|||
if (
|
||||
changedProps.has("_startDate") ||
|
||||
changedProps.has("_endDate") ||
|
||||
changedProps.has("_targetPickerValue") ||
|
||||
changedProps.has("_entities")
|
||||
changedProps.has("_entityId")
|
||||
) {
|
||||
this._getHistory();
|
||||
}
|
||||
|
||||
if (changedProps.has("hass") || changedProps.has("_entities")) {
|
||||
if (changedProps.has("hass")) {
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
if (!oldHass || oldHass.language !== this.hass.language) {
|
||||
this.rtl = computeRTL(this.hass);
|
||||
}
|
||||
if (this._entities) {
|
||||
const stateEntities: EntityRegistryEntry[] = [];
|
||||
const regEntityIds = new Set(
|
||||
this._entities.map((entity) => entity.entity_id)
|
||||
);
|
||||
for (const entityId of Object.keys(this.hass.states)) {
|
||||
if (regEntityIds.has(entityId)) {
|
||||
continue;
|
||||
}
|
||||
stateEntities.push({
|
||||
name: computeStateName(this.hass.states[entityId]),
|
||||
entity_id: entityId,
|
||||
platform: computeDomain(entityId),
|
||||
disabled_by: null,
|
||||
hidden_by: null,
|
||||
area_id: null,
|
||||
config_entry_id: null,
|
||||
device_id: null,
|
||||
icon: null,
|
||||
entity_category: null,
|
||||
});
|
||||
}
|
||||
this._stateEntities = stateEntities;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -243,16 +177,12 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
|||
|
||||
private async _getHistory() {
|
||||
this._isLoading = true;
|
||||
const entityIds = this._getEntityIds();
|
||||
const dateHistory =
|
||||
entityIds.length === 0
|
||||
? {}
|
||||
: await fetchDateWS(
|
||||
this.hass,
|
||||
this._startDate,
|
||||
this._endDate,
|
||||
entityIds
|
||||
);
|
||||
const dateHistory = await fetchDateWS(
|
||||
this.hass,
|
||||
this._startDate,
|
||||
this._endDate,
|
||||
this._entityId
|
||||
);
|
||||
this._stateHistory = computeHistory(
|
||||
this.hass,
|
||||
dateHistory,
|
||||
|
@ -261,52 +191,6 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
|||
this._isLoading = false;
|
||||
}
|
||||
|
||||
private _filterEntity(entity: EntityRegistryEntry): boolean {
|
||||
const { area_id, device_id, entity_id } = this._targetPickerValue;
|
||||
if (area_id !== undefined) {
|
||||
if (typeof area_id === "string" && area_id === entity.area_id) {
|
||||
return true;
|
||||
}
|
||||
if (Array.isArray(area_id) && area_id.includes(entity.area_id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (device_id !== undefined) {
|
||||
if (typeof device_id === "string" && device_id === entity.device_id) {
|
||||
return true;
|
||||
}
|
||||
if (Array.isArray(device_id) && device_id.includes(entity.device_id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (entity_id !== undefined) {
|
||||
if (typeof entity_id === "string" && entity_id === entity.entity_id) {
|
||||
return true;
|
||||
}
|
||||
if (Array.isArray(entity_id) && entity_id.includes(entity.entity_id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private _getEntityIds(): string[] {
|
||||
if (
|
||||
this._targetPickerValue === undefined ||
|
||||
this._entities === undefined ||
|
||||
this._stateEntities === undefined
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
const entityIds = this._entities
|
||||
.filter((entity) => this._filterEntity(entity))
|
||||
.map((entity) => entity.entity_id);
|
||||
const stateEntityIds = this._stateEntities
|
||||
.filter((entity) => this._filterEntity(entity))
|
||||
.map((entity) => entity.entity_id);
|
||||
return [...entityIds, ...stateEntityIds];
|
||||
}
|
||||
|
||||
private _dateRangeChanged(ev) {
|
||||
this._startDate = ev.detail.startDate;
|
||||
const endDate = ev.detail.endDate;
|
||||
|
@ -319,8 +203,8 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
|||
this._updatePath();
|
||||
}
|
||||
|
||||
private _entitiesChanged(ev) {
|
||||
this._targetPickerValue = ev.detail.value;
|
||||
private _entityPicked(ev) {
|
||||
this._entityId = ev.target.value;
|
||||
|
||||
this._updatePath();
|
||||
}
|
||||
|
@ -328,8 +212,8 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
|||
private _updatePath() {
|
||||
const params: Record<string, string> = {};
|
||||
|
||||
if (this._targetPickerValue) {
|
||||
params.entity_id = this._getEntityIds().join(",");
|
||||
if (this._entityId) {
|
||||
params.entity_id = this._entityId;
|
||||
}
|
||||
|
||||
if (this._startDate) {
|
||||
|
@ -371,18 +255,6 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
:host([narrow]) .narrow-wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.horizontal {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:host(:not([narrow])) .selector-padding {
|
||||
padding-left: 32px;
|
||||
}
|
||||
|
||||
.progress-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import "@lit-labs/virtualizer";
|
||||
import { VisibilityChangedEvent } from "@lit-labs/virtualizer/Virtualizer";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
|
@ -17,6 +16,7 @@ import { restoreScroll } from "../../common/decorators/restore-scroll";
|
|||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
import { computeRTL, emitRTLDirection } from "../../common/util/compute_rtl";
|
||||
import "../../components/entity/state-badge";
|
||||
import "../../components/ha-circular-progress";
|
||||
import "../../components/ha-relative-time";
|
||||
|
@ -35,12 +35,6 @@ import {
|
|||
import { HomeAssistant } from "../../types";
|
||||
import { brandsUrl } from "../../util/brands-url";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"hass-logbook-live": { enable: boolean };
|
||||
}
|
||||
}
|
||||
|
||||
const triggerDomains = ["script", "automation"];
|
||||
|
||||
const hasContext = (item: LogbookEntry) =>
|
||||
|
@ -62,6 +56,9 @@ class HaLogbookRenderer extends LitElement {
|
|||
@property({ type: Boolean, attribute: "narrow" })
|
||||
public narrow = false;
|
||||
|
||||
@property({ attribute: "rtl", type: Boolean })
|
||||
private _rtl = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "virtualize", reflect: true })
|
||||
public virtualize = false;
|
||||
|
||||
|
@ -89,10 +86,18 @@ class HaLogbookRenderer extends LitElement {
|
|||
);
|
||||
}
|
||||
|
||||
protected updated(_changedProps: PropertyValues) {
|
||||
const oldHass = _changedProps.get("hass") as HomeAssistant | undefined;
|
||||
|
||||
if (oldHass === undefined || oldHass.language !== this.hass.language) {
|
||||
this._rtl = computeRTL(this.hass);
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.entries?.length) {
|
||||
return html`
|
||||
<div class="container no-entries">
|
||||
<div class="container no-entries" .dir=${emitRTLDirection(this._rtl)}>
|
||||
${this.hass.localize("ui.components.logbook.entries_not_found")}
|
||||
</div>
|
||||
`;
|
||||
|
@ -102,6 +107,7 @@ class HaLogbookRenderer extends LitElement {
|
|||
<div
|
||||
class="container ha-scrollbar ${classMap({
|
||||
narrow: this.narrow,
|
||||
rtl: this._rtl,
|
||||
"no-name": this.noName,
|
||||
"no-icon": this.noIcon,
|
||||
})}"
|
||||
|
@ -109,7 +115,6 @@ class HaLogbookRenderer extends LitElement {
|
|||
>
|
||||
${this.virtualize
|
||||
? html`<lit-virtualizer
|
||||
@visibilityChanged=${this._visibilityChanged}
|
||||
scroller
|
||||
class="ha-scrollbar"
|
||||
.items=${this.entries}
|
||||
|
@ -247,13 +252,6 @@ class HaLogbookRenderer extends LitElement {
|
|||
this._savedScrollPos = (e.target as HTMLDivElement).scrollTop;
|
||||
}
|
||||
|
||||
@eventOptions({ passive: true })
|
||||
private _visibilityChanged(e: VisibilityChangedEvent) {
|
||||
fireEvent(this, "hass-logbook-live", {
|
||||
enable: e.first === 0,
|
||||
});
|
||||
}
|
||||
|
||||
private _renderMessage(
|
||||
item: LogbookEntry,
|
||||
seenEntityIds: string[],
|
||||
|
@ -509,6 +507,10 @@ class HaLogbookRenderer extends LitElement {
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
.rtl {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.entry-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
@ -533,9 +535,6 @@ class HaLogbookRenderer extends LitElement {
|
|||
|
||||
.narrow:not(.no-icon) .time {
|
||||
margin-left: 32px;
|
||||
margin-inline-start: 32px;
|
||||
margin-inline-end: initial;
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
.message-relative_time {
|
||||
|
@ -557,6 +556,10 @@ class HaLogbookRenderer extends LitElement {
|
|||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.rtl .date {
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.icon-message {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -569,11 +572,8 @@ class HaLogbookRenderer extends LitElement {
|
|||
|
||||
state-badge {
|
||||
margin-right: 16px;
|
||||
margin-inline-start: initial;
|
||||
flex-shrink: 0;
|
||||
color: var(--state-icon-color);
|
||||
margin-inline-end: 16px;
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
.message {
|
||||
|
@ -613,9 +613,6 @@ class HaLogbookRenderer extends LitElement {
|
|||
|
||||
.narrow .icon-message state-badge {
|
||||
margin-left: 0;
|
||||
margin-inline-start: 0;
|
||||
margin-inline-end: initial;
|
||||
direction: var(--direction);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
|
@ -78,11 +79,7 @@ export class HaLogbook extends LitElement {
|
|||
|
||||
@state() private _error?: string;
|
||||
|
||||
private _subscribed?: Promise<(() => Promise<void>) | void>;
|
||||
|
||||
private _liveUpdatesEnabled = true;
|
||||
|
||||
private _pendingStreamMessages: LogbookStreamMessage[] = [];
|
||||
private _subscribed?: Promise<UnsubscribeFunc | void>;
|
||||
|
||||
private _throttleGetLogbookEntries = throttle(
|
||||
() => this._getLogBookData(),
|
||||
|
@ -130,7 +127,6 @@ export class HaLogbook extends LitElement {
|
|||
.entries=${this._logbookEntries}
|
||||
.traceContexts=${this._traceContexts}
|
||||
.userIdToName=${this._userIdToName}
|
||||
@hass-logbook-live=${this._handleLogbookLive}
|
||||
></ha-logbook-renderer>
|
||||
`;
|
||||
}
|
||||
|
@ -140,7 +136,7 @@ export class HaLogbook extends LitElement {
|
|||
return;
|
||||
}
|
||||
|
||||
this._unsubscribeSetLoading();
|
||||
this._unsubscribe();
|
||||
this._throttleGetLogbookEntries.cancel();
|
||||
this._updateTraceContexts.cancel();
|
||||
this._updateUsers.cancel();
|
||||
|
@ -152,23 +148,13 @@ export class HaLogbook extends LitElement {
|
|||
);
|
||||
}
|
||||
|
||||
this._logbookEntries = undefined;
|
||||
this._throttleGetLogbookEntries();
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
if (changedProps.size !== 1 || !changedProps.has("hass")) {
|
||||
return true;
|
||||
}
|
||||
// We only respond to hass changes if the translations changed
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
return !oldHass || oldHass.localize !== this.hass.localize;
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
super.updated(changedProps);
|
||||
|
||||
let changed = changedProps.has("time");
|
||||
|
||||
for (const key of ["entityIds", "deviceIds"]) {
|
||||
|
@ -194,17 +180,6 @@ export class HaLogbook extends LitElement {
|
|||
}
|
||||
}
|
||||
|
||||
private _handleLogbookLive(ev: CustomEvent) {
|
||||
if (ev.detail.enable && !this._liveUpdatesEnabled) {
|
||||
// Process everything we queued up while we were scrolled down
|
||||
this._pendingStreamMessages.forEach((msg) =>
|
||||
this._processStreamMessage(msg)
|
||||
);
|
||||
this._pendingStreamMessages = [];
|
||||
}
|
||||
this._liveUpdatesEnabled = ev.detail.enable;
|
||||
}
|
||||
|
||||
private get _filterAlwaysEmptyResults(): boolean {
|
||||
const entityIds = ensureArray(this.entityIds);
|
||||
const deviceIds = ensureArray(this.deviceIds);
|
||||
|
@ -219,15 +194,7 @@ export class HaLogbook extends LitElement {
|
|||
|
||||
private _unsubscribe(): void {
|
||||
if (this._subscribed) {
|
||||
this._subscribed.then((unsub) =>
|
||||
unsub
|
||||
? unsub().catch(() => {
|
||||
// The backend will cancel the subscription if
|
||||
// we subscribe to entities that will all be
|
||||
// filtered away
|
||||
})
|
||||
: undefined
|
||||
);
|
||||
this._subscribed.then((unsub) => (unsub ? unsub() : undefined));
|
||||
this._subscribed = undefined;
|
||||
}
|
||||
}
|
||||
|
@ -241,26 +208,12 @@ export class HaLogbook extends LitElement {
|
|||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._unsubscribeSetLoading();
|
||||
}
|
||||
|
||||
/** Unsubscribe because we are unloading
|
||||
* or about to resubscribe.
|
||||
* Setting this._logbookEntries to undefined
|
||||
* will put the page in a loading state.
|
||||
*/
|
||||
private _unsubscribeSetLoading() {
|
||||
this._logbookEntries = undefined;
|
||||
this._unsubscribe();
|
||||
}
|
||||
|
||||
/** Unsubscribe because there are no results.
|
||||
* Setting this._logbookEntries to an empty
|
||||
* list will show a no results message.
|
||||
*/
|
||||
private _unsubscribeNoResults() {
|
||||
private _unsubscribeAndEmptyEntries() {
|
||||
this._unsubscribe();
|
||||
this._logbookEntries = [];
|
||||
this._unsubscribe();
|
||||
}
|
||||
|
||||
private _calculateLogbookPeriod() {
|
||||
|
@ -299,19 +252,20 @@ export class HaLogbook extends LitElement {
|
|||
// "recent" means start time is a sliding window
|
||||
// so we need to calculate an expireTime to
|
||||
// purge old events
|
||||
if (!this._subscribed) {
|
||||
// Message came in before we had a chance to unload
|
||||
return;
|
||||
}
|
||||
this._processOrQueueStreamMessage(streamMessage);
|
||||
this._processStreamMessage(
|
||||
streamMessage,
|
||||
"recent" in this.time
|
||||
? findStartOfRecentTime(new Date(), this.time.recent)
|
||||
: undefined
|
||||
);
|
||||
},
|
||||
logbookPeriod.startTime.toISOString(),
|
||||
logbookPeriod.endTime.toISOString(),
|
||||
ensureArray(this.entityIds),
|
||||
ensureArray(this.deviceIds)
|
||||
).catch((err) => {
|
||||
this._error = err.message;
|
||||
this._subscribed = undefined;
|
||||
this._error = err;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
@ -320,7 +274,7 @@ export class HaLogbook extends LitElement {
|
|||
this._error = undefined;
|
||||
|
||||
if (this._filterAlwaysEmptyResults) {
|
||||
this._unsubscribeNoResults();
|
||||
this._unsubscribeAndEmptyEntries();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -328,7 +282,7 @@ export class HaLogbook extends LitElement {
|
|||
|
||||
if (logbookPeriod.startTime > logbookPeriod.now) {
|
||||
// Time Travel not yet invented
|
||||
this._unsubscribeNoResults();
|
||||
this._unsubscribeAndEmptyEntries();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -349,21 +303,10 @@ export class HaLogbook extends LitElement {
|
|||
)
|
||||
: this._logbookEntries;
|
||||
|
||||
private _processOrQueueStreamMessage = (
|
||||
streamMessage: LogbookStreamMessage
|
||||
private _processStreamMessage = (
|
||||
streamMessage: LogbookStreamMessage,
|
||||
purgeBeforePythonTime: number | undefined
|
||||
) => {
|
||||
if (this._liveUpdatesEnabled) {
|
||||
this._processStreamMessage(streamMessage);
|
||||
return;
|
||||
}
|
||||
this._pendingStreamMessages.push(streamMessage);
|
||||
};
|
||||
|
||||
private _processStreamMessage = (streamMessage: LogbookStreamMessage) => {
|
||||
const purgeBeforePythonTime =
|
||||
"recent" in this.time
|
||||
? findStartOfRecentTime(new Date(), this.time.recent)
|
||||
: undefined;
|
||||
// Put newest ones on top. Reverse works in-place so
|
||||
// make a copy first.
|
||||
const newEntries = [...streamMessage.events].reverse();
|
||||
|
|
|
@ -12,17 +12,19 @@ import {
|
|||
} from "date-fns/esm";
|
||||
import { css, html, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import { navigate } from "../../common/navigate";
|
||||
import {
|
||||
createSearchParam,
|
||||
extractSearchParamsObject,
|
||||
} from "../../common/url/search-params";
|
||||
import { computeRTL } from "../../common/util/compute_rtl";
|
||||
import "../../components/entity/ha-entity-picker";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "../../components/entity/ha-entity-picker";
|
||||
import "../../components/ha-date-range-picker";
|
||||
import type { DateRangePickerRanges } from "../../components/ha-date-range-picker";
|
||||
import "../../components/ha-icon-button";
|
||||
import "../../components/ha-menu-button";
|
||||
import { filterLogbookCompatibleEntities } from "../../data/logbook";
|
||||
import "../../layouts/ha-app-layout";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
@ -38,6 +40,8 @@ export class HaPanelLogbook extends LitElement {
|
|||
|
||||
@state() _entityIds?: string[];
|
||||
|
||||
@property({ reflect: true, type: Boolean }) rtl = false;
|
||||
|
||||
@state() private _ranges?: DateRangePickerRanges;
|
||||
|
||||
public constructor() {
|
||||
|
@ -85,7 +89,7 @@ export class HaPanelLogbook extends LitElement {
|
|||
.label=${this.hass.localize(
|
||||
"ui.components.entity.entity-picker.entity"
|
||||
)}
|
||||
.entityFilter=${filterLogbookCompatibleEntities}
|
||||
.entityFilter=${this._entityFilter}
|
||||
@change=${this._entityPicked}
|
||||
></ha-entity-picker>
|
||||
</div>
|
||||
|
@ -146,6 +150,15 @@ export class HaPanelLogbook extends LitElement {
|
|||
this._applyURLParams();
|
||||
};
|
||||
|
||||
protected updated(changedProps: PropertyValues<this>) {
|
||||
if (changedProps.has("hass")) {
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
if (!oldHass || oldHass.language !== this.hass.language) {
|
||||
this.rtl = computeRTL(this.hass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _applyURLParams() {
|
||||
const searchParams = new URLSearchParams(location.search);
|
||||
|
||||
|
@ -229,6 +242,17 @@ export class HaPanelLogbook extends LitElement {
|
|||
this.shadowRoot!.querySelector("ha-logbook")?.refresh();
|
||||
}
|
||||
|
||||
private _entityFilter: HaEntityPickerEntityFilterFunc = (entity) => {
|
||||
if (computeStateDomain(entity) !== "sensor") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
entity.attributes.unit_of_measurement === undefined &&
|
||||
entity.attributes.state_class === undefined
|
||||
);
|
||||
};
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
haStyle,
|
||||
|
|
|
@ -56,23 +56,21 @@ export class HuiEnergyCompareCard
|
|||
|
||||
return html`
|
||||
<ha-alert dismissable @alert-dismissed-clicked=${this._stopCompare}>
|
||||
${this.hass.localize("ui.panel.energy.compare.info", {
|
||||
start: html`<b
|
||||
>${formatDate(this._start!, this.hass.locale)}${dayDifference > 0
|
||||
? ` -
|
||||
${formatDate(this._end || endOfDay(new Date()), this.hass.locale)}`
|
||||
: ""}</b
|
||||
>`,
|
||||
end: html`<b
|
||||
>${formatDate(
|
||||
this._startCompare,
|
||||
this.hass.locale
|
||||
)}${dayDifference > 0
|
||||
? ` -
|
||||
${formatDate(this._endCompare, this.hass.locale)}`
|
||||
: ""}</b
|
||||
>`,
|
||||
})}
|
||||
You are comparing the period
|
||||
<b
|
||||
>${formatDate(this._start!, this.hass.locale)}${dayDifference > 0
|
||||
? ` -
|
||||
${formatDate(this._end || endOfDay(new Date()), this.hass.locale)}`
|
||||
: ""}</b
|
||||
>
|
||||
with period
|
||||
<b
|
||||
>${formatDate(this._startCompare, this.hass.locale)}${dayDifference >
|
||||
0
|
||||
? ` -
|
||||
${formatDate(this._endCompare, this.hass.locale)}`
|
||||
: ""}</b
|
||||
>
|
||||
</ha-alert>
|
||||
`;
|
||||
}
|
||||
|
|
|
@ -489,8 +489,8 @@ class HuiEnergyDistrubutionCard
|
|||
<ha-svg-icon
|
||||
class="small"
|
||||
.path=${mdiArrowUp}
|
||||
></ha-svg-icon
|
||||
>${formatNumber(totalBatteryOut || 0, this.hass.locale, {
|
||||
></ha-svg-icon>
|
||||
${formatNumber(totalBatteryOut || 0, this.hass.locale, {
|
||||
maximumFractionDigits: 1,
|
||||
})}
|
||||
kWh</span
|
||||
|
|
|
@ -851,10 +851,7 @@ export class HuiEnergySourcesTableCard
|
|||
)}
|
||||
</th>
|
||||
${compare
|
||||
? html`${showCosts
|
||||
? html`<td class="mdc-data-table__cell"></td>`
|
||||
: ""}
|
||||
<td
|
||||
? html`<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||
>
|
||||
${formatNumber(
|
||||
|
@ -865,7 +862,10 @@ export class HuiEnergySourcesTableCard
|
|||
currency: this.hass.config.currency!,
|
||||
}
|
||||
)}
|
||||
</td>`
|
||||
</td>
|
||||
${showCosts
|
||||
? html`<td class="mdc-data-table__cell"></td>`
|
||||
: ""}`
|
||||
: ""}
|
||||
<td class="mdc-data-table__cell"></td>
|
||||
<td
|
||||
|
|
|
@ -186,7 +186,7 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
|
|||
id="alarmCode"
|
||||
.label=${this.hass.localize("ui.card.alarm_control_panel.code")}
|
||||
type="password"
|
||||
.inputMode=${stateObj.attributes.code_format === FORMAT_NUMBER
|
||||
.inputmode=${stateObj.attributes.code_format === FORMAT_NUMBER
|
||||
? "numeric"
|
||||
: "text"}
|
||||
></ha-textfield>
|
||||
|
@ -346,7 +346,6 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
|
|||
margin: auto;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
#keypad mwc-button {
|
||||
|
|
|
@ -3,10 +3,8 @@ import {
|
|||
mdiLightbulbMultiple,
|
||||
mdiLightbulbMultipleOff,
|
||||
mdiRun,
|
||||
mdiThermometer,
|
||||
mdiToggleSwitch,
|
||||
mdiToggleSwitchOff,
|
||||
mdiWaterAlert,
|
||||
mdiWaterPercent,
|
||||
} from "@mdi/js";
|
||||
import type { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
|
@ -63,21 +61,17 @@ const TOGGLE_DOMAINS = ["light", "switch", "fan"];
|
|||
const OTHER_DOMAINS = ["camera"];
|
||||
|
||||
const DEVICE_CLASSES = {
|
||||
sensor: ["temperature", "humidity"],
|
||||
binary_sensor: ["motion", "moisture"],
|
||||
sensor: ["temperature"],
|
||||
binary_sensor: ["motion"],
|
||||
};
|
||||
|
||||
const DOMAIN_ICONS = {
|
||||
light: { on: mdiLightbulbMultiple, off: mdiLightbulbMultipleOff },
|
||||
switch: { on: mdiToggleSwitch, off: mdiToggleSwitchOff },
|
||||
fan: { on: domainIcon("fan"), off: domainIcon("fan") },
|
||||
sensor: {
|
||||
temperature: mdiThermometer,
|
||||
humidity: mdiWaterPercent,
|
||||
},
|
||||
sensor: { humidity: mdiWaterPercent },
|
||||
binary_sensor: {
|
||||
motion: mdiRun,
|
||||
moisture: mdiWaterAlert,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -232,7 +232,6 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
|
|||
return segments.map((segment) => ({
|
||||
level: segment?.from,
|
||||
stroke: segment?.color,
|
||||
label: segment?.label,
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
|
@ -276,12 +276,9 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
|
|||
cursor: pointer;
|
||||
top: 0;
|
||||
right: 0;
|
||||
inset-inline-start: initial;
|
||||
inset-inline-end: 0;
|
||||
border-radius: 100%;
|
||||
color: var(--secondary-text-color);
|
||||
z-index: 1;
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
.content {
|
||||
|
|
|
@ -31,7 +31,6 @@ import {
|
|||
handleMediaControlClick,
|
||||
MediaPickedEvent,
|
||||
MediaPlayerEntity,
|
||||
mediaPlayerPlayMedia,
|
||||
SUPPORT_BROWSE_MEDIA,
|
||||
SUPPORT_SEEK,
|
||||
SUPPORT_TURN_ON,
|
||||
|
@ -490,15 +489,21 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
|||
action: "play",
|
||||
entityId: this._config!.entity,
|
||||
mediaPickedCallback: (pickedMedia: MediaPickedEvent) =>
|
||||
mediaPlayerPlayMedia(
|
||||
this.hass,
|
||||
this._config!.entity,
|
||||
this._playMedia(
|
||||
pickedMedia.item.media_content_id,
|
||||
pickedMedia.item.media_content_type
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
private _playMedia(media_content_id: string, media_content_type: string) {
|
||||
this.hass!.callService("media_player", "play_media", {
|
||||
entity_id: this._config!.entity,
|
||||
media_content_id,
|
||||
media_content_type,
|
||||
});
|
||||
}
|
||||
|
||||
private _handleClick(e: MouseEvent): void {
|
||||
handleMediaControlClick(
|
||||
this.hass!,
|
||||
|
@ -600,7 +605,6 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
|||
);
|
||||
height: 100%;
|
||||
right: 0;
|
||||
|
||||
opacity: 1;
|
||||
transition: width 0.8s, opacity 0.8s linear 0.8s;
|
||||
}
|
||||
|
@ -669,11 +673,6 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
|||
transition: padding, color;
|
||||
transition-duration: 0.4s;
|
||||
margin-left: -12px;
|
||||
margin-inline-start: -12px;
|
||||
margin-inline-end: initial;
|
||||
padding-inline-start: 0;
|
||||
padding-inline-end: 8px;
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
.controls > div {
|
||||
|
@ -699,9 +698,6 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
|||
ha-icon-button.browse-media {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
inset-inline-start: initial;
|
||||
inset-inline-end: 4px;
|
||||
direction: var(--direction);
|
||||
--mdc-icon-size: 24px;
|
||||
}
|
||||
|
||||
|
@ -718,18 +714,12 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
|||
|
||||
.icon-name ha-state-icon {
|
||||
padding-right: 8px;
|
||||
padding-inline-start: initial;
|
||||
padding-inline-end: 8px;
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
.more-info {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
inset-inline-start: initial;
|
||||
inset-inline-end: 4px;
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
.media-info {
|
||||
|
|
|
@ -497,12 +497,9 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
|
|||
cursor: pointer;
|
||||
top: 0;
|
||||
right: 0;
|
||||
inset-inline-end: 0px;
|
||||
inset-inline-start: initial;
|
||||
border-radius: 100%;
|
||||
color: var(--secondary-text-color);
|
||||
z-index: 1;
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
.content {
|
||||
|
@ -553,7 +550,6 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
|
|||
height: 50%;
|
||||
top: 45%;
|
||||
left: 50%;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
#set-values {
|
||||
|
|
|
@ -228,21 +228,12 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
|||
</div>
|
||||
<div class="temp-attribute">
|
||||
<div class="temp">
|
||||
${stateObj.attributes.temperature !== undefined &&
|
||||
stateObj.attributes.temperature !== null
|
||||
? html`
|
||||
${formatNumber(
|
||||
stateObj.attributes.temperature,
|
||||
this.hass.locale
|
||||
)} <span
|
||||
>${getWeatherUnit(
|
||||
this.hass,
|
||||
stateObj,
|
||||
"temperature"
|
||||
)}</span
|
||||
>
|
||||
`
|
||||
: html` `}
|
||||
${formatNumber(
|
||||
stateObj.attributes.temperature,
|
||||
this.hass.locale
|
||||
)} <span
|
||||
>${getWeatherUnit(this.hass, "temperature")}</span
|
||||
>
|
||||
</div>
|
||||
<div class="attribute">
|
||||
${this._config.secondary_info_attribute !== undefined
|
||||
|
@ -264,7 +255,6 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
|||
"wind_speed"
|
||||
? getWind(
|
||||
this.hass,
|
||||
stateObj,
|
||||
stateObj.attributes.wind_speed,
|
||||
stateObj.attributes.wind_bearing
|
||||
)
|
||||
|
@ -277,7 +267,6 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
|||
)}
|
||||
${getWeatherUnit(
|
||||
this.hass,
|
||||
stateObj,
|
||||
this._config.secondary_info_attribute
|
||||
)}
|
||||
`}
|
||||
|
|
|
@ -185,7 +185,6 @@ export interface SeverityConfig {
|
|||
export interface GaugeSegment {
|
||||
from: number;
|
||||
color: string;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export interface GaugeCardConfig extends LovelaceCardConfig {
|
||||
|
|