mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-14 04:46:34 +00:00
Better disabled/error handling on config/helpers
page (#22237)
* Add a way to fix/remove broken helpers * more disabled/sources fixes * Update src/translations/en.json Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com> * Update ha-config-helpers.ts --------- Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>
This commit is contained in:
parent
9e002f7940
commit
e1830470b6
@ -3,19 +3,23 @@ import { ResizeController } from "@lit-labs/observers/resize-controller";
|
|||||||
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
|
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
|
||||||
import {
|
import {
|
||||||
mdiAlertCircle,
|
mdiAlertCircle,
|
||||||
|
mdiCancel,
|
||||||
mdiChevronRight,
|
mdiChevronRight,
|
||||||
mdiCog,
|
mdiCog,
|
||||||
mdiDotsVertical,
|
mdiDotsVertical,
|
||||||
mdiMenuDown,
|
mdiMenuDown,
|
||||||
mdiPencilOff,
|
mdiPencilOff,
|
||||||
|
mdiProgressHelper,
|
||||||
mdiPlus,
|
mdiPlus,
|
||||||
mdiTag,
|
mdiTag,
|
||||||
|
mdiTrashCan,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import type { HassEntity } from "home-assistant-js-websocket";
|
import type { HassEntity } from "home-assistant-js-websocket";
|
||||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||||
import { LitElement, css, html, nothing } from "lit";
|
import { LitElement, css, html, nothing } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
|
import { debounce } from "../../../common/util/debounce";
|
||||||
import { computeCssColor } from "../../../common/color/compute-color";
|
import { computeCssColor } from "../../../common/color/compute-color";
|
||||||
import { storage } from "../../../common/decorators/storage";
|
import { storage } from "../../../common/decorators/storage";
|
||||||
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||||
@ -54,7 +58,11 @@ import {
|
|||||||
subscribeCategoryRegistry,
|
subscribeCategoryRegistry,
|
||||||
} from "../../../data/category_registry";
|
} from "../../../data/category_registry";
|
||||||
import type { ConfigEntry } from "../../../data/config_entries";
|
import type { ConfigEntry } from "../../../data/config_entries";
|
||||||
import { subscribeConfigEntries } from "../../../data/config_entries";
|
import {
|
||||||
|
ERROR_STATES,
|
||||||
|
deleteConfigEntry,
|
||||||
|
subscribeConfigEntries,
|
||||||
|
} from "../../../data/config_entries";
|
||||||
import { getConfigFlowHandlers } from "../../../data/config_flow";
|
import { getConfigFlowHandlers } from "../../../data/config_flow";
|
||||||
import { fullEntitiesContext } from "../../../data/context";
|
import { fullEntitiesContext } from "../../../data/context";
|
||||||
import type {
|
import type {
|
||||||
@ -97,6 +105,7 @@ import { showAssignCategoryDialog } from "../category/show-dialog-assign-categor
|
|||||||
import { showCategoryRegistryDetailDialog } from "../category/show-dialog-category-registry-detail";
|
import { showCategoryRegistryDetailDialog } from "../category/show-dialog-category-registry-detail";
|
||||||
import { configSections } from "../ha-panel-config";
|
import { configSections } from "../ha-panel-config";
|
||||||
import "../integrations/ha-integration-overflow-menu";
|
import "../integrations/ha-integration-overflow-menu";
|
||||||
|
import { renderConfigEntryError } from "../integrations/ha-config-integration-page";
|
||||||
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
|
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
|
||||||
import { isHelperDomain } from "./const";
|
import { isHelperDomain } from "./const";
|
||||||
import { showHelperDetailDialog } from "./show-dialog-helper-detail";
|
import { showHelperDetailDialog } from "./show-dialog-helper-detail";
|
||||||
@ -220,6 +229,12 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
|||||||
callback: (entries) => entries[0]?.contentRect.width,
|
callback: (entries) => entries[0]?.contentRect.width,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
private _debouncedFetchEntitySources = debounce(
|
||||||
|
() => this._fetchEntitySources(),
|
||||||
|
500,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
public hassSubscribe() {
|
public hassSubscribe() {
|
||||||
return [
|
return [
|
||||||
subscribeConfigEntries(
|
subscribeConfigEntries(
|
||||||
@ -236,6 +251,14 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
|||||||
} else if (message.type === "updated") {
|
} else if (message.type === "updated") {
|
||||||
newEntries[message.entry.entry_id] = message.entry;
|
newEntries[message.entry.entry_id] = message.entry;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
this._entitySource &&
|
||||||
|
this._configEntries &&
|
||||||
|
message.entry.state === "loaded" &&
|
||||||
|
this._configEntries[message.entry.entry_id]?.state !== "loaded"
|
||||||
|
) {
|
||||||
|
this._debouncedFetchEntitySources();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
this._configEntries = newEntries;
|
this._configEntries = newEntries;
|
||||||
},
|
},
|
||||||
@ -352,6 +375,19 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
narrow
|
narrow
|
||||||
.items=${[
|
.items=${[
|
||||||
|
...(helper.configEntry &&
|
||||||
|
ERROR_STATES.includes(helper.configEntry.state)
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
path: mdiAlertCircle,
|
||||||
|
label: this.hass.localize(
|
||||||
|
"ui.panel.config.helpers.picker.error_information"
|
||||||
|
),
|
||||||
|
warning: true,
|
||||||
|
action: () => this._showError(helper),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
{
|
{
|
||||||
path: mdiCog,
|
path: mdiCog,
|
||||||
label: this.hass.localize(
|
label: this.hass.localize(
|
||||||
@ -366,6 +402,19 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
|||||||
),
|
),
|
||||||
action: () => this._editCategory(helper),
|
action: () => this._editCategory(helper),
|
||||||
},
|
},
|
||||||
|
...(helper.configEntry &&
|
||||||
|
helper.editable &&
|
||||||
|
ERROR_STATES.includes(helper.configEntry.state) &&
|
||||||
|
helper.entity === undefined
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
path: mdiTrashCan,
|
||||||
|
label: this.hass.localize("ui.common.delete"),
|
||||||
|
warning: true,
|
||||||
|
action: () => this._deleteEntry(helper),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
</ha-icon-overflow-menu>
|
</ha-icon-overflow-menu>
|
||||||
@ -417,17 +466,27 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const entries = Object.values(configEntriesCopy).map((configEntry) => ({
|
const entries = Object.values(configEntriesCopy).map((configEntry) => {
|
||||||
id: configEntry.entry_id,
|
const entityEntry = Object.values(entityEntries).find(
|
||||||
entity_id: "",
|
(entry) => entry.config_entry_id === configEntry.entry_id
|
||||||
icon: mdiAlertCircle,
|
);
|
||||||
|
const entityIsDisabled = !!entityEntry?.disabled_by;
|
||||||
|
return {
|
||||||
|
id: entityIsDisabled ? entityEntry.entity_id : configEntry.entry_id,
|
||||||
|
entity_id: entityIsDisabled ? entityEntry.entity_id : "",
|
||||||
|
icon: entityIsDisabled
|
||||||
|
? mdiCancel
|
||||||
|
: configEntry.state === "setup_in_progress"
|
||||||
|
? mdiProgressHelper
|
||||||
|
: mdiAlertCircle,
|
||||||
name: configEntry.title || "",
|
name: configEntry.title || "",
|
||||||
editable: true,
|
editable: true,
|
||||||
type: configEntry.domain,
|
type: configEntry.domain,
|
||||||
configEntry,
|
configEntry,
|
||||||
entity: undefined,
|
entity: undefined,
|
||||||
selectable: false,
|
selectable: entityIsDisabled,
|
||||||
}));
|
};
|
||||||
|
});
|
||||||
|
|
||||||
return [...states, ...entries]
|
return [...states, ...entries]
|
||||||
.filter((item) =>
|
.filter((item) =>
|
||||||
@ -1081,6 +1140,34 @@ ${rejected
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _showError(helper: HelperItem) {
|
||||||
|
showAlertDialog(this, {
|
||||||
|
title: this.hass.localize("ui.errors.config.configuration_error"),
|
||||||
|
text: renderConfigEntryError(this.hass, helper.configEntry!),
|
||||||
|
warning: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _deleteEntry(helper: HelperItem) {
|
||||||
|
const confirmed = await showConfirmationDialog(this, {
|
||||||
|
title: this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.delete_confirm_title",
|
||||||
|
{ title: helper.configEntry!.title }
|
||||||
|
),
|
||||||
|
text: this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.delete_confirm_text"
|
||||||
|
),
|
||||||
|
confirmText: this.hass!.localize("ui.common.delete"),
|
||||||
|
dismissText: this.hass!.localize("ui.common.cancel"),
|
||||||
|
destructive: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
deleteConfigEntry(this.hass, helper.id);
|
||||||
|
}
|
||||||
|
|
||||||
private _openSettings(helper: HelperItem) {
|
private _openSettings(helper: HelperItem) {
|
||||||
if (helper.entity) {
|
if (helper.entity) {
|
||||||
showMoreInfoDialog(this, {
|
showMoreInfoDialog(this, {
|
||||||
|
@ -106,6 +106,38 @@ import { fileDownload } from "../../../util/file_download";
|
|||||||
import type { DataEntryFlowProgressExtended } from "./ha-config-integrations";
|
import type { DataEntryFlowProgressExtended } from "./ha-config-integrations";
|
||||||
import { showAddIntegrationDialog } from "./show-add-integration-dialog";
|
import { showAddIntegrationDialog } from "./show-add-integration-dialog";
|
||||||
|
|
||||||
|
export const renderConfigEntryError = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry: ConfigEntry
|
||||||
|
): TemplateResult => {
|
||||||
|
if (entry.reason) {
|
||||||
|
if (entry.error_reason_translation_key) {
|
||||||
|
const lokalisePromExc = hass
|
||||||
|
.loadBackendTranslation("exceptions", entry.domain)
|
||||||
|
.then(
|
||||||
|
(localize) =>
|
||||||
|
localize(
|
||||||
|
`component.${entry.domain}.exceptions.${entry.error_reason_translation_key}.message`,
|
||||||
|
entry.error_reason_translation_placeholders ?? undefined
|
||||||
|
) || entry.reason
|
||||||
|
);
|
||||||
|
return html`${until(lokalisePromExc)}`;
|
||||||
|
}
|
||||||
|
const lokalisePromError = hass
|
||||||
|
.loadBackendTranslation("config", entry.domain)
|
||||||
|
.then(
|
||||||
|
(localize) =>
|
||||||
|
localize(`component.${entry.domain}.config.error.${entry.reason}`) ||
|
||||||
|
entry.reason
|
||||||
|
);
|
||||||
|
return html`${until(lokalisePromError, entry.reason)}`;
|
||||||
|
}
|
||||||
|
return html`
|
||||||
|
<br />
|
||||||
|
${hass.localize("ui.panel.config.integrations.config_entry.check_the_logs")}
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
@customElement("ha-config-integration-page")
|
@customElement("ha-config-integration-page")
|
||||||
class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@ -618,37 +650,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
|||||||
stateText = [
|
stateText = [
|
||||||
`ui.panel.config.integrations.config_entry.state.${item.state}`,
|
`ui.panel.config.integrations.config_entry.state.${item.state}`,
|
||||||
];
|
];
|
||||||
if (item.reason) {
|
stateTextExtra = renderConfigEntryError(this.hass, item);
|
||||||
if (item.error_reason_translation_key) {
|
|
||||||
const lokalisePromExc = this.hass
|
|
||||||
.loadBackendTranslation("exceptions", item.domain)
|
|
||||||
.then(
|
|
||||||
(localize) =>
|
|
||||||
localize(
|
|
||||||
`component.${item.domain}.exceptions.${item.error_reason_translation_key}.message`,
|
|
||||||
item.error_reason_translation_placeholders ?? undefined
|
|
||||||
) || item.reason
|
|
||||||
);
|
|
||||||
stateTextExtra = html`${until(lokalisePromExc)}`;
|
|
||||||
} else {
|
|
||||||
const lokalisePromError = this.hass
|
|
||||||
.loadBackendTranslation("config", item.domain)
|
|
||||||
.then(
|
|
||||||
(localize) =>
|
|
||||||
localize(
|
|
||||||
`component.${item.domain}.config.error.${item.reason}`
|
|
||||||
) || item.reason
|
|
||||||
);
|
|
||||||
stateTextExtra = html`${until(lokalisePromError, item.reason)}`;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
stateTextExtra = html`
|
|
||||||
<br />
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.integrations.config_entry.check_the_logs"
|
|
||||||
)}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const devices = this._getConfigEntryDevices(item);
|
const devices = this._getConfigEntryDevices(item);
|
||||||
|
@ -2343,7 +2343,8 @@
|
|||||||
},
|
},
|
||||||
"create_helper": "Create helper",
|
"create_helper": "Create helper",
|
||||||
"no_helpers": "Looks like you don't have any helpers yet!",
|
"no_helpers": "Looks like you don't have any helpers yet!",
|
||||||
"search": "Search {number} helpers"
|
"search": "Search {number} helpers",
|
||||||
|
"error_information": "Error information"
|
||||||
},
|
},
|
||||||
"dialog": {
|
"dialog": {
|
||||||
"create": "Create",
|
"create": "Create",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user