Add base support for sub entries (#23160)
* Add base support for sub entries * add demo types * fix translations * Use sub entry name when deleting * Update show-dialog-sub-config-flow.ts * adjust for multiple sub types * WIP, not functional * add subentry_type * rename to supported_subentry_types * config_subentries -> config_entries_subentries * Add localized sub flow title * use Record * rename * more renamepull/23358/head^2
parent
03a415beff
commit
0d97afb3f2
|
@ -65,6 +65,7 @@ export class HaDemo extends HomeAssistantAppEl {
|
||||||
mockEntityRegistry(hass, [
|
mockEntityRegistry(hass, [
|
||||||
{
|
{
|
||||||
config_entry_id: "co2signal",
|
config_entry_id: "co2signal",
|
||||||
|
config_subentry_id: null,
|
||||||
device_id: "co2signal",
|
device_id: "co2signal",
|
||||||
area_id: null,
|
area_id: null,
|
||||||
disabled_by: null,
|
disabled_by: null,
|
||||||
|
@ -85,6 +86,7 @@ export class HaDemo extends HomeAssistantAppEl {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
config_entry_id: "co2signal",
|
config_entry_id: "co2signal",
|
||||||
|
config_subentry_id: null,
|
||||||
device_id: "co2signal",
|
device_id: "co2signal",
|
||||||
area_id: null,
|
area_id: null,
|
||||||
disabled_by: null,
|
disabled_by: null,
|
||||||
|
|
|
@ -11,6 +11,7 @@ export const mockConfigEntries = (hass: MockHomeAssistant) => {
|
||||||
supports_remove_device: false,
|
supports_remove_device: false,
|
||||||
supports_unload: true,
|
supports_unload: true,
|
||||||
supports_reconfigure: true,
|
supports_reconfigure: true,
|
||||||
|
supported_subentry_types: {},
|
||||||
pref_disable_new_entities: false,
|
pref_disable_new_entities: false,
|
||||||
pref_disable_polling: false,
|
pref_disable_polling: false,
|
||||||
disabled_by: null,
|
disabled_by: null,
|
||||||
|
|
|
@ -48,6 +48,7 @@ const DEVICES: DeviceRegistryEntry[] = [
|
||||||
area_id: "bedroom",
|
area_id: "bedroom",
|
||||||
configuration_url: null,
|
configuration_url: null,
|
||||||
config_entries: ["config_entry_1"],
|
config_entries: ["config_entry_1"],
|
||||||
|
config_entries_subentries: {},
|
||||||
connections: [],
|
connections: [],
|
||||||
disabled_by: null,
|
disabled_by: null,
|
||||||
entry_type: null,
|
entry_type: null,
|
||||||
|
@ -71,6 +72,7 @@ const DEVICES: DeviceRegistryEntry[] = [
|
||||||
area_id: "backyard",
|
area_id: "backyard",
|
||||||
configuration_url: null,
|
configuration_url: null,
|
||||||
config_entries: ["config_entry_2"],
|
config_entries: ["config_entry_2"],
|
||||||
|
config_entries_subentries: {},
|
||||||
connections: [],
|
connections: [],
|
||||||
disabled_by: null,
|
disabled_by: null,
|
||||||
entry_type: null,
|
entry_type: null,
|
||||||
|
@ -94,6 +96,7 @@ const DEVICES: DeviceRegistryEntry[] = [
|
||||||
area_id: null,
|
area_id: null,
|
||||||
configuration_url: null,
|
configuration_url: null,
|
||||||
config_entries: ["config_entry_3"],
|
config_entries: ["config_entry_3"],
|
||||||
|
config_entries_subentries: {},
|
||||||
connections: [],
|
connections: [],
|
||||||
disabled_by: null,
|
disabled_by: null,
|
||||||
entry_type: null,
|
entry_type: null,
|
||||||
|
|
|
@ -47,6 +47,7 @@ const DEVICES: DeviceRegistryEntry[] = [
|
||||||
area_id: "bedroom",
|
area_id: "bedroom",
|
||||||
configuration_url: null,
|
configuration_url: null,
|
||||||
config_entries: ["config_entry_1"],
|
config_entries: ["config_entry_1"],
|
||||||
|
config_entries_subentries: {},
|
||||||
connections: [],
|
connections: [],
|
||||||
disabled_by: null,
|
disabled_by: null,
|
||||||
entry_type: null,
|
entry_type: null,
|
||||||
|
@ -70,6 +71,7 @@ const DEVICES: DeviceRegistryEntry[] = [
|
||||||
area_id: "backyard",
|
area_id: "backyard",
|
||||||
configuration_url: null,
|
configuration_url: null,
|
||||||
config_entries: ["config_entry_2"],
|
config_entries: ["config_entry_2"],
|
||||||
|
config_entries_subentries: {},
|
||||||
connections: [],
|
connections: [],
|
||||||
disabled_by: null,
|
disabled_by: null,
|
||||||
entry_type: null,
|
entry_type: null,
|
||||||
|
@ -93,6 +95,7 @@ const DEVICES: DeviceRegistryEntry[] = [
|
||||||
area_id: null,
|
area_id: null,
|
||||||
configuration_url: null,
|
configuration_url: null,
|
||||||
config_entries: ["config_entry_3"],
|
config_entries: ["config_entry_3"],
|
||||||
|
config_entries_subentries: {},
|
||||||
connections: [],
|
connections: [],
|
||||||
disabled_by: null,
|
disabled_by: null,
|
||||||
entry_type: null,
|
entry_type: null,
|
||||||
|
|
|
@ -32,6 +32,8 @@ const createConfigEntry = (
|
||||||
supports_remove_device: false,
|
supports_remove_device: false,
|
||||||
supports_unload: true,
|
supports_unload: true,
|
||||||
supports_reconfigure: true,
|
supports_reconfigure: true,
|
||||||
|
supported_subentry_types: {},
|
||||||
|
num_subentries: 0,
|
||||||
disabled_by: null,
|
disabled_by: null,
|
||||||
pref_disable_new_entities: false,
|
pref_disable_new_entities: false,
|
||||||
pref_disable_polling: false,
|
pref_disable_polling: false,
|
||||||
|
@ -188,6 +190,7 @@ const createEntityRegistryEntries = (
|
||||||
): EntityRegistryEntry[] => [
|
): EntityRegistryEntry[] => [
|
||||||
{
|
{
|
||||||
config_entry_id: item.entry_id,
|
config_entry_id: item.entry_id,
|
||||||
|
config_subentry_id: null,
|
||||||
device_id: "mock-device-id",
|
device_id: "mock-device-id",
|
||||||
area_id: null,
|
area_id: null,
|
||||||
disabled_by: null,
|
disabled_by: null,
|
||||||
|
@ -214,6 +217,7 @@ const createDeviceRegistryEntries = (
|
||||||
{
|
{
|
||||||
entry_type: null,
|
entry_type: null,
|
||||||
config_entries: [item.entry_id],
|
config_entries: [item.entry_id],
|
||||||
|
config_entries_subentries: {},
|
||||||
connections: [],
|
connections: [],
|
||||||
manufacturer: "ESPHome",
|
manufacturer: "ESPHome",
|
||||||
model: "Mock Device",
|
model: "Mock Device",
|
||||||
|
|
|
@ -19,6 +19,8 @@ export interface ConfigEntry {
|
||||||
supports_remove_device: boolean;
|
supports_remove_device: boolean;
|
||||||
supports_unload: boolean;
|
supports_unload: boolean;
|
||||||
supports_reconfigure: boolean;
|
supports_reconfigure: boolean;
|
||||||
|
supported_subentry_types: Record<string, { supports_reconfigure: boolean }>;
|
||||||
|
num_subentries: number;
|
||||||
pref_disable_new_entities: boolean;
|
pref_disable_new_entities: boolean;
|
||||||
pref_disable_polling: boolean;
|
pref_disable_polling: boolean;
|
||||||
disabled_by: "user" | null;
|
disabled_by: "user" | null;
|
||||||
|
@ -27,6 +29,30 @@ export interface ConfigEntry {
|
||||||
error_reason_translation_placeholders: Record<string, string> | null;
|
error_reason_translation_placeholders: Record<string, string> | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SubEntry {
|
||||||
|
subentry_id: string;
|
||||||
|
subentry_type: string;
|
||||||
|
title: string;
|
||||||
|
unique_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getSubEntries = (hass: HomeAssistant, entry_id: string) =>
|
||||||
|
hass.callWS<SubEntry[]>({
|
||||||
|
type: "config_entries/subentries/list",
|
||||||
|
entry_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const deleteSubEntry = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry_id: string,
|
||||||
|
subentry_id: string
|
||||||
|
) =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "config_entries/subentries/delete",
|
||||||
|
entry_id,
|
||||||
|
subentry_id,
|
||||||
|
});
|
||||||
|
|
||||||
export type ConfigEntryMutableParams = Partial<
|
export type ConfigEntryMutableParams = Partial<
|
||||||
Pick<
|
Pick<
|
||||||
ConfigEntry,
|
ConfigEntry,
|
||||||
|
|
|
@ -2,7 +2,11 @@ import type { Connection } from "home-assistant-js-websocket";
|
||||||
import type { HaFormSchema } from "../components/ha-form/types";
|
import type { HaFormSchema } from "../components/ha-form/types";
|
||||||
import type { ConfigEntry } from "./config_entries";
|
import type { ConfigEntry } from "./config_entries";
|
||||||
|
|
||||||
export type FlowType = "config_flow" | "options_flow" | "repair_flow";
|
export type FlowType =
|
||||||
|
| "config_flow"
|
||||||
|
| "config_subentries_flow"
|
||||||
|
| "options_flow"
|
||||||
|
| "repair_flow";
|
||||||
|
|
||||||
export interface DataEntryFlowProgressedEvent {
|
export interface DataEntryFlowProgressedEvent {
|
||||||
type: "data_entry_flow_progressed";
|
type: "data_entry_flow_progressed";
|
||||||
|
|
|
@ -17,6 +17,7 @@ export {
|
||||||
export interface DeviceRegistryEntry extends RegistryEntry {
|
export interface DeviceRegistryEntry extends RegistryEntry {
|
||||||
id: string;
|
id: string;
|
||||||
config_entries: string[];
|
config_entries: string[];
|
||||||
|
config_entries_subentries: Record<string, (string | null)[]>;
|
||||||
connections: [string, string][];
|
connections: [string, string][];
|
||||||
identifiers: [string, string][];
|
identifiers: [string, string][];
|
||||||
manufacturer: string | null;
|
manufacturer: string | null;
|
||||||
|
|
|
@ -50,6 +50,7 @@ export interface EntityRegistryEntry extends RegistryEntry {
|
||||||
icon: string | null;
|
icon: string | null;
|
||||||
platform: string;
|
platform: string;
|
||||||
config_entry_id: string | null;
|
config_entry_id: string | null;
|
||||||
|
config_subentry_id: string | null;
|
||||||
device_id: string | null;
|
device_id: string | null;
|
||||||
area_id: string | null;
|
area_id: string | null;
|
||||||
labels: string[];
|
labels: string[];
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
import type { HomeAssistant } from "../types";
|
||||||
|
import type { DataEntryFlowStep } from "./data_entry_flow";
|
||||||
|
|
||||||
|
const HEADERS = {
|
||||||
|
"HA-Frontend-Base": `${location.protocol}//${location.host}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createSubConfigFlow = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
configEntryId: string,
|
||||||
|
subFlowType: string,
|
||||||
|
subentry_id?: string
|
||||||
|
) =>
|
||||||
|
hass.callApi<DataEntryFlowStep>(
|
||||||
|
"POST",
|
||||||
|
"config/config_entries/subentries/flow",
|
||||||
|
{
|
||||||
|
handler: [configEntryId, subFlowType],
|
||||||
|
show_advanced_options: Boolean(hass.userData?.showAdvanced),
|
||||||
|
subentry_id,
|
||||||
|
},
|
||||||
|
HEADERS
|
||||||
|
);
|
||||||
|
|
||||||
|
export const fetchSubConfigFlow = (hass: HomeAssistant, flowId: string) =>
|
||||||
|
hass.callApi<DataEntryFlowStep>(
|
||||||
|
"GET",
|
||||||
|
`config/config_entries/subentries/flow/${flowId}`,
|
||||||
|
undefined,
|
||||||
|
HEADERS
|
||||||
|
);
|
||||||
|
|
||||||
|
export const handleSubConfigFlowStep = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
flowId: string,
|
||||||
|
data: Record<string, any>
|
||||||
|
) =>
|
||||||
|
hass.callApi<DataEntryFlowStep>(
|
||||||
|
"POST",
|
||||||
|
`config/config_entries/subentries/flow/${flowId}`,
|
||||||
|
data,
|
||||||
|
HEADERS
|
||||||
|
);
|
||||||
|
|
||||||
|
export const deleteSubConfigFlow = (hass: HomeAssistant, flowId: string) =>
|
||||||
|
hass.callApi("DELETE", `config/config_entries/subentries/flow/${flowId}`);
|
|
@ -63,6 +63,7 @@ export type TranslationCategory =
|
||||||
| "entity_component"
|
| "entity_component"
|
||||||
| "exceptions"
|
| "exceptions"
|
||||||
| "config"
|
| "config"
|
||||||
|
| "config_subentries"
|
||||||
| "config_panel"
|
| "config_panel"
|
||||||
| "options"
|
| "options"
|
||||||
| "device_automation"
|
| "device_automation"
|
||||||
|
|
|
@ -77,7 +77,7 @@ export class FlowPreviewGeneric extends LitElement {
|
||||||
(await this._unsub)();
|
(await this._unsub)();
|
||||||
this._unsub = undefined;
|
this._unsub = undefined;
|
||||||
}
|
}
|
||||||
if (this.flowType === "repair_flow") {
|
if (this.flowType !== "config_flow" && this.flowType !== "options_flow") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -147,7 +147,7 @@ class FlowPreviewTemplate extends LitElement {
|
||||||
(await this._unsub)();
|
(await this._unsub)();
|
||||||
this._unsub = undefined;
|
this._unsub = undefined;
|
||||||
}
|
}
|
||||||
if (this.flowType === "repair_flow") {
|
if (this.flowType !== "config_flow" && this.flowType !== "options_flow") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -16,7 +16,9 @@ export const loadConfigFlowDialog = loadDataEntryFlowDialog;
|
||||||
|
|
||||||
export const showConfigFlowDialog = (
|
export const showConfigFlowDialog = (
|
||||||
element: HTMLElement,
|
element: HTMLElement,
|
||||||
dialogParams: Omit<DataEntryFlowDialogParams, "flowConfig">
|
dialogParams: Omit<DataEntryFlowDialogParams, "flowConfig"> & {
|
||||||
|
entryId?: string;
|
||||||
|
}
|
||||||
): void =>
|
): void =>
|
||||||
showFlowDialog(element, dialogParams, {
|
showFlowDialog(element, dialogParams, {
|
||||||
flowType: "config_flow",
|
flowType: "config_flow",
|
||||||
|
|
|
@ -148,7 +148,6 @@ export interface DataEntryFlowDialogParams {
|
||||||
}) => void;
|
}) => void;
|
||||||
flowConfig: FlowConfig;
|
flowConfig: FlowConfig;
|
||||||
showAdvanced?: boolean;
|
showAdvanced?: boolean;
|
||||||
entryId?: string;
|
|
||||||
dialogParentElement?: HTMLElement;
|
dialogParentElement?: HTMLElement;
|
||||||
navigateToResult?: boolean;
|
navigateToResult?: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,275 @@
|
||||||
|
import { html } from "lit";
|
||||||
|
import type { ConfigEntry } from "../../data/config_entries";
|
||||||
|
import { domainToName } from "../../data/integration";
|
||||||
|
import {
|
||||||
|
createSubConfigFlow,
|
||||||
|
deleteSubConfigFlow,
|
||||||
|
fetchSubConfigFlow,
|
||||||
|
handleSubConfigFlowStep,
|
||||||
|
} from "../../data/sub_config_flow";
|
||||||
|
import type { DataEntryFlowDialogParams } from "./show-dialog-data-entry-flow";
|
||||||
|
import {
|
||||||
|
loadDataEntryFlowDialog,
|
||||||
|
showFlowDialog,
|
||||||
|
} from "./show-dialog-data-entry-flow";
|
||||||
|
|
||||||
|
export const loadSubConfigFlowDialog = loadDataEntryFlowDialog;
|
||||||
|
|
||||||
|
export const showSubConfigFlowDialog = (
|
||||||
|
element: HTMLElement,
|
||||||
|
configEntry: ConfigEntry,
|
||||||
|
flowType: string,
|
||||||
|
dialogParams: Omit<DataEntryFlowDialogParams, "flowConfig"> & {
|
||||||
|
subEntryId?: string;
|
||||||
|
}
|
||||||
|
): void =>
|
||||||
|
showFlowDialog(element, dialogParams, {
|
||||||
|
flowType: "config_subentries_flow",
|
||||||
|
showDevices: true,
|
||||||
|
createFlow: async (hass, handler) => {
|
||||||
|
const [step] = await Promise.all([
|
||||||
|
createSubConfigFlow(hass, handler, flowType, dialogParams.subEntryId),
|
||||||
|
hass.loadFragmentTranslation("config"),
|
||||||
|
hass.loadBackendTranslation("config_subentries", configEntry.domain),
|
||||||
|
hass.loadBackendTranslation("selector", configEntry.domain),
|
||||||
|
// Used as fallback if no header defined for step
|
||||||
|
hass.loadBackendTranslation("title", configEntry.domain),
|
||||||
|
]);
|
||||||
|
return step;
|
||||||
|
},
|
||||||
|
fetchFlow: async (hass, flowId) => {
|
||||||
|
const step = await fetchSubConfigFlow(hass, flowId);
|
||||||
|
await hass.loadFragmentTranslation("config");
|
||||||
|
await hass.loadBackendTranslation(
|
||||||
|
"config_subentries",
|
||||||
|
configEntry.domain
|
||||||
|
);
|
||||||
|
await hass.loadBackendTranslation("selector", configEntry.domain);
|
||||||
|
return step;
|
||||||
|
},
|
||||||
|
handleFlowStep: handleSubConfigFlowStep,
|
||||||
|
deleteFlow: deleteSubConfigFlow,
|
||||||
|
|
||||||
|
renderAbortDescription(hass, step) {
|
||||||
|
const description = hass.localize(
|
||||||
|
`component.${step.translation_domain || configEntry.domain}.config_subentries.${flowType}.abort.${step.reason}`,
|
||||||
|
step.description_placeholders
|
||||||
|
);
|
||||||
|
|
||||||
|
return description
|
||||||
|
? html`
|
||||||
|
<ha-markdown allowsvg breaks .content=${description}></ha-markdown>
|
||||||
|
`
|
||||||
|
: step.reason;
|
||||||
|
},
|
||||||
|
|
||||||
|
renderShowFormStepHeader(hass, step) {
|
||||||
|
return (
|
||||||
|
hass.localize(
|
||||||
|
`component.${step.translation_domain || configEntry.domain}.config_subentries.${flowType}.step.${step.step_id}.title`,
|
||||||
|
step.description_placeholders
|
||||||
|
) || hass.localize(`component.${configEntry.domain}.title`)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
renderShowFormStepDescription(hass, step) {
|
||||||
|
const description = hass.localize(
|
||||||
|
`component.${step.translation_domain || configEntry.domain}.config_subentries.${flowType}.step.${step.step_id}.description`,
|
||||||
|
step.description_placeholders
|
||||||
|
);
|
||||||
|
return description
|
||||||
|
? html`
|
||||||
|
<ha-markdown allowsvg breaks .content=${description}></ha-markdown>
|
||||||
|
`
|
||||||
|
: "";
|
||||||
|
},
|
||||||
|
|
||||||
|
renderShowFormStepFieldLabel(hass, step, field, options) {
|
||||||
|
if (field.type === "expandable") {
|
||||||
|
return hass.localize(
|
||||||
|
`component.${configEntry.domain}.config_subentries.${flowType}.step.${step.step_id}.sections.${field.name}.name`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const prefix = options?.path?.[0] ? `sections.${options.path[0]}.` : "";
|
||||||
|
|
||||||
|
return (
|
||||||
|
hass.localize(
|
||||||
|
`component.${configEntry.domain}.config_subentries.${flowType}.step.${step.step_id}.${prefix}data.${field.name}`
|
||||||
|
) || field.name
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
renderShowFormStepFieldHelper(hass, step, field, options) {
|
||||||
|
if (field.type === "expandable") {
|
||||||
|
return hass.localize(
|
||||||
|
`component.${step.translation_domain || configEntry.domain}.config_subentries.${flowType}.step.${step.step_id}.sections.${field.name}.description`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const prefix = options?.path?.[0] ? `sections.${options.path[0]}.` : "";
|
||||||
|
|
||||||
|
const description = hass.localize(
|
||||||
|
`component.${step.translation_domain || configEntry.domain}.config_subentries.${flowType}.step.${step.step_id}.${prefix}data_description.${field.name}`,
|
||||||
|
step.description_placeholders
|
||||||
|
);
|
||||||
|
|
||||||
|
return description
|
||||||
|
? html`<ha-markdown breaks .content=${description}></ha-markdown>`
|
||||||
|
: "";
|
||||||
|
},
|
||||||
|
|
||||||
|
renderShowFormStepFieldError(hass, step, error) {
|
||||||
|
return (
|
||||||
|
hass.localize(
|
||||||
|
`component.${step.translation_domain || step.translation_domain || configEntry.domain}.config_subentries.${flowType}.error.${error}`,
|
||||||
|
step.description_placeholders
|
||||||
|
) || error
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
renderShowFormStepFieldLocalizeValue(hass, _step, key) {
|
||||||
|
return hass.localize(`component.${configEntry.domain}.selector.${key}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
renderShowFormStepSubmitButton(hass, step) {
|
||||||
|
return (
|
||||||
|
hass.localize(
|
||||||
|
`component.${configEntry.domain}.config_subentries.${flowType}.step.${step.step_id}.submit`
|
||||||
|
) ||
|
||||||
|
hass.localize(
|
||||||
|
`ui.panel.config.integrations.config_flow.${
|
||||||
|
step.last_step === false ? "next" : "submit"
|
||||||
|
}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
renderExternalStepHeader(hass, step) {
|
||||||
|
return (
|
||||||
|
hass.localize(
|
||||||
|
`component.${configEntry.domain}.config_subentries.${flowType}.step.${step.step_id}.title`
|
||||||
|
) ||
|
||||||
|
hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_flow.external_step.open_site"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
renderExternalStepDescription(hass, step) {
|
||||||
|
const description = hass.localize(
|
||||||
|
`component.${step.translation_domain || configEntry.domain}.config_subentries.${flowType}.step.${step.step_id}.description`,
|
||||||
|
step.description_placeholders
|
||||||
|
);
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<p>
|
||||||
|
${hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_flow.external_step.description"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
${description
|
||||||
|
? html`
|
||||||
|
<ha-markdown
|
||||||
|
allowsvg
|
||||||
|
breaks
|
||||||
|
.content=${description}
|
||||||
|
></ha-markdown>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
|
||||||
|
renderCreateEntryDescription(hass, step) {
|
||||||
|
const description = hass.localize(
|
||||||
|
`component.${step.translation_domain || configEntry.domain}.config_subentries.${flowType}.create_entry.${
|
||||||
|
step.description || "default"
|
||||||
|
}`,
|
||||||
|
step.description_placeholders
|
||||||
|
);
|
||||||
|
|
||||||
|
return html`
|
||||||
|
${description
|
||||||
|
? html`
|
||||||
|
<ha-markdown
|
||||||
|
allowsvg
|
||||||
|
breaks
|
||||||
|
.content=${description}
|
||||||
|
></ha-markdown>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
<p>
|
||||||
|
${hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_flow.created_config",
|
||||||
|
{ name: step.title }
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
|
||||||
|
renderShowFormProgressHeader(hass, step) {
|
||||||
|
return (
|
||||||
|
hass.localize(
|
||||||
|
`component.${configEntry.domain}.config_subentries.${flowType}.step.${step.step_id}.title`
|
||||||
|
) || hass.localize(`component.${configEntry.domain}.title`)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
renderShowFormProgressDescription(hass, step) {
|
||||||
|
const description = hass.localize(
|
||||||
|
`component.${step.translation_domain || configEntry.domain}.config_subentries.${flowType}.progress.${step.progress_action}`,
|
||||||
|
step.description_placeholders
|
||||||
|
);
|
||||||
|
return description
|
||||||
|
? html`
|
||||||
|
<ha-markdown allowsvg breaks .content=${description}></ha-markdown>
|
||||||
|
`
|
||||||
|
: "";
|
||||||
|
},
|
||||||
|
|
||||||
|
renderMenuHeader(hass, step) {
|
||||||
|
return (
|
||||||
|
hass.localize(
|
||||||
|
`component.${configEntry.domain}.config_subentries.${flowType}.step.${step.step_id}.title`,
|
||||||
|
step.description_placeholders
|
||||||
|
) || hass.localize(`component.${configEntry.domain}.title`)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
renderMenuDescription(hass, step) {
|
||||||
|
const description = hass.localize(
|
||||||
|
`component.${step.translation_domain || configEntry.domain}.config_subentries.${flowType}.step.${step.step_id}.description`,
|
||||||
|
step.description_placeholders
|
||||||
|
);
|
||||||
|
return description
|
||||||
|
? html`
|
||||||
|
<ha-markdown allowsvg breaks .content=${description}></ha-markdown>
|
||||||
|
`
|
||||||
|
: "";
|
||||||
|
},
|
||||||
|
|
||||||
|
renderMenuOption(hass, step, option) {
|
||||||
|
return hass.localize(
|
||||||
|
`component.${step.translation_domain || configEntry.domain}.config_subentries.${flowType}.step.${step.step_id}.menu_options.${option}`,
|
||||||
|
step.description_placeholders
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
renderLoadingDescription(hass, reason, handler, step) {
|
||||||
|
if (reason !== "loading_flow" && reason !== "loading_step") {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
const domain = step?.handler || handler;
|
||||||
|
return hass.localize(
|
||||||
|
`ui.panel.config.integrations.config_flow.loading.${reason}`,
|
||||||
|
{
|
||||||
|
integration: domain
|
||||||
|
? domainToName(hass.localize, domain)
|
||||||
|
: // when we are continuing a config flow, we only know the ID and not the domain
|
||||||
|
hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_flow.loading.fallback_title"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
|
@ -51,8 +51,8 @@ import "../../../components/ha-icon-button";
|
||||||
import "../../../components/ha-md-menu-item";
|
import "../../../components/ha-md-menu-item";
|
||||||
import "../../../components/ha-sub-menu";
|
import "../../../components/ha-sub-menu";
|
||||||
import { createAreaRegistryEntry } from "../../../data/area_registry";
|
import { createAreaRegistryEntry } from "../../../data/area_registry";
|
||||||
import type { ConfigEntry } from "../../../data/config_entries";
|
import type { ConfigEntry, SubEntry } from "../../../data/config_entries";
|
||||||
import { sortConfigEntries } from "../../../data/config_entries";
|
import { getSubEntries, sortConfigEntries } from "../../../data/config_entries";
|
||||||
import { fullEntitiesContext } from "../../../data/context";
|
import { fullEntitiesContext } from "../../../data/context";
|
||||||
import type { DataTableFilters } from "../../../data/data_table_filters";
|
import type { DataTableFilters } from "../../../data/data_table_filters";
|
||||||
import {
|
import {
|
||||||
|
@ -108,6 +108,8 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
||||||
|
|
||||||
@property({ attribute: false }) public entries!: ConfigEntry[];
|
@property({ attribute: false }) public entries!: ConfigEntry[];
|
||||||
|
|
||||||
|
@state() private _subEntries?: SubEntry[];
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
@consume({ context: fullEntitiesContext, subscribe: true })
|
@consume({ context: fullEntitiesContext, subscribe: true })
|
||||||
entities!: EntityRegistryEntry[];
|
entities!: EntityRegistryEntry[];
|
||||||
|
@ -219,6 +221,7 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
||||||
private _setFiltersFromUrl() {
|
private _setFiltersFromUrl() {
|
||||||
const domain = this._searchParms.get("domain");
|
const domain = this._searchParms.get("domain");
|
||||||
const configEntry = this._searchParms.get("config_entry");
|
const configEntry = this._searchParms.get("config_entry");
|
||||||
|
const subEntry = this._searchParms.get("sub_entry");
|
||||||
const label = this._searchParms.has("label");
|
const label = this._searchParms.has("label");
|
||||||
|
|
||||||
if (!domain && !configEntry && !label) {
|
if (!domain && !configEntry && !label) {
|
||||||
|
@ -243,6 +246,10 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
||||||
value: configEntry ? [configEntry] : [],
|
value: configEntry ? [configEntry] : [],
|
||||||
items: undefined,
|
items: undefined,
|
||||||
},
|
},
|
||||||
|
sub_entry: {
|
||||||
|
value: subEntry ? [subEntry] : [],
|
||||||
|
items: undefined,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
this._filterLabel();
|
this._filterLabel();
|
||||||
}
|
}
|
||||||
|
@ -334,6 +341,32 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
||||||
if (configEntries.length === 1) {
|
if (configEntries.length === 1) {
|
||||||
filteredConfigEntry = configEntries[0];
|
filteredConfigEntry = configEntries[0];
|
||||||
}
|
}
|
||||||
|
} else if (
|
||||||
|
key === "sub_entry" &&
|
||||||
|
Array.isArray(filter.value) &&
|
||||||
|
filter.value.length
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
Array.isArray(this._filters.config_entry?.value) &&
|
||||||
|
this._filters.config_entry.value.length === 1
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const configEntryId = this._filters.config_entry.value[0];
|
||||||
|
outputDevices = outputDevices.filter(
|
||||||
|
(device) =>
|
||||||
|
device.config_entries_subentries[configEntryId] &&
|
||||||
|
(filter.value as string[]).some((subEntryId) =>
|
||||||
|
device.config_entries_subentries[configEntryId].includes(
|
||||||
|
subEntryId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (!this._subEntries) {
|
||||||
|
this._loadSubEntries(configEntryId);
|
||||||
|
}
|
||||||
} else if (
|
} else if (
|
||||||
key === "ha-filter-integrations" &&
|
key === "ha-filter-integrations" &&
|
||||||
Array.isArray(filter.value) &&
|
Array.isArray(filter.value) &&
|
||||||
|
@ -755,7 +788,15 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
||||||
${this.entries?.find(
|
${this.entries?.find(
|
||||||
(entry) =>
|
(entry) =>
|
||||||
entry.entry_id === this._filters.config_entry!.value![0]
|
entry.entry_id === this._filters.config_entry!.value![0]
|
||||||
)?.title || this._filters.config_entry.value[0]}
|
)?.title || this._filters.config_entry.value[0]}${this._filters
|
||||||
|
.config_entry.value.length === 1 &&
|
||||||
|
Array.isArray(this._filters.sub_entry?.value) &&
|
||||||
|
this._filters.sub_entry.value.length
|
||||||
|
? html` (${this._subEntries?.find(
|
||||||
|
(entry) =>
|
||||||
|
entry.subentry_id === this._filters.sub_entry!.value![0]
|
||||||
|
)?.title || this._filters.sub_entry!.value![0]})`
|
||||||
|
: nothing}
|
||||||
</ha-alert>`
|
</ha-alert>`
|
||||||
: nothing}
|
: nothing}
|
||||||
<ha-filter-floor-areas
|
<ha-filter-floor-areas
|
||||||
|
@ -888,6 +929,10 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _loadSubEntries(entryId: string) {
|
||||||
|
this._subEntries = await getSubEntries(this.hass, entryId);
|
||||||
|
}
|
||||||
|
|
||||||
private _filterExpanded(ev) {
|
private _filterExpanded(ev) {
|
||||||
if (ev.detail.expanded) {
|
if (ev.detail.expanded) {
|
||||||
this._expandedFilter = ev.target.localName;
|
this._expandedFilter = ev.target.localName;
|
||||||
|
|
|
@ -66,8 +66,8 @@ import "../../../components/ha-icon-button";
|
||||||
import "../../../components/ha-md-menu-item";
|
import "../../../components/ha-md-menu-item";
|
||||||
import "../../../components/ha-sub-menu";
|
import "../../../components/ha-sub-menu";
|
||||||
import "../../../components/ha-svg-icon";
|
import "../../../components/ha-svg-icon";
|
||||||
import type { ConfigEntry } from "../../../data/config_entries";
|
import type { ConfigEntry, SubEntry } from "../../../data/config_entries";
|
||||||
import { getConfigEntries } from "../../../data/config_entries";
|
import { getConfigEntries, getSubEntries } from "../../../data/config_entries";
|
||||||
import { fullEntitiesContext } from "../../../data/context";
|
import { fullEntitiesContext } from "../../../data/context";
|
||||||
import type {
|
import type {
|
||||||
DataTableFiltersItems,
|
DataTableFiltersItems,
|
||||||
|
@ -146,6 +146,8 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||||
|
|
||||||
@state() private _entries?: ConfigEntry[];
|
@state() private _entries?: ConfigEntry[];
|
||||||
|
|
||||||
|
@state() private _subEntries?: SubEntry[];
|
||||||
|
|
||||||
@state() private _manifests?: IntegrationManifest[];
|
@state() private _manifests?: IntegrationManifest[];
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
|
@ -522,6 +524,27 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||||
if (configEntries.length === 1) {
|
if (configEntries.length === 1) {
|
||||||
filteredConfigEntry = configEntries[0];
|
filteredConfigEntry = configEntries[0];
|
||||||
}
|
}
|
||||||
|
} else if (
|
||||||
|
key === "sub_entry" &&
|
||||||
|
Array.isArray(filter) &&
|
||||||
|
filter.length
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
Array.isArray(this._filters.config_entry) &&
|
||||||
|
this._filters.config_entry.length === 1
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
filteredEntities = filteredEntities.filter(
|
||||||
|
(entity) =>
|
||||||
|
entity.config_subentry_id &&
|
||||||
|
(filter as string[]).includes(entity.config_subentry_id)
|
||||||
|
);
|
||||||
|
if (!this._subEntries) {
|
||||||
|
this._loadSubEntries(this._filters.config_entry[0]);
|
||||||
|
}
|
||||||
} else if (
|
} else if (
|
||||||
key === "ha-filter-integrations" &&
|
key === "ha-filter-integrations" &&
|
||||||
Array.isArray(filter) &&
|
Array.isArray(filter) &&
|
||||||
|
@ -904,14 +927,22 @@ ${
|
||||||
</ha-md-button-menu>
|
</ha-md-button-menu>
|
||||||
${
|
${
|
||||||
Array.isArray(this._filters.config_entry) &&
|
Array.isArray(this._filters.config_entry) &&
|
||||||
this._filters.config_entry?.length
|
this._filters.config_entry.length
|
||||||
? html`<ha-alert slot="filter-pane">
|
? html`<ha-alert slot="filter-pane">
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.entities.picker.filtering_by_config_entry"
|
"ui.panel.config.entities.picker.filtering_by_config_entry"
|
||||||
)}
|
)}
|
||||||
${this._entries?.find(
|
${this._entries?.find(
|
||||||
(entry) => entry.entry_id === this._filters.config_entry![0]
|
(entry) => entry.entry_id === this._filters.config_entry![0]
|
||||||
)?.title || this._filters.config_entry[0]}
|
)?.title || this._filters.config_entry[0]}${this._filters
|
||||||
|
.config_entry.length === 1 &&
|
||||||
|
Array.isArray(this._filters.sub_entry) &&
|
||||||
|
this._filters.sub_entry.length
|
||||||
|
? html` (${this._subEntries?.find(
|
||||||
|
(entry) =>
|
||||||
|
entry.subentry_id === this._filters.sub_entry![0]
|
||||||
|
)?.title || this._filters.sub_entry[0]})`
|
||||||
|
: nothing}
|
||||||
</ha-alert>`
|
</ha-alert>`
|
||||||
: nothing
|
: nothing
|
||||||
}
|
}
|
||||||
|
@ -1024,6 +1055,7 @@ ${
|
||||||
private _setFiltersFromUrl() {
|
private _setFiltersFromUrl() {
|
||||||
const domain = this._searchParms.get("domain");
|
const domain = this._searchParms.get("domain");
|
||||||
const configEntry = this._searchParms.get("config_entry");
|
const configEntry = this._searchParms.get("config_entry");
|
||||||
|
const subEntry = this._searchParms.get("sub_entry");
|
||||||
const label = this._searchParms.has("label");
|
const label = this._searchParms.has("label");
|
||||||
|
|
||||||
if (!domain && !configEntry && !label) {
|
if (!domain && !configEntry && !label) {
|
||||||
|
@ -1036,6 +1068,7 @@ ${
|
||||||
"ha-filter-states": [],
|
"ha-filter-states": [],
|
||||||
"ha-filter-integrations": domain ? [domain] : [],
|
"ha-filter-integrations": domain ? [domain] : [],
|
||||||
config_entry: configEntry ? [configEntry] : [],
|
config_entry: configEntry ? [configEntry] : [],
|
||||||
|
sub_entry: subEntry ? [subEntry] : [],
|
||||||
};
|
};
|
||||||
this._filterLabel();
|
this._filterLabel();
|
||||||
}
|
}
|
||||||
|
@ -1093,6 +1126,7 @@ ${
|
||||||
hidden_by: null,
|
hidden_by: null,
|
||||||
area_id: null,
|
area_id: null,
|
||||||
config_entry_id: null,
|
config_entry_id: null,
|
||||||
|
config_subentry_id: null,
|
||||||
device_id: null,
|
device_id: null,
|
||||||
icon: null,
|
icon: null,
|
||||||
readonly: true,
|
readonly: true,
|
||||||
|
@ -1384,6 +1418,10 @@ ${rejected
|
||||||
this._entries = await getConfigEntries(this.hass);
|
this._entries = await getConfigEntries(this.hass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _loadSubEntries(entryId: string) {
|
||||||
|
this._subEntries = await getSubEntries(this.hass, entryId);
|
||||||
|
}
|
||||||
|
|
||||||
private _addDevice() {
|
private _addDevice() {
|
||||||
const { filteredConfigEntry, filteredDomains } =
|
const { filteredConfigEntry, filteredDomains } =
|
||||||
this._filteredEntitiesAndDomains(
|
this._filteredEntitiesAndDomains(
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
mdiOpenInNew,
|
mdiOpenInNew,
|
||||||
mdiPackageVariant,
|
mdiPackageVariant,
|
||||||
mdiPlayCircleOutline,
|
mdiPlayCircleOutline,
|
||||||
|
mdiPlus,
|
||||||
mdiProgressHelper,
|
mdiProgressHelper,
|
||||||
mdiReload,
|
mdiReload,
|
||||||
mdiReloadAlert,
|
mdiReloadAlert,
|
||||||
|
@ -52,14 +53,17 @@ import { getSignedPath } from "../../../data/auth";
|
||||||
import type {
|
import type {
|
||||||
ConfigEntry,
|
ConfigEntry,
|
||||||
DisableConfigEntryResult,
|
DisableConfigEntryResult,
|
||||||
|
SubEntry,
|
||||||
} from "../../../data/config_entries";
|
} from "../../../data/config_entries";
|
||||||
import {
|
import {
|
||||||
ERROR_STATES,
|
ERROR_STATES,
|
||||||
RECOVERABLE_STATES,
|
RECOVERABLE_STATES,
|
||||||
deleteConfigEntry,
|
deleteConfigEntry,
|
||||||
|
deleteSubEntry,
|
||||||
disableConfigEntry,
|
disableConfigEntry,
|
||||||
enableConfigEntry,
|
enableConfigEntry,
|
||||||
getConfigEntries,
|
getConfigEntries,
|
||||||
|
getSubEntries,
|
||||||
reloadConfigEntry,
|
reloadConfigEntry,
|
||||||
updateConfigEntry,
|
updateConfigEntry,
|
||||||
} from "../../../data/config_entries";
|
} from "../../../data/config_entries";
|
||||||
|
@ -106,6 +110,7 @@ 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";
|
||||||
import { QUALITY_SCALE_MAP } from "../../../data/integration_quality_scale";
|
import { QUALITY_SCALE_MAP } from "../../../data/integration_quality_scale";
|
||||||
|
import { showSubConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-sub-config-flow";
|
||||||
|
|
||||||
export const renderConfigEntryError = (
|
export const renderConfigEntryError = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
@ -172,6 +177,8 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||||
|
|
||||||
@state() private _domainEntities: Record<string, string[]> = {};
|
@state() private _domainEntities: Record<string, string[]> = {};
|
||||||
|
|
||||||
|
@state() private _subEntries: Record<string, SubEntry[]> = {};
|
||||||
|
|
||||||
private _configPanel = memoizeOne(
|
private _configPanel = memoizeOne(
|
||||||
(domain: string, panels: HomeAssistant["panels"]): string | undefined =>
|
(domain: string, panels: HomeAssistant["panels"]): string | undefined =>
|
||||||
Object.values(panels).find(
|
Object.values(panels).find(
|
||||||
|
@ -214,11 +221,18 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||||
protected willUpdate(changedProperties: PropertyValues): void {
|
protected willUpdate(changedProperties: PropertyValues): void {
|
||||||
if (changedProperties.has("domain")) {
|
if (changedProperties.has("domain")) {
|
||||||
this.hass.loadBackendTranslation("title", [this.domain]);
|
this.hass.loadBackendTranslation("title", [this.domain]);
|
||||||
|
this.hass.loadBackendTranslation("config_subentries", [this.domain]);
|
||||||
this._extraConfigEntries = undefined;
|
this._extraConfigEntries = undefined;
|
||||||
this._fetchManifest();
|
this._fetchManifest();
|
||||||
this._fetchDiagnostics();
|
this._fetchDiagnostics();
|
||||||
this._fetchEntitySources();
|
this._fetchEntitySources();
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
changedProperties.has("configEntries") ||
|
||||||
|
changedProperties.has("_extraConfigEntries")
|
||||||
|
) {
|
||||||
|
this._fetchSubEntries();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _fetchEntitySources() {
|
private async _fetchEntitySources() {
|
||||||
|
@ -573,7 +587,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||||
const attention = ATTENTION_SOURCES.includes(
|
const attention = ATTENTION_SOURCES.includes(
|
||||||
flow.context.source
|
flow.context.source
|
||||||
);
|
);
|
||||||
return html` <ha-md-list-item
|
return html`<ha-md-list-item
|
||||||
class="config_entry ${attention ? "attention" : ""}"
|
class="config_entry ${attention ? "attention" : ""}"
|
||||||
>
|
>
|
||||||
${flow.localized_title}
|
${flow.localized_title}
|
||||||
|
@ -673,6 +687,73 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||||
ev.target.style.display = "none";
|
ev.target.style.display = "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _renderDeviceLine(
|
||||||
|
item: ConfigEntry,
|
||||||
|
devices: DeviceRegistryEntry[],
|
||||||
|
services: DeviceRegistryEntry[],
|
||||||
|
entities: EntityRegistryEntry[],
|
||||||
|
subItem?: SubEntry
|
||||||
|
) {
|
||||||
|
let devicesLine: (TemplateResult | string)[] = [];
|
||||||
|
for (const [items, localizeKey] of [
|
||||||
|
[devices, "devices"],
|
||||||
|
[services, "services"],
|
||||||
|
] as const) {
|
||||||
|
if (items.length === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const url =
|
||||||
|
items.length === 1
|
||||||
|
? `/config/devices/device/${items[0].id}`
|
||||||
|
: `/config/devices/dashboard?historyBack=1&config_entry=${item.entry_id}${subItem ? `&sub_entry=${subItem.subentry_id}` : ""}`;
|
||||||
|
devicesLine.push(
|
||||||
|
// no white space before/after template on purpose
|
||||||
|
html`<a href=${url}
|
||||||
|
>${this.hass.localize(
|
||||||
|
`ui.panel.config.integrations.config_entry.${localizeKey}`,
|
||||||
|
{ count: items.length }
|
||||||
|
)}</a
|
||||||
|
>`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entities.length) {
|
||||||
|
devicesLine.push(
|
||||||
|
// no white space before/after template on purpose
|
||||||
|
html`<a
|
||||||
|
href=${`/config/entities?historyBack=1&config_entry=${item.entry_id}${subItem ? `&sub_entry=${subItem.subentry_id}` : ""}`}
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.entities",
|
||||||
|
{ count: entities.length }
|
||||||
|
)}</a
|
||||||
|
>`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (devicesLine.length === 0) {
|
||||||
|
devicesLine = [
|
||||||
|
this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.no_devices_or_entities"
|
||||||
|
),
|
||||||
|
];
|
||||||
|
} else if (devicesLine.length === 2) {
|
||||||
|
devicesLine = [
|
||||||
|
devicesLine[0],
|
||||||
|
` ${this.hass.localize("ui.common.and")} `,
|
||||||
|
devicesLine[1],
|
||||||
|
];
|
||||||
|
} else if (devicesLine.length === 3) {
|
||||||
|
devicesLine = [
|
||||||
|
devicesLine[0],
|
||||||
|
", ",
|
||||||
|
devicesLine[1],
|
||||||
|
` ${this.hass.localize("ui.common.and")} `,
|
||||||
|
devicesLine[2],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return devicesLine;
|
||||||
|
}
|
||||||
|
|
||||||
private _renderConfigEntry(item: ConfigEntry) {
|
private _renderConfigEntry(item: ConfigEntry) {
|
||||||
let stateText: Parameters<typeof this.hass.localize> | undefined;
|
let stateText: Parameters<typeof this.hass.localize> | undefined;
|
||||||
let stateTextExtra: TemplateResult | string | undefined;
|
let stateTextExtra: TemplateResult | string | undefined;
|
||||||
|
@ -720,274 +801,299 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||||
)}.`);
|
)}.`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (const [items, localizeKey] of [
|
devicesLine = this._renderDeviceLine(item, devices, services, entities);
|
||||||
[devices, "devices"],
|
|
||||||
[services, "services"],
|
|
||||||
] as const) {
|
|
||||||
if (items.length === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const url =
|
|
||||||
items.length === 1
|
|
||||||
? `/config/devices/device/${items[0].id}`
|
|
||||||
: `/config/devices/dashboard?historyBack=1&config_entry=${item.entry_id}`;
|
|
||||||
devicesLine.push(
|
|
||||||
// no white space before/after template on purpose
|
|
||||||
html`<a href=${url}
|
|
||||||
>${this.hass.localize(
|
|
||||||
`ui.panel.config.integrations.config_entry.${localizeKey}`,
|
|
||||||
{ count: items.length }
|
|
||||||
)}</a
|
|
||||||
>`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entities.length) {
|
|
||||||
devicesLine.push(
|
|
||||||
// no white space before/after template on purpose
|
|
||||||
html`<a
|
|
||||||
href=${`/config/entities?historyBack=1&config_entry=${item.entry_id}`}
|
|
||||||
>${this.hass.localize(
|
|
||||||
"ui.panel.config.integrations.config_entry.entities",
|
|
||||||
{ count: entities.length }
|
|
||||||
)}</a
|
|
||||||
>`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (devicesLine.length === 0) {
|
|
||||||
devicesLine = [
|
|
||||||
this.hass.localize(
|
|
||||||
"ui.panel.config.integrations.config_entry.no_devices_or_entities"
|
|
||||||
),
|
|
||||||
];
|
|
||||||
} else if (devicesLine.length === 2) {
|
|
||||||
devicesLine = [
|
|
||||||
devicesLine[0],
|
|
||||||
` ${this.hass.localize("ui.common.and")} `,
|
|
||||||
devicesLine[1],
|
|
||||||
];
|
|
||||||
} else if (devicesLine.length === 3) {
|
|
||||||
devicesLine = [
|
|
||||||
devicesLine[0],
|
|
||||||
", ",
|
|
||||||
devicesLine[1],
|
|
||||||
` ${this.hass.localize("ui.common.and")} `,
|
|
||||||
devicesLine[2],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const configPanel = this._configPanel(item.domain, this.hass.panels);
|
const configPanel = this._configPanel(item.domain, this.hass.panels);
|
||||||
|
|
||||||
|
const subEntries = this._subEntries[item.entry_id] || [];
|
||||||
|
|
||||||
return html`<ha-md-list-item
|
return html`<ha-md-list-item
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
config_entry: true,
|
config_entry: true,
|
||||||
"state-not-loaded": item!.state === "not_loaded",
|
"state-not-loaded": item!.state === "not_loaded",
|
||||||
"state-failed-unload": item!.state === "failed_unload",
|
"state-failed-unload": item!.state === "failed_unload",
|
||||||
"state-setup": item!.state === "setup_in_progress",
|
"state-setup": item!.state === "setup_in_progress",
|
||||||
"state-error": ERROR_STATES.includes(item!.state),
|
"state-error": ERROR_STATES.includes(item!.state),
|
||||||
"state-disabled": item.disabled_by !== null,
|
"state-disabled": item.disabled_by !== null,
|
||||||
})}
|
})}
|
||||||
data-entry-id=${item.entry_id}
|
data-entry-id=${item.entry_id}
|
||||||
.configEntry=${item}
|
.configEntry=${item}
|
||||||
>
|
>
|
||||||
<div slot="headline">
|
<div slot="headline">
|
||||||
${item.title || domainToName(this.hass.localize, item.domain)}
|
${item.title || domainToName(this.hass.localize, item.domain)}
|
||||||
</div>
|
</div>
|
||||||
<div slot="supporting-text">
|
<div slot="supporting-text">
|
||||||
<div>${devicesLine}</div>
|
<div>${devicesLine}</div>
|
||||||
${stateText
|
${stateText
|
||||||
? html`
|
|
||||||
<div class="message">
|
|
||||||
<ha-svg-icon .path=${icon}></ha-svg-icon>
|
|
||||||
<div>
|
|
||||||
${this.hass.localize(...stateText)}${stateTextExtra
|
|
||||||
? html`: ${stateTextExtra}`
|
|
||||||
: nothing}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
</div>
|
|
||||||
${item.disabled_by === "user"
|
|
||||||
? html`<ha-button unelevated slot="end" @click=${this._handleEnable}>
|
|
||||||
${this.hass.localize("ui.common.enable")}
|
|
||||||
</ha-button>`
|
|
||||||
: configPanel &&
|
|
||||||
(item.domain !== "matter" ||
|
|
||||||
isDevVersion(this.hass.config.version)) &&
|
|
||||||
!stateText
|
|
||||||
? html`<a
|
|
||||||
slot="end"
|
|
||||||
href=${`/${configPanel}?config_entry=${item.entry_id}`}
|
|
||||||
><ha-button>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.integrations.config_entry.configure"
|
|
||||||
)}
|
|
||||||
</ha-button></a
|
|
||||||
>`
|
|
||||||
: item.supports_options
|
|
||||||
? html`
|
? html`
|
||||||
<ha-button slot="end" @click=${this._showOptions}>
|
<div class="message">
|
||||||
|
<ha-svg-icon .path=${icon}></ha-svg-icon>
|
||||||
|
<div>
|
||||||
|
${this.hass.localize(...stateText)}${stateTextExtra
|
||||||
|
? html`: ${stateTextExtra}`
|
||||||
|
: nothing}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
</div>
|
||||||
|
${item.disabled_by === "user"
|
||||||
|
? html`<ha-button unelevated slot="end" @click=${this._handleEnable}>
|
||||||
|
${this.hass.localize("ui.common.enable")}
|
||||||
|
</ha-button>`
|
||||||
|
: configPanel &&
|
||||||
|
(item.domain !== "matter" ||
|
||||||
|
isDevVersion(this.hass.config.version)) &&
|
||||||
|
!stateText
|
||||||
|
? html`<a
|
||||||
|
slot="end"
|
||||||
|
href=${`/${configPanel}?config_entry=${item.entry_id}`}
|
||||||
|
><ha-button>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.integrations.config_entry.configure"
|
"ui.panel.config.integrations.config_entry.configure"
|
||||||
)}
|
)}
|
||||||
</ha-button>
|
</ha-button></a
|
||||||
|
>`
|
||||||
|
: item.supports_options
|
||||||
|
? html`
|
||||||
|
<ha-button slot="end" @click=${this._showOptions}>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.configure"
|
||||||
|
)}
|
||||||
|
</ha-button>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
<ha-md-button-menu positioning="popover" slot="end">
|
||||||
|
<ha-icon-button
|
||||||
|
slot="trigger"
|
||||||
|
.label=${this.hass.localize("ui.common.menu")}
|
||||||
|
.path=${mdiDotsVertical}
|
||||||
|
></ha-icon-button>
|
||||||
|
${item.disabled_by && devices.length
|
||||||
|
? html`
|
||||||
|
<ha-md-menu-item
|
||||||
|
href=${devices.length === 1
|
||||||
|
? `/config/devices/device/${devices[0].id}`
|
||||||
|
: `/config/devices/dashboard?historyBack=1&config_entry=${item.entry_id}`}
|
||||||
|
>
|
||||||
|
<ha-svg-icon .path=${mdiDevices} slot="start"></ha-svg-icon>
|
||||||
|
${this.hass.localize(
|
||||||
|
`ui.panel.config.integrations.config_entry.devices`,
|
||||||
|
{ count: devices.length }
|
||||||
|
)}
|
||||||
|
<ha-icon-next slot="end"></ha-icon-next>
|
||||||
|
</ha-md-menu-item>
|
||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
|
${item.disabled_by && services.length
|
||||||
|
? html`<ha-md-menu-item
|
||||||
|
href=${services.length === 1
|
||||||
|
? `/config/devices/device/${services[0].id}`
|
||||||
|
: `/config/devices/dashboard?historyBack=1&config_entry=${item.entry_id}`}
|
||||||
|
>
|
||||||
|
<ha-svg-icon
|
||||||
|
.path=${mdiHandExtendedOutline}
|
||||||
|
slot="start"
|
||||||
|
></ha-svg-icon>
|
||||||
|
${this.hass.localize(
|
||||||
|
`ui.panel.config.integrations.config_entry.services`,
|
||||||
|
{ count: services.length }
|
||||||
|
)}
|
||||||
|
<ha-icon-next slot="end"></ha-icon-next>
|
||||||
|
</ha-md-menu-item> `
|
||||||
|
: nothing}
|
||||||
|
${item.disabled_by && entities.length
|
||||||
|
? html`
|
||||||
|
<ha-md-menu-item
|
||||||
|
href=${`/config/entities?historyBack=1&config_entry=${item.entry_id}`}
|
||||||
|
>
|
||||||
|
<ha-svg-icon
|
||||||
|
.path=${mdiShapeOutline}
|
||||||
|
slot="start"
|
||||||
|
></ha-svg-icon>
|
||||||
|
${this.hass.localize(
|
||||||
|
`ui.panel.config.integrations.config_entry.entities`,
|
||||||
|
{ count: entities.length }
|
||||||
|
)}
|
||||||
|
<ha-icon-next slot="end"></ha-icon-next>
|
||||||
|
</ha-md-menu-item>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
${!item.disabled_by &&
|
||||||
|
RECOVERABLE_STATES.includes(item.state) &&
|
||||||
|
item.supports_unload &&
|
||||||
|
item.source !== "system"
|
||||||
|
? html`
|
||||||
|
<ha-md-menu-item @click=${this._handleReload}>
|
||||||
|
<ha-svg-icon slot="start" .path=${mdiReload}></ha-svg-icon>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.reload"
|
||||||
|
)}
|
||||||
|
</ha-md-menu-item>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
|
||||||
|
<ha-md-menu-item @click=${this._handleRename} graphic="icon">
|
||||||
|
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.rename"
|
||||||
|
)}
|
||||||
|
</ha-md-menu-item>
|
||||||
|
|
||||||
|
${Object.keys(item.supported_subentry_types).map(
|
||||||
|
(flowType) =>
|
||||||
|
html`<ha-md-menu-item
|
||||||
|
@click=${this._addSubEntry}
|
||||||
|
.entry=${item}
|
||||||
|
.flowType=${flowType}
|
||||||
|
graphic="icon"
|
||||||
|
>
|
||||||
|
<ha-svg-icon slot="start" .path=${mdiPlus}></ha-svg-icon>
|
||||||
|
${this.hass.localize(
|
||||||
|
`component.${item.domain}.config_subentries.${flowType}.title`
|
||||||
|
)}</ha-md-menu-item
|
||||||
|
>`
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||||
|
|
||||||
|
${this._diagnosticHandler && item.state === "loaded"
|
||||||
|
? html`
|
||||||
|
<ha-md-menu-item
|
||||||
|
href=${getConfigEntryDiagnosticsDownloadUrl(item.entry_id)}
|
||||||
|
target="_blank"
|
||||||
|
@click=${this._signUrl}
|
||||||
|
>
|
||||||
|
<ha-svg-icon slot="start" .path=${mdiDownload}></ha-svg-icon>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.download_diagnostics"
|
||||||
|
)}
|
||||||
|
</ha-md-menu-item>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
${!item.disabled_by &&
|
||||||
|
item.supports_reconfigure &&
|
||||||
|
item.source !== "system"
|
||||||
|
? html`
|
||||||
|
<ha-md-menu-item @click=${this._handleReconfigure}>
|
||||||
|
<ha-svg-icon slot="start" .path=${mdiWrench}></ha-svg-icon>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.reconfigure"
|
||||||
|
)}
|
||||||
|
</ha-md-menu-item>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
|
||||||
|
<ha-md-menu-item @click=${this._handleSystemOptions} graphic="icon">
|
||||||
|
<ha-svg-icon slot="start" .path=${mdiCog}></ha-svg-icon>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.system_options"
|
||||||
|
)}
|
||||||
|
</ha-md-menu-item>
|
||||||
|
${item.disabled_by === "user"
|
||||||
|
? html`
|
||||||
|
<ha-md-menu-item @click=${this._handleEnable}>
|
||||||
|
<ha-svg-icon
|
||||||
|
slot="start"
|
||||||
|
.path=${mdiPlayCircleOutline}
|
||||||
|
></ha-svg-icon>
|
||||||
|
${this.hass.localize("ui.common.enable")}
|
||||||
|
</ha-md-menu-item>
|
||||||
|
`
|
||||||
|
: item.source !== "system"
|
||||||
|
? html`
|
||||||
|
<ha-md-menu-item
|
||||||
|
class="warning"
|
||||||
|
@click=${this._handleDisable}
|
||||||
|
graphic="icon"
|
||||||
|
>
|
||||||
|
<ha-svg-icon
|
||||||
|
slot="start"
|
||||||
|
class="warning"
|
||||||
|
.path=${mdiStopCircleOutline}
|
||||||
|
></ha-svg-icon>
|
||||||
|
${this.hass.localize("ui.common.disable")}
|
||||||
|
</ha-md-menu-item>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
${item.source !== "system"
|
||||||
|
? html`
|
||||||
|
<ha-md-menu-item class="warning" @click=${this._handleDelete}>
|
||||||
|
<ha-svg-icon
|
||||||
|
slot="start"
|
||||||
|
class="warning"
|
||||||
|
.path=${mdiDelete}
|
||||||
|
></ha-svg-icon>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.delete"
|
||||||
|
)}
|
||||||
|
</ha-md-menu-item>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
</ha-md-button-menu>
|
||||||
|
</ha-md-list-item>
|
||||||
|
${subEntries.map((subEntry) => this._renderSubEntry(item, subEntry))}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderSubEntry(configEntry: ConfigEntry, subEntry: SubEntry) {
|
||||||
|
const devices = this._getConfigEntryDevices(configEntry).filter((device) =>
|
||||||
|
device.config_entries_subentries[configEntry.entry_id]?.includes(
|
||||||
|
subEntry.subentry_id
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const services = this._getConfigEntryServices(configEntry).filter(
|
||||||
|
(device) =>
|
||||||
|
device.config_entries_subentries[configEntry.entry_id]?.includes(
|
||||||
|
subEntry.subentry_id
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const entities = this._getConfigEntryEntities(configEntry).filter(
|
||||||
|
(entity) => entity.config_subentry_id === subEntry.subentry_id
|
||||||
|
);
|
||||||
|
|
||||||
|
return html`<ha-md-list-item
|
||||||
|
class="sub-entry"
|
||||||
|
data-entry-id=${configEntry.entry_id}
|
||||||
|
.configEntry=${configEntry}
|
||||||
|
.subEntry=${subEntry}
|
||||||
|
>
|
||||||
|
<span slot="headline">${subEntry.title}</span>
|
||||||
|
<span slot="supporting-text"
|
||||||
|
>${this._renderDeviceLine(
|
||||||
|
configEntry,
|
||||||
|
devices,
|
||||||
|
services,
|
||||||
|
entities,
|
||||||
|
subEntry
|
||||||
|
)}</span
|
||||||
|
>
|
||||||
|
${configEntry.supported_subentry_types[subEntry.subentry_type]
|
||||||
|
?.supports_reconfigure
|
||||||
|
? html`
|
||||||
|
<ha-button slot="end" @click=${this._handleReconfigureSub}>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.configure"
|
||||||
|
)}
|
||||||
|
</ha-button>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
<ha-md-button-menu positioning="popover" slot="end">
|
<ha-md-button-menu positioning="popover" slot="end">
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
slot="trigger"
|
slot="trigger"
|
||||||
.label=${this.hass.localize("ui.common.menu")}
|
.label=${this.hass.localize("ui.common.menu")}
|
||||||
.path=${mdiDotsVertical}
|
.path=${mdiDotsVertical}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
${item.disabled_by && devices.length
|
<ha-md-menu-item class="warning" @click=${this._handleDeleteSub}>
|
||||||
? html`
|
<ha-svg-icon
|
||||||
<ha-md-menu-item
|
slot="start"
|
||||||
href=${devices.length === 1
|
class="warning"
|
||||||
? `/config/devices/device/${devices[0].id}`
|
.path=${mdiDelete}
|
||||||
: `/config/devices/dashboard?historyBack=1&config_entry=${item.entry_id}`}
|
></ha-svg-icon>
|
||||||
>
|
|
||||||
<ha-svg-icon .path=${mdiDevices} slot="start"></ha-svg-icon>
|
|
||||||
${this.hass.localize(
|
|
||||||
`ui.panel.config.integrations.config_entry.devices`,
|
|
||||||
{ count: devices.length }
|
|
||||||
)}
|
|
||||||
<ha-icon-next slot="end"></ha-icon-next>
|
|
||||||
</ha-md-menu-item>
|
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
${item.disabled_by && services.length
|
|
||||||
? html`<ha-md-menu-item
|
|
||||||
href=${services.length === 1
|
|
||||||
? `/config/devices/device/${services[0].id}`
|
|
||||||
: `/config/devices/dashboard?historyBack=1&config_entry=${item.entry_id}`}
|
|
||||||
>
|
|
||||||
<ha-svg-icon
|
|
||||||
.path=${mdiHandExtendedOutline}
|
|
||||||
slot="start"
|
|
||||||
></ha-svg-icon>
|
|
||||||
${this.hass.localize(
|
|
||||||
`ui.panel.config.integrations.config_entry.services`,
|
|
||||||
{ count: services.length }
|
|
||||||
)}
|
|
||||||
<ha-icon-next slot="end"></ha-icon-next>
|
|
||||||
</ha-md-menu-item> `
|
|
||||||
: nothing}
|
|
||||||
${item.disabled_by && entities.length
|
|
||||||
? html`
|
|
||||||
<ha-md-menu-item
|
|
||||||
href=${`/config/entities?historyBack=1&config_entry=${item.entry_id}`}
|
|
||||||
>
|
|
||||||
<ha-svg-icon
|
|
||||||
.path=${mdiShapeOutline}
|
|
||||||
slot="start"
|
|
||||||
></ha-svg-icon>
|
|
||||||
${this.hass.localize(
|
|
||||||
`ui.panel.config.integrations.config_entry.entities`,
|
|
||||||
{ count: entities.length }
|
|
||||||
)}
|
|
||||||
<ha-icon-next slot="end"></ha-icon-next>
|
|
||||||
</ha-md-menu-item>
|
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
${!item.disabled_by &&
|
|
||||||
RECOVERABLE_STATES.includes(item.state) &&
|
|
||||||
item.supports_unload &&
|
|
||||||
item.source !== "system"
|
|
||||||
? html`
|
|
||||||
<ha-md-menu-item @click=${this._handleReload}>
|
|
||||||
<ha-svg-icon slot="start" .path=${mdiReload}></ha-svg-icon>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.integrations.config_entry.reload"
|
|
||||||
)}
|
|
||||||
</ha-md-menu-item>
|
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
|
|
||||||
<ha-md-menu-item @click=${this._handleRename} graphic="icon">
|
|
||||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.integrations.config_entry.rename"
|
"ui.panel.config.integrations.config_entry.delete"
|
||||||
)}
|
)}
|
||||||
</ha-md-menu-item>
|
</ha-md-menu-item>
|
||||||
|
|
||||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
|
||||||
|
|
||||||
${this._diagnosticHandler && item.state === "loaded"
|
|
||||||
? html`
|
|
||||||
<ha-md-menu-item
|
|
||||||
href=${getConfigEntryDiagnosticsDownloadUrl(item.entry_id)}
|
|
||||||
target="_blank"
|
|
||||||
@click=${this._signUrl}
|
|
||||||
>
|
|
||||||
<ha-svg-icon slot="start" .path=${mdiDownload}></ha-svg-icon>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.integrations.config_entry.download_diagnostics"
|
|
||||||
)}
|
|
||||||
</ha-md-menu-item>
|
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
${!item.disabled_by &&
|
|
||||||
item.supports_reconfigure &&
|
|
||||||
item.source !== "system"
|
|
||||||
? html`
|
|
||||||
<ha-md-menu-item @click=${this._handleReconfigure}>
|
|
||||||
<ha-svg-icon slot="start" .path=${mdiWrench}></ha-svg-icon>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.integrations.config_entry.reconfigure"
|
|
||||||
)}
|
|
||||||
</ha-md-menu-item>
|
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
|
|
||||||
<ha-md-menu-item @click=${this._handleSystemOptions} graphic="icon">
|
|
||||||
<ha-svg-icon slot="start" .path=${mdiCog}></ha-svg-icon>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.integrations.config_entry.system_options"
|
|
||||||
)}
|
|
||||||
</ha-md-menu-item>
|
|
||||||
${item.disabled_by === "user"
|
|
||||||
? html`
|
|
||||||
<ha-md-menu-item @click=${this._handleEnable}>
|
|
||||||
<ha-svg-icon
|
|
||||||
slot="start"
|
|
||||||
.path=${mdiPlayCircleOutline}
|
|
||||||
></ha-svg-icon>
|
|
||||||
${this.hass.localize("ui.common.enable")}
|
|
||||||
</ha-md-menu-item>
|
|
||||||
`
|
|
||||||
: item.source !== "system"
|
|
||||||
? html`
|
|
||||||
<ha-md-menu-item
|
|
||||||
class="warning"
|
|
||||||
@click=${this._handleDisable}
|
|
||||||
graphic="icon"
|
|
||||||
>
|
|
||||||
<ha-svg-icon
|
|
||||||
slot="start"
|
|
||||||
class="warning"
|
|
||||||
.path=${mdiStopCircleOutline}
|
|
||||||
></ha-svg-icon>
|
|
||||||
${this.hass.localize("ui.common.disable")}
|
|
||||||
</ha-md-menu-item>
|
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
${item.source !== "system"
|
|
||||||
? html`
|
|
||||||
<ha-md-menu-item class="warning" @click=${this._handleDelete}>
|
|
||||||
<ha-svg-icon
|
|
||||||
slot="start"
|
|
||||||
class="warning"
|
|
||||||
.path=${mdiDelete}
|
|
||||||
></ha-svg-icon>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.integrations.config_entry.delete"
|
|
||||||
)}
|
|
||||||
</ha-md-menu-item>
|
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
</ha-md-button-menu>
|
</ha-md-button-menu>
|
||||||
</ha-md-list-item>`;
|
</ha-md-list-item>`;
|
||||||
}
|
}
|
||||||
|
@ -1031,6 +1137,27 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _fetchSubEntries() {
|
||||||
|
const subEntriesPromises = (
|
||||||
|
this._extraConfigEntries || this.configEntries
|
||||||
|
)?.map((entry) =>
|
||||||
|
entry.num_subentries
|
||||||
|
? getSubEntries(this.hass, entry.entry_id).then((subEntries) => ({
|
||||||
|
entry_id: entry.entry_id,
|
||||||
|
subEntries,
|
||||||
|
}))
|
||||||
|
: undefined
|
||||||
|
);
|
||||||
|
if (subEntriesPromises) {
|
||||||
|
const subEntries = await Promise.all(subEntriesPromises);
|
||||||
|
this._subEntries = {};
|
||||||
|
subEntries.forEach((entry) => {
|
||||||
|
if (!entry) return;
|
||||||
|
this._subEntries[entry.entry_id] = entry.subEntries;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async _fetchDiagnostics() {
|
private async _fetchDiagnostics() {
|
||||||
if (!this.domain || !isComponentLoaded(this.hass, "diagnostics")) {
|
if (!this.domain || !isComponentLoaded(this.hass, "diagnostics")) {
|
||||||
return;
|
return;
|
||||||
|
@ -1178,6 +1305,49 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _handleReconfigureSub(ev: Event): Promise<void> {
|
||||||
|
const configEntry = (
|
||||||
|
(ev.target as HTMLElement).closest(".sub-entry") as any
|
||||||
|
).configEntry;
|
||||||
|
const subEntry = ((ev.target as HTMLElement).closest(".sub-entry") as any)
|
||||||
|
.subEntry;
|
||||||
|
|
||||||
|
showSubConfigFlowDialog(
|
||||||
|
this,
|
||||||
|
configEntry,
|
||||||
|
subEntry.flowType || subEntry.subentry_type,
|
||||||
|
{
|
||||||
|
startFlowHandler: configEntry.entry_id,
|
||||||
|
subEntryId: subEntry.subentry_id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _handleDeleteSub(ev: Event): Promise<void> {
|
||||||
|
const configEntry = (
|
||||||
|
(ev.target as HTMLElement).closest(".sub-entry") as any
|
||||||
|
).configEntry;
|
||||||
|
const subEntry = ((ev.target as HTMLElement).closest(".sub-entry") as any)
|
||||||
|
.subEntry;
|
||||||
|
const confirmed = await showConfirmationDialog(this, {
|
||||||
|
title: this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.delete_confirm_title",
|
||||||
|
{ title: subEntry.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;
|
||||||
|
}
|
||||||
|
await deleteSubEntry(this.hass, configEntry.entry_id, subEntry.subentry_id);
|
||||||
|
}
|
||||||
|
|
||||||
private _handleDisable(ev: Event): void {
|
private _handleDisable(ev: Event): void {
|
||||||
this._disableIntegration(
|
this._disableIntegration(
|
||||||
((ev.target as HTMLElement).closest(".config_entry") as any).configEntry
|
((ev.target as HTMLElement).closest(".config_entry") as any).configEntry
|
||||||
|
@ -1456,6 +1626,12 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _addSubEntry(ev) {
|
||||||
|
showSubConfigFlowDialog(this, ev.target.entry, ev.target.flowType, {
|
||||||
|
startFlowHandler: ev.target.entry.entry_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
|
@ -1585,6 +1761,9 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
content: "";
|
content: "";
|
||||||
}
|
}
|
||||||
|
ha-md-list-item.sub-entry {
|
||||||
|
--md-list-item-leading-space: 50px;
|
||||||
|
}
|
||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -207,6 +207,8 @@ class HaConfigIntegrationsDashboard extends KeyboardShortcutMixin(
|
||||||
supports_remove_device: false,
|
supports_remove_device: false,
|
||||||
supports_unload: false,
|
supports_unload: false,
|
||||||
supports_reconfigure: false,
|
supports_reconfigure: false,
|
||||||
|
supported_subentry_types: {},
|
||||||
|
num_subentries: 0,
|
||||||
pref_disable_new_entities: false,
|
pref_disable_new_entities: false,
|
||||||
pref_disable_polling: false,
|
pref_disable_polling: false,
|
||||||
disabled_by: null,
|
disabled_by: null,
|
||||||
|
|
Loading…
Reference in New Issue