+ ${`${this.hass.localize(
+ "ui.components.logbook.retrieval_error"
+ )}: ${this._error}`}
+
`
+ : !this._logbookEntries
? html`
`
: html`
@@ -70,7 +80,7 @@ export class MoreInfoLogbook extends LitElement {
}
protected firstUpdated(): void {
- this._fetchPersonNames();
+ this._fetchUserPromise = this._fetchUserNames();
this.addEventListener("click", (ev) => {
if ((ev.composedPath()[0] as HTMLElement).tagName === "A") {
setTimeout(() => closeDialog("ha-more-info-dialog"), 500);
@@ -116,16 +126,25 @@ export class MoreInfoLogbook extends LitElement {
this._lastLogbookDate ||
new Date(new Date().getTime() - 24 * 60 * 60 * 1000);
const now = new Date();
- const [newEntries, traceContexts] = await Promise.all([
- getLogbookData(
- this.hass,
- lastDate.toISOString(),
- now.toISOString(),
- this.entityId,
- true
- ),
- loadTraceContexts(this.hass),
- ]);
+ let newEntries;
+ let traceContexts;
+
+ try {
+ [newEntries, traceContexts] = await Promise.all([
+ getLogbookData(
+ this.hass,
+ lastDate.toISOString(),
+ now.toISOString(),
+ this.entityId,
+ true
+ ),
+ this.hass.user?.is_admin ? loadTraceContexts(this.hass) : {},
+ this._fetchUserPromise,
+ ]);
+ } catch (err) {
+ this._error = err.message;
+ }
+
this._logbookEntries = this._logbookEntries
? [...newEntries, ...this._logbookEntries]
: newEntries;
@@ -133,16 +152,34 @@ export class MoreInfoLogbook extends LitElement {
this._traceContexts = traceContexts;
}
- private _fetchPersonNames() {
+ private async _fetchUserNames() {
+ const userIdToName = {};
+
+ // Start loading users
+ const userProm = this.hass.user?.is_admin && fetchUsers(this.hass);
+
+ // Process persons
Object.values(this.hass.states).forEach((entity) => {
if (
entity.attributes.user_id &&
computeStateDomain(entity) === "person"
) {
- this._persons[entity.attributes.user_id] =
+ this._userIdToName[entity.attributes.user_id] =
entity.attributes.friendly_name;
}
});
+
+ // Process users
+ if (userProm) {
+ const users = await userProm;
+ for (const user of users) {
+ if (!(user.id in userIdToName)) {
+ userIdToName[user.id] = user.name;
+ }
+ }
+ }
+
+ this._userIdToName = userIdToName;
}
static get styles() {
diff --git a/src/html/_js_base.html.template b/src/html/_js_base.html.template
index 08cc3ce65a..f8dc4835c9 100644
--- a/src/html/_js_base.html.template
+++ b/src/html/_js_base.html.template
@@ -20,4 +20,5 @@
"content" in document.createElement("template"))) {
document.write("
diff --git a/src/html/authorize.html.template b/src/html/authorize.html.template
index 555aea9e0e..4b7fbc9c53 100644
--- a/src/html/authorize.html.template
+++ b/src/html/authorize.html.template
@@ -43,13 +43,16 @@
<%= renderTemplate('_preload_roboto') %>
diff --git a/src/html/index.html.template b/src/html/index.html.template
index 0bcd77307f..f758aa5e33 100644
--- a/src/html/index.html.template
+++ b/src/html/index.html.template
@@ -67,12 +67,15 @@
<%= renderTemplate('_preload_roboto') %>
diff --git a/src/layouts/partial-panel-resolver.ts b/src/layouts/partial-panel-resolver.ts
index 6035c27713..612ced46c5 100644
--- a/src/layouts/partial-panel-resolver.ts
+++ b/src/layouts/partial-panel-resolver.ts
@@ -43,7 +43,7 @@ const COMPONENTS = {
class PartialPanelResolver extends HassRouterPage {
@property({ attribute: false }) public hass!: HomeAssistant;
- @property() public narrow?: boolean;
+ @property({ type: Boolean }) public narrow?: boolean;
private _waitForStart = false;
@@ -206,7 +206,7 @@ class PartialPanelResolver extends HassRouterPage {
this._currentPage &&
!this.hass.panels[this._currentPage]
) {
- if (this.hass.config.state !== STATE_NOT_RUNNING) {
+ if (this.hass.config.state === STATE_NOT_RUNNING) {
this._waitForStart = true;
if (this.lastChild) {
this.removeChild(this.lastChild);
diff --git a/src/onboarding/ha-onboarding.ts b/src/onboarding/ha-onboarding.ts
index 2a7c66bd9f..cea41caaa6 100644
--- a/src/onboarding/ha-onboarding.ts
+++ b/src/onboarding/ha-onboarding.ts
@@ -12,7 +12,10 @@ import { HASSDomEvent } from "../common/dom/fire_event";
import { extractSearchParamsObject } from "../common/url/search-params";
import { subscribeOne } from "../common/util/subscribe-one";
import { AuthUrlSearchParams, hassUrl } from "../data/auth";
-import { fetchDiscoveryInformation } from "../data/discovery";
+import {
+ DiscoveryInformation,
+ fetchDiscoveryInformation,
+} from "../data/discovery";
import {
fetchOnboardingOverview,
OnboardingResponses,
@@ -68,6 +71,8 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
@state() private _steps?: OnboardingStep[];
+ @state() private _discoveryInformation?: DiscoveryInformation;
+
protected render(): TemplateResult {
const step = this._curStep()!;
@@ -87,6 +92,7 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
? html`
`
diff --git a/src/onboarding/onboarding-core-config.ts b/src/onboarding/onboarding-core-config.ts
index f1bc58a83d..cf330ea87d 100644
--- a/src/onboarding/onboarding-core-config.ts
+++ b/src/onboarding/onboarding-core-config.ts
@@ -5,9 +5,11 @@ import "@polymer/paper-radio-button/paper-radio-button";
import "@polymer/paper-radio-group/paper-radio-group";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
+import memoizeOne from "memoize-one";
import { fireEvent } from "../common/dom/fire_event";
import type { LocalizeFunc } from "../common/translations/localize";
-import "../components/map/ha-location-editor";
+import "../components/map/ha-locations-editor";
+import type { MarkerLocation } from "../components/map/ha-locations-editor";
import { createTimezoneListEl } from "../components/timezone-datalist";
import {
ConfigUpdateValues,
@@ -81,14 +83,14 @@ class OnboardingCoreConfig extends LitElement {
-
+ @location-updated=${this._locationChanged}
+ >
@@ -208,13 +210,24 @@ class OnboardingCoreConfig extends LitElement {
return this._unitSystem !== undefined ? this._unitSystem : "metric";
}
+ private _markerLocation = memoizeOne(
+ (location: [number, number]): MarkerLocation[] => [
+ {
+ id: "location",
+ latitude: location[0],
+ longitude: location[1],
+ location_editable: true,
+ },
+ ]
+ );
+
private _handleChange(ev: PolymerChangedEvent
) {
const target = ev.currentTarget as PaperInputElement;
this[`_${target.name}`] = target.value;
}
private _locationChanged(ev) {
- this._location = ev.currentTarget.location;
+ this._location = ev.detail.location;
}
private _unitSystemChanged(
diff --git a/src/onboarding/onboarding-restore-snapshot.ts b/src/onboarding/onboarding-restore-snapshot.ts
index 32d604c7ac..ca1afc56eb 100644
--- a/src/onboarding/onboarding-restore-snapshot.ts
+++ b/src/onboarding/onboarding-restore-snapshot.ts
@@ -4,9 +4,12 @@ import { customElement, property } from "lit/decorators";
import "../../hassio/src/components/hassio-ansi-to-html";
import { showHassioSnapshotDialog } from "../../hassio/src/dialogs/snapshot/show-dialog-hassio-snapshot";
import { showSnapshotUploadDialog } from "../../hassio/src/dialogs/snapshot/show-dialog-snapshot-upload";
-import { navigate } from "../common/navigate";
import type { LocalizeFunc } from "../common/translations/localize";
import "../components/ha-card";
+import {
+ DiscoveryInformation,
+ fetchDiscoveryInformation,
+} from "../data/discovery";
import { makeDialogManager } from "../dialogs/make-dialog-manager";
import { ProvideHassLitMixin } from "../mixins/provide-hass-lit-mixin";
import { haStyle } from "../resources/styles";
@@ -26,6 +29,9 @@ class OnboardingRestoreSnapshot extends ProvideHassLitMixin(LitElement) {
@property({ type: Boolean }) public restoring = false;
+ @property({ attribute: false })
+ public discoveryInformation?: DiscoveryInformation;
+
protected render(): TemplateResult {
return this.restoring
? html` {
if (this.restoring) {
try {
- const response = await fetch("/api/hassio/supervisor/info", {
- method: "GET",
- });
- if (response.status === 401) {
- // If we get a unauthorized response, the restore is done
- navigate("/", { replace: true });
- location.reload();
+ const response = await fetchDiscoveryInformation();
+
+ if (
+ !this.discoveryInformation ||
+ this.discoveryInformation.uuid !== response.uuid
+ ) {
+ // When the UUID changes, the restore is complete
+ window.location.replace("/");
}
} catch (err) {
// We fully expected issues with fetching info untill restore is complete.
@@ -76,6 +83,7 @@ class OnboardingRestoreSnapshot extends ProvideHassLitMixin(LitElement) {
showHassioSnapshotDialog(this, {
slug,
onboarding: true,
+ localize: this.localize,
});
}
diff --git a/src/panels/config/core/ha-config-core-form.ts b/src/panels/config/core/ha-config-core-form.ts
index 322c2697c1..2d26def4ce 100644
--- a/src/panels/config/core/ha-config-core-form.ts
+++ b/src/panels/config/core/ha-config-core-form.ts
@@ -8,7 +8,8 @@ import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { UNIT_C } from "../../../common/const";
import "../../../components/ha-card";
-import "../../../components/map/ha-location-editor";
+import "../../../components/map/ha-locations-editor";
+import type { MarkerLocation } from "../../../components/map/ha-locations-editor";
import { createTimezoneListEl } from "../../../components/timezone-datalist";
import { ConfigUpdateValues, saveCoreConfig } from "../../../data/core";
import type { PolymerChangedEvent } from "../../../polymer-types";
@@ -20,13 +21,13 @@ class ConfigCoreForm extends LitElement {
@state() private _working = false;
- @state() private _location!: [number, number];
+ @state() private _location?: [number, number];
- @state() private _elevation!: string;
+ @state() private _elevation?: string;
- @state() private _unitSystem!: ConfigUpdateValues["unit_system"];
+ @state() private _unitSystem?: ConfigUpdateValues["unit_system"];
- @state() private _timeZone!: string;
+ @state() private _timeZone?: string;
protected render(): TemplateResult {
const canEdit = ["storage", "default"].includes(
@@ -52,16 +53,16 @@ class ConfigCoreForm extends LitElement {
: ""}
-
+ @location-updated=${this._locationChanged}
+ >
@@ -162,8 +163,19 @@ class ConfigCoreForm extends LitElement {
input.inputElement.appendChild(createTimezoneListEl());
}
- private _locationValue = memoizeOne(
- (location, lat, lng) => location || [Number(lat), Number(lng)]
+ private _markerLocation = memoizeOne(
+ (
+ lat: number,
+ lng: number,
+ location?: [number, number]
+ ): MarkerLocation[] => [
+ {
+ id: "location",
+ latitude: location ? location[0] : lat,
+ longitude: location ? location[1] : lng,
+ location_editable: true,
+ },
+ ]
);
private get _elevationValue() {
@@ -192,7 +204,7 @@ class ConfigCoreForm extends LitElement {
}
private _locationChanged(ev) {
- this._location = ev.currentTarget.location;
+ this._location = ev.detail.location;
}
private _unitSystemChanged(
@@ -204,11 +216,10 @@ class ConfigCoreForm extends LitElement {
private async _save() {
this._working = true;
try {
- const location = this._locationValue(
- this._location,
+ const location = this._location || [
this.hass.config.latitude,
- this.hass.config.longitude
- );
+ this.hass.config.longitude,
+ ];
await saveCoreConfig(this.hass, {
latitude: location[0],
longitude: location[1],
diff --git a/src/panels/config/integrations/ha-integration-card.ts b/src/panels/config/integrations/ha-integration-card.ts
index 85663de87d..4ac6414562 100644
--- a/src/panels/config/integrations/ha-integration-card.ts
+++ b/src/panels/config/integrations/ha-integration-card.ts
@@ -22,6 +22,7 @@ import {
enableConfigEntry,
reloadConfigEntry,
updateConfigEntry,
+ ERROR_STATES,
} from "../../../data/config_entries";
import type { DeviceRegistryEntry } from "../../../data/device_registry";
import type { EntityRegistryEntry } from "../../../data/entity_registry";
@@ -38,12 +39,6 @@ import type { HomeAssistant } from "../../../types";
import type { ConfigEntryExtended } from "./ha-config-integrations";
import "./ha-integration-header";
-const ERROR_STATES: ConfigEntry["state"][] = [
- "migration_error",
- "setup_error",
- "setup_retry",
-];
-
const integrationsWithPanel = {
hassio: "/hassio/dashboard",
mqtt: "/config/mqtt",
@@ -303,7 +298,7 @@ export class HaIntegrationCard extends LitElement {
>
-
+
${this.hass.localize(
"ui.panel.config.integrations.config_entry.rename"
)}
@@ -420,6 +415,15 @@ export class HaIntegrationCard extends LitElement {
showOptionsFlowDialog(this, ev.target.closest("ha-card").configEntry);
}
+ private _handleRename(ev: CustomEvent): void {
+ if (!shouldHandleRequestSelectedEvent(ev)) {
+ return;
+ }
+ this._editEntryName(
+ ((ev.target as HTMLElement).closest("ha-card") as any).configEntry
+ );
+ }
+
private _handleReload(ev: CustomEvent): void {
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
@@ -578,8 +582,7 @@ export class HaIntegrationCard extends LitElement {
});
}
- private async _editEntryName(ev) {
- const configEntry = ev.target.closest("ha-card").configEntry;
+ private async _editEntryName(configEntry: ConfigEntry) {
const newName = await showPromptDialog(this, {
title: this.hass.localize("ui.panel.config.integrations.rename_dialog"),
defaultValue: configEntry.title,
diff --git a/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-heal-network.ts b/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-heal-network.ts
new file mode 100644
index 0000000000..4cdaacd1e6
--- /dev/null
+++ b/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-heal-network.ts
@@ -0,0 +1,312 @@
+import "@material/mwc-button/mwc-button";
+import "@material/mwc-linear-progress/mwc-linear-progress";
+import { mdiStethoscope, mdiCheckCircle, mdiCloseCircle } from "@mdi/js";
+import { UnsubscribeFunc } from "home-assistant-js-websocket";
+import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
+import { customElement, property, state } from "lit/decorators";
+import { fireEvent } from "../../../../../common/dom/fire_event";
+import { createCloseHeading } from "../../../../../components/ha-dialog";
+import {
+ fetchNetworkStatus,
+ healNetwork,
+ stopHealNetwork,
+ subscribeHealNetworkProgress,
+ ZWaveJSHealNetworkStatusMessage,
+ ZWaveJSNetwork,
+} from "../../../../../data/zwave_js";
+import { haStyleDialog } from "../../../../../resources/styles";
+import { HomeAssistant } from "../../../../../types";
+import { ZWaveJSHealNetworkDialogParams } from "./show-dialog-zwave_js-heal-network";
+
+@customElement("dialog-zwave_js-heal-network")
+class DialogZWaveJSHealNetwork extends LitElement {
+ @property({ attribute: false }) public hass!: HomeAssistant;
+
+ @state() private entry_id?: string;
+
+ @state() private _status?: string;
+
+ @state() private _progress_total = 0;
+
+ @state() private _progress_finished = 0;
+
+ @state() private _progress_in_progress = 0;
+
+ private _subscribed?: Promise;
+
+ public showDialog(params: ZWaveJSHealNetworkDialogParams): void {
+ this._progress_total = 0;
+ this.entry_id = params.entry_id;
+ this._fetchData();
+ }
+
+ public closeDialog(): void {
+ this.entry_id = undefined;
+ this._status = undefined;
+ this._progress_total = 0;
+
+ this._unsubscribe();
+
+ fireEvent(this, "dialog-closed", { dialog: this.localName });
+ }
+
+ protected render(): TemplateResult {
+ if (!this.entry_id) {
+ return html``;
+ }
+
+ return html`
+
+ ${!this._status
+ ? html`
+
+
+
+
+ ${this.hass.localize(
+ "ui.panel.config.zwave_js.heal_network.introduction"
+ )}
+
+
+
+
+
+ ${this.hass.localize(
+ "ui.panel.config.zwave_js.heal_network.traffic_warning"
+ )}
+
+
+
+ ${this.hass.localize(
+ "ui.panel.config.zwave_js.heal_network.start_heal"
+ )}
+
+ `
+ : ``}
+ ${this._status === "started"
+ ? html`
+
+
+
+ ${this.hass.localize(
+ "ui.panel.config.zwave_js.heal_network.in_progress"
+ )}
+
+
+
+ ${this.hass.localize(
+ "ui.panel.config.zwave_js.heal_network.run_in_background"
+ )}
+
+
+ ${!this._progress_total
+ ? html`
+
+ `
+ : ""}
+
+ ${this.hass.localize(
+ "ui.panel.config.zwave_js.heal_network.stop_heal"
+ )}
+
+
+ ${this.hass.localize("ui.panel.config.zwave_js.common.close")}
+
+ `
+ : ``}
+ ${this._status === "failed"
+ ? html`
+
+
+
+
+ ${this.hass.localize(
+ "ui.panel.config.zwave_js.heal_network.healing_failed"
+ )}
+
+
+
+
+ ${this.hass.localize("ui.panel.config.zwave_js.common.close")}
+
+ `
+ : ``}
+ ${this._status === "finished"
+ ? html`
+
+
+
+
+ ${this.hass.localize(
+ "ui.panel.config.zwave_js.heal_network.healing_complete"
+ )}
+
+
+
+
+ ${this.hass.localize("ui.panel.config.zwave_js.common.close")}
+
+ `
+ : ``}
+ ${this._status === "cancelled"
+ ? html`
+
+
+
+
+ ${this.hass.localize(
+ "ui.panel.config.zwave_js.heal_network.healing_cancelled"
+ )}
+
+
+
+
+ ${this.hass.localize("ui.panel.config.zwave_js.common.close")}
+
+ `
+ : ``}
+ ${this._progress_total && this._status !== "finished"
+ ? html`
+
+
+ `
+ : ""}
+
+ `;
+ }
+
+ private async _fetchData(): Promise {
+ if (!this.hass) {
+ return;
+ }
+ const network: ZWaveJSNetwork = await fetchNetworkStatus(
+ this.hass!,
+ this.entry_id!
+ );
+ if (network.controller.is_heal_network_active) {
+ this._status = "started";
+ this._subscribed = subscribeHealNetworkProgress(
+ this.hass,
+ this.entry_id!,
+ this._handleMessage.bind(this)
+ );
+ }
+ }
+
+ private _startHeal(): void {
+ if (!this.hass) {
+ return;
+ }
+ healNetwork(this.hass, this.entry_id!);
+ this._status = "started";
+ this._subscribed = subscribeHealNetworkProgress(
+ this.hass,
+ this.entry_id!,
+ this._handleMessage.bind(this)
+ );
+ }
+
+ private _stopHeal(): void {
+ if (!this.hass) {
+ return;
+ }
+ stopHealNetwork(this.hass, this.entry_id!);
+ this._unsubscribe();
+ this._status = "cancelled";
+ }
+
+ private _handleMessage(message: ZWaveJSHealNetworkStatusMessage): void {
+ if (message.event === "heal network progress") {
+ let finished = 0;
+ let in_progress = 0;
+ for (const status of Object.values(message.heal_node_status)) {
+ if (status === "pending") {
+ in_progress++;
+ }
+ if (["skipped", "failed", "done"].includes(status)) {
+ finished++;
+ }
+ }
+ this._progress_total = Object.keys(message.heal_node_status).length;
+ this._progress_finished = finished / this._progress_total;
+ this._progress_in_progress = in_progress / this._progress_total;
+ }
+ if (message.event === "heal network done") {
+ this._unsubscribe();
+ this._status = "finished";
+ }
+ }
+
+ private _unsubscribe(): void {
+ if (this._subscribed) {
+ this._subscribed.then((unsub) => unsub());
+ this._subscribed = undefined;
+ }
+ }
+
+ static get styles(): CSSResultGroup {
+ return [
+ haStyleDialog,
+ css`
+ .success {
+ color: var(--success-color);
+ }
+
+ .failed {
+ color: var(--warning-color);
+ }
+
+ .flex-container {
+ display: flex;
+ align-items: center;
+ }
+
+ ha-svg-icon {
+ width: 68px;
+ height: 48px;
+ }
+
+ ha-svg-icon.introduction {
+ color: var(--primary-color);
+ }
+
+ .flex-container ha-svg-icon {
+ margin-right: 20px;
+ }
+
+ mwc-linear-progress {
+ margin-top: 8px;
+ }
+ `,
+ ];
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "dialog-zwave_js-heal-network": DialogZWaveJSHealNetwork;
+ }
+}
diff --git a/src/panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-heal-network.ts b/src/panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-heal-network.ts
new file mode 100644
index 0000000000..cb46ad9d43
--- /dev/null
+++ b/src/panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-heal-network.ts
@@ -0,0 +1,19 @@
+import { fireEvent } from "../../../../../common/dom/fire_event";
+
+export interface ZWaveJSHealNetworkDialogParams {
+ entry_id: string;
+}
+
+export const loadHealNetworkDialog = () =>
+ import("./dialog-zwave_js-heal-network");
+
+export const showZWaveJSHealNetworkDialog = (
+ element: HTMLElement,
+ healNetworkDialogParams: ZWaveJSHealNetworkDialogParams
+): void => {
+ fireEvent(element, "show-dialog", {
+ dialogTag: "dialog-zwave_js-heal-network",
+ dialogImport: loadHealNetworkDialog,
+ dialogParams: healNetworkDialogParams,
+ });
+};
diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts
index 6a9580cb7f..5903bdc044 100644
--- a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts
+++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts
@@ -1,6 +1,6 @@
import "@material/mwc-button/mwc-button";
import "@material/mwc-icon-button/mwc-icon-button";
-import { mdiCheckCircle, mdiCircle, mdiRefresh } from "@mdi/js";
+import { mdiAlertCircle, mdiCheckCircle, mdiCircle, mdiRefresh } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
@@ -17,6 +17,11 @@ import {
ZWaveJSNetwork,
ZWaveJSNode,
} from "../../../../../data/zwave_js";
+import {
+ ConfigEntry,
+ getConfigEntries,
+ ERROR_STATES,
+} from "../../../../../data/config_entries";
import {
showAlertDialog,
showConfirmationDialog,
@@ -24,10 +29,13 @@ import {
import "../../../../../layouts/hass-tabs-subpage";
import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant, Route } from "../../../../../types";
+import { fileDownload } from "../../../../../util/file_download";
import "../../../ha-config-section";
import { showZWaveJSAddNodeDialog } from "./show-dialog-zwave_js-add-node";
+import { showZWaveJSHealNetworkDialog } from "./show-dialog-zwave_js-heal-network";
import { showZWaveJSRemoveNodeDialog } from "./show-dialog-zwave_js-remove-node";
import { configTabs } from "./zwave_js-config-router";
+import { showOptionsFlowDialog } from "../../../../../dialogs/config-flow/show-dialog-options-flow";
@customElement("zwave_js-config-dashboard")
class ZWaveJSConfigDashboard extends LitElement {
@@ -41,6 +49,8 @@ class ZWaveJSConfigDashboard extends LitElement {
@property() public configEntryId?: string;
+ @state() private _configEntry?: ConfigEntry;
+
@state() private _network?: ZWaveJSNetwork;
@state() private _nodes?: ZWaveJSNode[];
@@ -58,6 +68,14 @@ class ZWaveJSConfigDashboard extends LitElement {
}
protected render(): TemplateResult {
+ if (!this._configEntry) {
+ return html``;
+ }
+
+ if (ERROR_STATES.includes(this._configEntry.state)) {
+ return this._renderErrorScreen();
+ }
+
return html`
+
+ ${this.hass.localize(
+ "ui.panel.config.zwave_js.common.heal_network"
+ )}
+
+
+ ${this.hass.localize(
+ "ui.panel.config.zwave_js.common.reconfigure_server"
+ )}
+
@@ -208,10 +236,83 @@ class ZWaveJSConfigDashboard extends LitElement {
`;
}
+ private _renderErrorScreen() {
+ const item = this._configEntry!;
+ let stateText: [string, ...unknown[]] | undefined;
+ let stateTextExtra: TemplateResult | string | undefined;
+
+ if (item.disabled_by) {
+ stateText = [
+ "ui.panel.config.integrations.config_entry.disable.disabled_cause",
+ {
+ cause:
+ this.hass.localize(
+ `ui.panel.config.integrations.config_entry.disable.disabled_by.${item.disabled_by}`
+ ) || item.disabled_by,
+ },
+ ];
+ if (item.state === "failed_unload") {
+ stateTextExtra = html`.
+ ${this.hass.localize(
+ "ui.panel.config.integrations.config_entry.disable_restart_confirm"
+ )}.`;
+ }
+ } else if (item.state === "not_loaded") {
+ stateText = ["ui.panel.config.integrations.config_entry.not_loaded"];
+ } else if (ERROR_STATES.includes(item.state)) {
+ stateText = [
+ `ui.panel.config.integrations.config_entry.state.${item.state}`,
+ ];
+ if (item.reason) {
+ this.hass.loadBackendTranslation("config", item.domain);
+ stateTextExtra = html` ${this.hass.localize(
+ `component.${item.domain}.config.error.${item.reason}`
+ ) || item.reason}`;
+ } else {
+ stateTextExtra = html`
+
+ ${this.hass.localize(
+ "ui.panel.config.integrations.config_entry.check_the_logs"
+ )}
+ `;
+ }
+ }
+
+ return html` ${stateText
+ ? html`
+
+
+
+ ${this._configEntry!.title}: ${this.hass.localize(...stateText)}
+
+
${stateTextExtra}
+
+ ${this.hass?.localize("ui.panel.error.go_back") || "go back"}
+
+
+ `
+ : ""}`;
+ }
+
+ private _handleBack(): void {
+ history.back();
+ }
+
private async _fetchData() {
if (!this.configEntryId) {
return;
}
+ const configEntries = await getConfigEntries(this.hass);
+ this._configEntry = configEntries.find(
+ (entry) => entry.entry_id === this.configEntryId!
+ );
+
+ if (ERROR_STATES.includes(this._configEntry!.state)) {
+ return;
+ }
+
const [network, dataCollectionStatus] = await Promise.all([
fetchNetworkStatus(this.hass!, this.configEntryId),
fetchDataCollectionStatus(this.hass!, this.configEntryId),
@@ -253,6 +354,12 @@ class ZWaveJSConfigDashboard extends LitElement {
});
}
+ private async _healNetworkClicked() {
+ showZWaveJSHealNetworkDialog(this, {
+ entry_id: this.configEntryId!,
+ });
+ }
+
private _dataCollectionToggled(ev) {
setDataCollectionPreference(
this.hass!,
@@ -261,6 +368,17 @@ class ZWaveJSConfigDashboard extends LitElement {
);
}
+ private async _openOptionFlow() {
+ if (!this.configEntryId) {
+ return;
+ }
+ const configEntries = await getConfigEntries(this.hass);
+ const configEntry = configEntries.find(
+ (entry) => entry.entry_id === this.configEntryId
+ );
+ showOptionsFlowDialog(this, configEntry!);
+ }
+
private async _dumpDebugClicked() {
await this._fetchNodeStatus();
@@ -312,12 +430,7 @@ class ZWaveJSConfigDashboard extends LitElement {
return;
}
- const a = document.createElement("a");
- a.href = signedPath.path;
- a.download = `zwave_js_dump.jsonl`;
- this.shadowRoot!.appendChild(a);
- a.click();
- this.shadowRoot!.removeChild(a);
+ fileDownload(this, signedPath.path, `zwave_js_dump.jsonl`);
}
static get styles(): CSSResultGroup {
@@ -337,6 +450,27 @@ class ZWaveJSConfigDashboard extends LitElement {
color: red;
}
+ .error-message {
+ display: flex;
+ color: var(--primary-text-color);
+ height: calc(100% - var(--header-height));
+ padding: 16px;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+ }
+
+ .error-message h3 {
+ text-align: center;
+ font-weight: bold;
+ }
+
+ .error-message ha-svg-icon {
+ color: var(--error-color);
+ width: 64px;
+ height: 64px;
+ }
+
.content {
margin-top: 24px;
}
diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-logs.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-logs.ts
index 37a984666e..0e380a18b3 100644
--- a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-logs.ts
+++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-logs.ts
@@ -1,5 +1,6 @@
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-listbox/paper-listbox";
+import { mdiDownload } from "@mdi/js";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { css, CSSResultArray, html, LitElement } from "lit";
import { customElement, property, state, query } from "lit/decorators";
@@ -13,6 +14,7 @@ import "../../../../../layouts/hass-tabs-subpage";
import { SubscribeMixin } from "../../../../../mixins/subscribe-mixin";
import { haStyle } from "../../../../../resources/styles";
import { HomeAssistant, Route } from "../../../../../types";
+import { fileDownload } from "../../../../../util/file_download";
import { configTabs } from "./zwave_js-config-router";
@customElement("zwave_js-logs")
@@ -31,16 +33,20 @@ class ZWaveJSLogs extends SubscribeMixin(LitElement) {
public hassSubscribe(): Array> {
return [
- subscribeZWaveJSLogs(this.hass, this.configEntryId, (log) => {
+ subscribeZWaveJSLogs(this.hass, this.configEntryId, (update) => {
if (!this.hasUpdated) {
return;
}
- if (Array.isArray(log.message)) {
- for (const line of log.message) {
- this._textarea!.value += `${line}\n`;
+ if (update.type === "log_message") {
+ if (Array.isArray(update.log_message.message)) {
+ for (const line of update.log_message.message) {
+ this._textarea!.value += `${line}\n`;
+ }
+ } else {
+ this._textarea!.value += `${update.log_message.message}\n`;
}
} else {
- this._textarea!.value += `${log.message}\n`;
+ this._logConfig = update.log_config;
}
}).then((unsub) => {
this._textarea!.value += `${this.hass.localize(
@@ -92,6 +98,14 @@ class ZWaveJSLogs extends SubscribeMixin(LitElement) {
`
: ""}
+
+
+
@@ -114,6 +128,14 @@ class ZWaveJSLogs extends SubscribeMixin(LitElement) {
);
}
+ private _downloadLogs() {
+ fileDownload(
+ this,
+ `data:text/plain;charset=utf-8,${encodeURI(this._textarea!.value)}`,
+ `zwave_js.log`
+ );
+ }
+
private _dropdownSelected(ev) {
if (ev.target === undefined || this._logConfig === undefined) {
return;
@@ -123,7 +145,6 @@ class ZWaveJSLogs extends SubscribeMixin(LitElement) {
return;
}
setZWaveJSLogLevel(this.hass!, this.configEntryId, selected);
- this._logConfig.level = selected;
this._textarea!.value += `${this.hass.localize(
"ui.panel.config.zwave_js.logs.log_level_changed",
{ level: selected.charAt(0).toUpperCase() + selected.slice(1) }
diff --git a/src/panels/config/zone/dialog-zone-detail.ts b/src/panels/config/zone/dialog-zone-detail.ts
index ac4cedc32a..f49ab613e3 100644
--- a/src/panels/config/zone/dialog-zone-detail.ts
+++ b/src/panels/config/zone/dialog-zone-detail.ts
@@ -9,13 +9,9 @@ import { computeRTLDirection } from "../../../common/util/compute_rtl";
import { createCloseHeading } from "../../../components/ha-dialog";
import "../../../components/ha-formfield";
import "../../../components/ha-switch";
-import "../../../components/map/ha-location-editor";
-import {
- defaultRadiusColor,
- getZoneEditorInitData,
- passiveRadiusColor,
- ZoneMutableParams,
-} from "../../../data/zone";
+import "../../../components/map/ha-locations-editor";
+import type { MarkerLocation } from "../../../components/map/ha-locations-editor";
+import { getZoneEditorInitData, ZoneMutableParams } from "../../../data/zone";
import { haStyleDialog } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import { ZoneDetailDialogParams } from "./show-dialog-zone-detail";
@@ -132,17 +128,19 @@ class DialogZoneDetail extends LitElement {
)}"
.invalid=${iconValid}
>