mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-22 16:56:35 +00:00
Show config entry state on card (#8911)
This commit is contained in:
parent
2dcd0d2b0a
commit
60fe48d355
299
gallery/src/demos/demo-integration-card.ts
Normal file
299
gallery/src/demos/demo-integration-card.ts
Normal file
@ -0,0 +1,299 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
css,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
property,
|
||||
} from "lit-element";
|
||||
import "../../../src/components/ha-formfield";
|
||||
import "../../../src/components/ha-switch";
|
||||
|
||||
import { IntegrationManifest } from "../../../src/data/integration";
|
||||
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import "../../../src/panels/config/integrations/ha-integration-card";
|
||||
import "../../../src/panels/config/integrations/ha-ignored-config-entry-card";
|
||||
import "../../../src/panels/config/integrations/ha-config-flow-card";
|
||||
import type {
|
||||
ConfigEntryExtended,
|
||||
DataEntryFlowProgressExtended,
|
||||
} from "../../../src/panels/config/integrations/ha-config-integrations";
|
||||
import { DeviceRegistryEntry } from "../../../src/data/device_registry";
|
||||
import { EntityRegistryEntry } from "../../../src/data/entity_registry";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
|
||||
const createConfigEntry = (
|
||||
title: string,
|
||||
override: Partial<ConfigEntryExtended> = {}
|
||||
): ConfigEntryExtended => ({
|
||||
entry_id: title,
|
||||
domain: "esphome",
|
||||
localized_domain_name: "ESPHome",
|
||||
title,
|
||||
source: "zeroconf",
|
||||
state: "loaded",
|
||||
connection_class: "local_push",
|
||||
supports_options: false,
|
||||
supports_unload: true,
|
||||
disabled_by: null,
|
||||
...override,
|
||||
});
|
||||
|
||||
const createManifest = (
|
||||
isCustom: boolean,
|
||||
isCloud: boolean
|
||||
): IntegrationManifest => ({
|
||||
name: "ESPHome",
|
||||
domain: "esphome",
|
||||
is_built_in: !isCustom,
|
||||
config_flow: false,
|
||||
documentation: "https://www.home-assistant.io/integrations/esphome/",
|
||||
iot_class: isCloud ? "cloud_polling" : "local_polling",
|
||||
});
|
||||
|
||||
const loadedEntry = createConfigEntry("Loaded");
|
||||
const nameAsDomainEntry = createConfigEntry("ESPHome");
|
||||
const longNameEntry = createConfigEntry(
|
||||
"Entry with a super long name that is going to the next line"
|
||||
);
|
||||
const configPanelEntry = createConfigEntry("Config Panel", {
|
||||
domain: "mqtt",
|
||||
localized_domain_name: "MQTT",
|
||||
});
|
||||
const optionsFlowEntry = createConfigEntry("Options Flow", {
|
||||
supports_options: true,
|
||||
});
|
||||
const setupErrorEntry = createConfigEntry("Setup Error", {
|
||||
state: "setup_error",
|
||||
});
|
||||
const migrationErrorEntry = createConfigEntry("Migration Error", {
|
||||
state: "migration_error",
|
||||
});
|
||||
const setupRetryEntry = createConfigEntry("Setup Retry", {
|
||||
state: "setup_retry",
|
||||
});
|
||||
const failedUnloadEntry = createConfigEntry("Failed Unload", {
|
||||
state: "failed_unload",
|
||||
});
|
||||
const notLoadedEntry = createConfigEntry("Not Loaded", { state: "not_loaded" });
|
||||
const disabledEntry = createConfigEntry("Disabled", {
|
||||
state: "not_loaded",
|
||||
disabled_by: "user",
|
||||
});
|
||||
const disabledFailedUnloadEntry = createConfigEntry(
|
||||
"Disabled - Failed Unload",
|
||||
{
|
||||
state: "failed_unload",
|
||||
disabled_by: "user",
|
||||
}
|
||||
);
|
||||
|
||||
const configFlows: DataEntryFlowProgressExtended[] = [
|
||||
{
|
||||
flow_id: "adbb401329d8439ebb78ef29837826a8",
|
||||
handler: "roku",
|
||||
context: {
|
||||
source: "ssdp",
|
||||
unique_id: "YF008D862864",
|
||||
title_placeholders: {
|
||||
name: "Living room Roku",
|
||||
},
|
||||
},
|
||||
step_id: "discovery_confirm",
|
||||
localized_title: "Roku: Living room Roku",
|
||||
},
|
||||
{
|
||||
flow_id: "adbb401329d8439ebb78ef29837826a8",
|
||||
handler: "hue",
|
||||
context: {
|
||||
source: "reauth",
|
||||
unique_id: "YF008D862864",
|
||||
title_placeholders: {
|
||||
name: "Living room Roku",
|
||||
},
|
||||
},
|
||||
step_id: "discovery_confirm",
|
||||
localized_title: "Philips Hue",
|
||||
},
|
||||
];
|
||||
|
||||
const configEntries: Array<{
|
||||
items: ConfigEntryExtended[];
|
||||
is_custom?: boolean;
|
||||
disabled?: boolean;
|
||||
highlight?: string;
|
||||
}> = [
|
||||
{ items: [loadedEntry] },
|
||||
{ items: [configPanelEntry] },
|
||||
{ items: [optionsFlowEntry] },
|
||||
{ items: [nameAsDomainEntry] },
|
||||
{ items: [longNameEntry] },
|
||||
{ items: [setupErrorEntry] },
|
||||
{ items: [migrationErrorEntry] },
|
||||
{ items: [setupRetryEntry] },
|
||||
{ items: [failedUnloadEntry] },
|
||||
{ items: [notLoadedEntry] },
|
||||
{
|
||||
items: [
|
||||
loadedEntry,
|
||||
longNameEntry,
|
||||
setupErrorEntry,
|
||||
migrationErrorEntry,
|
||||
setupRetryEntry,
|
||||
failedUnloadEntry,
|
||||
notLoadedEntry,
|
||||
disabledEntry,
|
||||
nameAsDomainEntry,
|
||||
configPanelEntry,
|
||||
optionsFlowEntry,
|
||||
],
|
||||
},
|
||||
{ disabled: true, items: [disabledEntry] },
|
||||
{ disabled: true, items: [disabledFailedUnloadEntry] },
|
||||
{
|
||||
disabled: true,
|
||||
items: [disabledEntry, disabledFailedUnloadEntry],
|
||||
},
|
||||
{
|
||||
items: [loadedEntry, configPanelEntry],
|
||||
highlight: "Loaded",
|
||||
},
|
||||
];
|
||||
|
||||
const createEntityRegistryEntries = (
|
||||
item: ConfigEntryExtended
|
||||
): EntityRegistryEntry[] => [
|
||||
{
|
||||
config_entry_id: item.entry_id,
|
||||
device_id: "mock-device-id",
|
||||
area_id: null,
|
||||
disabled_by: null,
|
||||
entity_id: "binary_sensor.updater",
|
||||
name: null,
|
||||
icon: null,
|
||||
platform: "updater",
|
||||
},
|
||||
];
|
||||
|
||||
const createDeviceRegistryEntries = (
|
||||
item: ConfigEntryExtended
|
||||
): DeviceRegistryEntry[] => [
|
||||
{
|
||||
entry_type: null,
|
||||
config_entries: [item.entry_id],
|
||||
connections: [],
|
||||
manufacturer: "ESPHome",
|
||||
model: "Mock Device",
|
||||
name: "Tag Reader",
|
||||
sw_version: null,
|
||||
id: "mock-device-id",
|
||||
identifiers: [],
|
||||
via_device_id: null,
|
||||
area_id: null,
|
||||
name_by_user: null,
|
||||
disabled_by: null,
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("demo-integration-card")
|
||||
export class DemoIntegrationCard extends LitElement {
|
||||
@property({ attribute: false }) hass?: HomeAssistant;
|
||||
|
||||
@internalProperty() isCustomIntegration = false;
|
||||
|
||||
@internalProperty() isCloud = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<div class="filters">
|
||||
<ha-formfield label="Custom Integration">
|
||||
<ha-switch @change=${this._toggleCustomIntegration}></ha-switch>
|
||||
</ha-formfield>
|
||||
<ha-formfield label="Relies on cloud">
|
||||
<ha-switch @change=${this._toggleCloud}></ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
|
||||
<ha-ignored-config-entry-card
|
||||
.hass=${this.hass}
|
||||
.entry=${createConfigEntry("Ignored Entry")}
|
||||
.manifest=${createManifest(this.isCustomIntegration, this.isCloud)}
|
||||
></ha-ignored-config-entry-card>
|
||||
|
||||
${configFlows.map(
|
||||
(flow) => html`
|
||||
<ha-config-flow-card
|
||||
.hass=${this.hass}
|
||||
.flow=${flow}
|
||||
.manifest=${createManifest(this.isCustomIntegration, this.isCloud)}
|
||||
></ha-config-flow-card>
|
||||
`
|
||||
)}
|
||||
${configEntries.map(
|
||||
(info) => html`
|
||||
<ha-integration-card
|
||||
class=${classMap({
|
||||
highlight: info.highlight !== undefined,
|
||||
})}
|
||||
.hass=${this.hass}
|
||||
domain="esphome"
|
||||
.items=${info.items}
|
||||
.manifest=${createManifest(this.isCustomIntegration, this.isCloud)}
|
||||
.entityRegistryEntries=${createEntityRegistryEntries(info.items[0])}
|
||||
.deviceRegistryEntries=${createDeviceRegistryEntries(info.items[0])}
|
||||
?disabled=${info.disabled}
|
||||
.selectedConfigEntryId=${info.highlight}
|
||||
></ha-integration-card>
|
||||
`
|
||||
)}
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
const hass = provideHass(this);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("config", "en");
|
||||
}
|
||||
|
||||
private _toggleCustomIntegration() {
|
||||
this.isCustomIntegration = !this.isCustomIntegration;
|
||||
}
|
||||
|
||||
private _toggleCloud() {
|
||||
this.isCloud = !this.isCloud;
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
:host {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
grid-gap: 16px 16px;
|
||||
padding: 8px 16px 16px;
|
||||
margin-bottom: 64px;
|
||||
}
|
||||
|
||||
:host > * {
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
ha-formfield {
|
||||
margin: 8px 0;
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-integration-card": DemoIntegrationCard;
|
||||
}
|
||||
}
|
@ -5,11 +5,17 @@ export interface ConfigEntry {
|
||||
domain: string;
|
||||
title: string;
|
||||
source: string;
|
||||
state: string;
|
||||
state:
|
||||
| "loaded"
|
||||
| "setup_error"
|
||||
| "migration_error"
|
||||
| "setup_retry"
|
||||
| "not_loaded"
|
||||
| "failed_unload";
|
||||
connection_class: string;
|
||||
supports_options: boolean;
|
||||
supports_unload: boolean;
|
||||
disabled_by: string | null;
|
||||
disabled_by: "user" | null;
|
||||
}
|
||||
|
||||
export interface ConfigEntryMutableParams {
|
||||
|
@ -9,13 +9,13 @@ export interface DeviceRegistryEntry {
|
||||
config_entries: string[];
|
||||
connections: Array<[string, string]>;
|
||||
identifiers: Array<[string, string]>;
|
||||
manufacturer: string;
|
||||
model?: string;
|
||||
name?: string;
|
||||
sw_version?: string;
|
||||
via_device_id?: string;
|
||||
area_id?: string;
|
||||
name_by_user?: string;
|
||||
manufacturer: string | null;
|
||||
model: string | null;
|
||||
name: string | null;
|
||||
sw_version: string | null;
|
||||
via_device_id: string | null;
|
||||
area_id: string | null;
|
||||
name_by_user: string | null;
|
||||
entry_type: "service" | null;
|
||||
disabled_by: string | null;
|
||||
}
|
||||
|
@ -5,12 +5,12 @@ import { HomeAssistant } from "../types";
|
||||
|
||||
export interface EntityRegistryEntry {
|
||||
entity_id: string;
|
||||
name: string;
|
||||
icon?: string;
|
||||
name: string | null;
|
||||
icon: string | null;
|
||||
platform: string;
|
||||
config_entry_id?: string;
|
||||
device_id?: string;
|
||||
area_id?: string;
|
||||
config_entry_id: string | null;
|
||||
device_id: string | null;
|
||||
area_id: string | null;
|
||||
disabled_by: string | null;
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,13 @@ export interface IntegrationManifest {
|
||||
ssdp?: Array<{ manufacturer?: string; modelName?: string; st?: string }>;
|
||||
zeroconf?: string[];
|
||||
homekit?: { models: string[] };
|
||||
quality_scale?: string;
|
||||
quality_scale?: "gold" | "internal" | "platinum" | "silver";
|
||||
iot_class:
|
||||
| "assumed_state"
|
||||
| "cloud_polling"
|
||||
| "cloud_push"
|
||||
| "local_polling"
|
||||
| "local_push";
|
||||
}
|
||||
|
||||
export const integrationIssuesUrl = (
|
||||
|
@ -33,7 +33,7 @@ class DialogDeviceRegistryDetail extends LitElement {
|
||||
|
||||
@internalProperty() private _params?: DeviceRegistryDetailDialogParams;
|
||||
|
||||
@internalProperty() private _areaId?: string;
|
||||
@internalProperty() private _areaId?: string | null;
|
||||
|
||||
@internalProperty() private _disabledBy!: string | null;
|
||||
|
||||
|
@ -38,7 +38,7 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) {
|
||||
|
||||
@internalProperty() private _entityId!: string;
|
||||
|
||||
@internalProperty() private _areaId?: string;
|
||||
@internalProperty() private _areaId?: string | null;
|
||||
|
||||
@internalProperty() private _disabledBy!: string | null;
|
||||
|
||||
|
@ -663,6 +663,10 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
entity_id: entityId,
|
||||
platform: computeDomain(entityId),
|
||||
disabled_by: null,
|
||||
area_id: null,
|
||||
config_entry_id: null,
|
||||
device_id: null,
|
||||
icon: null,
|
||||
readonly: true,
|
||||
selectable: false,
|
||||
});
|
||||
|
130
src/panels/config/integrations/ha-config-flow-card.ts
Normal file
130
src/panels/config/integrations/ha-config-flow-card.ts
Normal file
@ -0,0 +1,130 @@
|
||||
import {
|
||||
customElement,
|
||||
LitElement,
|
||||
property,
|
||||
css,
|
||||
html,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import {
|
||||
ATTENTION_SOURCES,
|
||||
DISCOVERY_SOURCES,
|
||||
ignoreConfigFlow,
|
||||
localizeConfigFlowTitle,
|
||||
} from "../../../data/config_flow";
|
||||
import type { IntegrationManifest } from "../../../data/integration";
|
||||
import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-config-flow";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { DataEntryFlowProgressExtended } from "./ha-config-integrations";
|
||||
import "./ha-integration-action-card";
|
||||
|
||||
@customElement("ha-config-flow-card")
|
||||
export class HaConfigFlowCard extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public flow!: DataEntryFlowProgressExtended;
|
||||
|
||||
@property() public manifest?: IntegrationManifest;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const attention = ATTENTION_SOURCES.includes(this.flow.context.source);
|
||||
return html`
|
||||
<ha-integration-action-card
|
||||
class=${classMap({
|
||||
discovered: !attention,
|
||||
attention: attention,
|
||||
})}
|
||||
.hass=${this.hass}
|
||||
.manifest=${this.manifest}
|
||||
.banner=${this.hass.localize(
|
||||
`ui.panel.config.integrations.${
|
||||
attention ? "attention" : "discovered"
|
||||
}`
|
||||
)}
|
||||
.domain=${this.flow.handler}
|
||||
.label=${this.flow.localized_title}
|
||||
>
|
||||
<mwc-button
|
||||
unelevated
|
||||
@click=${this._continueFlow}
|
||||
.label=${this.hass.localize(
|
||||
`ui.panel.config.integrations.${
|
||||
attention ? "reconfigure" : "configure"
|
||||
}`
|
||||
)}
|
||||
></mwc-button>
|
||||
${DISCOVERY_SOURCES.includes(this.flow.context.source) &&
|
||||
this.flow.context.unique_id
|
||||
? html`
|
||||
<mwc-button
|
||||
@click=${this._ignoreFlow}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.integrations.ignore.ignore"
|
||||
)}
|
||||
></mwc-button>
|
||||
`
|
||||
: ""}
|
||||
</ha-integration-action-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private _continueFlow() {
|
||||
showConfigFlowDialog(this, {
|
||||
continueFlowId: this.flow.flow_id,
|
||||
dialogClosedCallback: () => {
|
||||
this._handleFlowUpdated();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async _ignoreFlow() {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.hass!.localize(
|
||||
"ui.panel.config.integrations.ignore.confirm_ignore_title",
|
||||
"name",
|
||||
localizeConfigFlowTitle(this.hass.localize, this.flow)
|
||||
),
|
||||
text: this.hass!.localize(
|
||||
"ui.panel.config.integrations.ignore.confirm_ignore"
|
||||
),
|
||||
confirmText: this.hass!.localize(
|
||||
"ui.panel.config.integrations.ignore.ignore"
|
||||
),
|
||||
});
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
await ignoreConfigFlow(
|
||||
this.hass,
|
||||
this.flow.flow_id,
|
||||
localizeConfigFlowTitle(this.hass.localize, this.flow)
|
||||
);
|
||||
this._handleFlowUpdated();
|
||||
}
|
||||
|
||||
private _handleFlowUpdated() {
|
||||
fireEvent(this, "change", undefined, {
|
||||
bubbles: false,
|
||||
});
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.attention {
|
||||
--state-color: var(--error-color);
|
||||
--text-on-state-color: var(--text-primary-color);
|
||||
}
|
||||
.discovered {
|
||||
--state-color: var(--primary-color);
|
||||
--text-on-state-color: var(--text-primary-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-config-flow-card": HaConfigFlowCard;
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
import { mdiPackageVariant, mdiCloud } from "@mdi/js";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import { css, html } from "lit-element";
|
||||
import { IntegrationManifest } from "../../../data/integration";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
|
||||
export const haConfigIntegrationsStyles = css`
|
||||
.banner {
|
||||
background-color: var(--state-color);
|
||||
color: var(--text-on-state-color);
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
}
|
||||
.icons {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 16px;
|
||||
color: var(--text-on-state-color, var(--secondary-text-color));
|
||||
background-color: var(--state-color, #e0e0e0);
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
padding: 1px 4px 2px;
|
||||
}
|
||||
.icons ha-svg-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
paper-tooltip {
|
||||
white-space: nowrap;
|
||||
}
|
||||
`;
|
||||
|
||||
export const haConfigIntegrationRenderIcons = (
|
||||
hass: HomeAssistant,
|
||||
manifest?: IntegrationManifest
|
||||
) => {
|
||||
const icons: [string, string][] = [];
|
||||
|
||||
if (manifest) {
|
||||
if (!manifest.is_built_in) {
|
||||
icons.push([
|
||||
mdiPackageVariant,
|
||||
hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.provided_by_custom_component"
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
if (manifest.iot_class && manifest.iot_class.startsWith("cloud_")) {
|
||||
icons.push([
|
||||
mdiCloud,
|
||||
hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.depends_on_cloud"
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return icons.length === 0
|
||||
? ""
|
||||
: html`
|
||||
<div class="icons">
|
||||
${icons.map(
|
||||
([icon, description]) => html`
|
||||
<span>
|
||||
<ha-svg-icon .path=${icon}></ha-svg-icon>
|
||||
<paper-tooltip animation-delay="0"
|
||||
>${description}</paper-tooltip
|
||||
>
|
||||
</span>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
};
|
@ -2,9 +2,8 @@ import "@material/mwc-icon-button";
|
||||
import { ActionDetail } from "@material/mwc-list";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiFilterVariant, mdiPlus } from "@mdi/js";
|
||||
import "@polymer/app-route/app-route";
|
||||
import Fuse from "fuse.js";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@ -16,31 +15,15 @@ import {
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import "../../../common/search/search-input";
|
||||
import { caseInsensitiveCompare } from "../../../common/string/compare";
|
||||
import { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import { extractSearchParam } from "../../../common/url/search-params";
|
||||
import { nextRender } from "../../../common/util/render-status";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-fab";
|
||||
import "../../../components/ha-checkbox";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { ConfigEntry, getConfigEntries } from "../../../data/config_entries";
|
||||
import {
|
||||
ConfigEntry,
|
||||
deleteConfigEntry,
|
||||
getConfigEntries,
|
||||
} from "../../../data/config_entries";
|
||||
import {
|
||||
ATTENTION_SOURCES,
|
||||
DISCOVERY_SOURCES,
|
||||
getConfigFlowInProgressCollection,
|
||||
ignoreConfigFlow,
|
||||
localizeConfigFlowTitle,
|
||||
subscribeConfigFlowInProgress,
|
||||
} from "../../../data/config_flow";
|
||||
@ -60,21 +43,43 @@ import {
|
||||
} from "../../../data/integration";
|
||||
import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-config-flow";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../layouts/hass-loading-screen";
|
||||
import "../../../layouts/hass-tabs-subpage";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
import { brandsUrl } from "../../../util/brands-url";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import "./ha-integration-card";
|
||||
import type {
|
||||
ConfigEntryRemovedEvent,
|
||||
ConfigEntryUpdatedEvent,
|
||||
HaIntegrationCard,
|
||||
} from "./ha-integration-card";
|
||||
|
||||
interface DataEntryFlowProgressExtended extends DataEntryFlowProgress {
|
||||
import type { HomeAssistant, Route } from "../../../types";
|
||||
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import type { HaIntegrationCard } from "./ha-integration-card";
|
||||
|
||||
import "../../../common/search/search-input";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-fab";
|
||||
import "../../../components/ha-checkbox";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../layouts/hass-loading-screen";
|
||||
import "../../../layouts/hass-tabs-subpage";
|
||||
import "./ha-integration-card";
|
||||
import "./ha-config-flow-card";
|
||||
import "./ha-ignored-config-entry-card";
|
||||
|
||||
export interface ConfigEntryUpdatedEvent {
|
||||
entry: ConfigEntry;
|
||||
}
|
||||
|
||||
export interface ConfigEntryRemovedEvent {
|
||||
entryId: string;
|
||||
}
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"entry-updated": ConfigEntryUpdatedEvent;
|
||||
"entry-removed": ConfigEntryRemovedEvent;
|
||||
}
|
||||
}
|
||||
|
||||
export interface DataEntryFlowProgressExtended extends DataEntryFlowProgress {
|
||||
localized_title?: string;
|
||||
}
|
||||
|
||||
@ -119,9 +124,8 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
@internalProperty()
|
||||
private _deviceRegistryEntries: DeviceRegistryEntry[] = [];
|
||||
|
||||
@internalProperty() private _manifests!: {
|
||||
[domain: string]: IntegrationManifest;
|
||||
};
|
||||
@internalProperty()
|
||||
private _manifests: Record<string, IntegrationManifest> = {};
|
||||
|
||||
@internalProperty() private _showIgnored = false;
|
||||
|
||||
@ -217,12 +221,6 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
configEntriesInProgress: DataEntryFlowProgressExtended[],
|
||||
filter?: string
|
||||
): DataEntryFlowProgressExtended[] => {
|
||||
configEntriesInProgress = configEntriesInProgress.map(
|
||||
(flow: DataEntryFlowProgressExtended) => ({
|
||||
...flow,
|
||||
title: localizeConfigFlowTitle(this.hass.localize, flow),
|
||||
})
|
||||
);
|
||||
if (!filter) {
|
||||
return configEntriesInProgress;
|
||||
}
|
||||
@ -349,11 +347,12 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
"number",
|
||||
disabledConfigEntries.size
|
||||
)}
|
||||
<mwc-button @click=${this._toggleShowDisabled}>
|
||||
${this.hass.localize(
|
||||
<mwc-button
|
||||
@click=${this._toggleShowDisabled}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.integrations.disable.show"
|
||||
)}
|
||||
</mwc-button>
|
||||
></mwc-button>
|
||||
</div>`
|
||||
: ""}
|
||||
${filterMenu}
|
||||
@ -362,112 +361,30 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
|
||||
<div
|
||||
class="container"
|
||||
@entry-removed=${this._handleRemoved}
|
||||
@entry-updated=${this._handleUpdated}
|
||||
@entry-removed=${this._handleEntryRemoved}
|
||||
@entry-updated=${this._handleEntryUpdated}
|
||||
>
|
||||
${this._showIgnored
|
||||
? ignoredConfigEntries.map(
|
||||
(item: ConfigEntryExtended) => html`
|
||||
<ha-card outlined class="ignored">
|
||||
<div class="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.ignore.ignored"
|
||||
)}
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="image">
|
||||
<img
|
||||
src=${brandsUrl(item.domain, "logo")}
|
||||
referrerpolicy="no-referrer"
|
||||
@error=${this._onImageError}
|
||||
@load=${this._onImageLoad}
|
||||
/>
|
||||
</div>
|
||||
<h2>
|
||||
${// In 2020.2 we added support for item.title. All ignored entries before
|
||||
// that have title "Ignored" so we fallback to localized domain name.
|
||||
item.title === "Ignored"
|
||||
? item.localized_domain_name
|
||||
: item.title}
|
||||
</h2>
|
||||
<mwc-button
|
||||
@click=${this._removeIgnoredIntegration}
|
||||
.entry=${item}
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.panel.config.integrations.ignore.stop_ignore"
|
||||
)}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.ignore.stop_ignore"
|
||||
)}</mwc-button
|
||||
>
|
||||
</div>
|
||||
</ha-card>
|
||||
(entry: ConfigEntryExtended) => html`
|
||||
<ha-ignored-config-entry-card
|
||||
.hass=${this.hass}
|
||||
.manifest=${this._manifests[entry.domain]}
|
||||
.entry=${entry}
|
||||
@change=${this._handleFlowUpdated}
|
||||
></ha-ignored-config-entry-card>
|
||||
`
|
||||
)
|
||||
: ""}
|
||||
${configEntriesInProgress.length
|
||||
? configEntriesInProgress.map(
|
||||
(flow: DataEntryFlowProgressExtended) => {
|
||||
const attention = ATTENTION_SOURCES.includes(
|
||||
flow.context.source
|
||||
);
|
||||
return html`
|
||||
<ha-card
|
||||
outlined
|
||||
class=${classMap({
|
||||
discovered: !attention,
|
||||
attention: attention,
|
||||
})}
|
||||
>
|
||||
<div class="header">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.integrations.${
|
||||
attention ? "attention" : "discovered"
|
||||
}`
|
||||
)}
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="image">
|
||||
<img
|
||||
src=${brandsUrl(flow.handler, "logo")}
|
||||
referrerpolicy="no-referrer"
|
||||
@error=${this._onImageError}
|
||||
@load=${this._onImageLoad}
|
||||
/>
|
||||
</div>
|
||||
<h2>
|
||||
${flow.localized_title}
|
||||
</h2>
|
||||
<div>
|
||||
<mwc-button
|
||||
unelevated
|
||||
@click=${this._continueFlow}
|
||||
.flowId=${flow.flow_id}
|
||||
>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.integrations.${
|
||||
attention ? "reconfigure" : "configure"
|
||||
}`
|
||||
)}
|
||||
</mwc-button>
|
||||
${DISCOVERY_SOURCES.includes(flow.context.source) &&
|
||||
flow.context.unique_id
|
||||
? html`
|
||||
<mwc-button
|
||||
@click=${this._ignoreFlow}
|
||||
.flow=${flow}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.ignore.ignore"
|
||||
)}
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
(flow: DataEntryFlowProgressExtended) => html`
|
||||
<ha-config-flow-card
|
||||
.hass=${this.hass}
|
||||
.flow=${flow}
|
||||
@change=${this._handleFlowUpdated}
|
||||
></ha-config-flow-card>
|
||||
`
|
||||
)
|
||||
: ""}
|
||||
${this._showDisabled
|
||||
@ -498,25 +415,28 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
.deviceRegistryEntries=${this._deviceRegistryEntries}
|
||||
></ha-integration-card>`
|
||||
)
|
||||
: !this._configEntries.length
|
||||
: // If we're showing 0 cards, show empty state text
|
||||
(!this._showIgnored || ignoredConfigEntries.length === 0) &&
|
||||
(!this._showDisabled || disabledConfigEntries.size === 0) &&
|
||||
groupedConfigEntries.size === 0
|
||||
? html`
|
||||
<ha-card outlined>
|
||||
<div class="card-content">
|
||||
<h1>
|
||||
${this.hass.localize("ui.panel.config.integrations.none")}
|
||||
</h1>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.no_integrations"
|
||||
)}
|
||||
</p>
|
||||
<mwc-button @click=${this._createFlow} unelevated
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.add_integration"
|
||||
)}</mwc-button
|
||||
>
|
||||
</div>
|
||||
</ha-card>
|
||||
<div class="empty-message">
|
||||
<h1>
|
||||
${this.hass.localize("ui.panel.config.integrations.none")}
|
||||
</h1>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.no_integrations"
|
||||
)}
|
||||
</p>
|
||||
<mwc-button
|
||||
@click=${this._createFlow}
|
||||
unelevated
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.integrations.add_integration"
|
||||
)}
|
||||
></mwc-button>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${this._filter &&
|
||||
@ -524,7 +444,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
!groupedConfigEntries.size &&
|
||||
this._configEntries.length
|
||||
? html`
|
||||
<div class="none-found">
|
||||
<div class="empty-message">
|
||||
<h1>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.none_found"
|
||||
@ -581,13 +501,13 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
this._manifests = manifests;
|
||||
}
|
||||
|
||||
private _handleRemoved(ev: HASSDomEvent<ConfigEntryRemovedEvent>) {
|
||||
private _handleEntryRemoved(ev: HASSDomEvent<ConfigEntryRemovedEvent>) {
|
||||
this._configEntries = this._configEntries!.filter(
|
||||
(entry) => entry.entry_id !== ev.detail.entryId
|
||||
);
|
||||
}
|
||||
|
||||
private _handleUpdated(ev: HASSDomEvent<ConfigEntryUpdatedEvent>) {
|
||||
private _handleEntryUpdated(ev: HASSDomEvent<ConfigEntryUpdatedEvent>) {
|
||||
const newEntry = ev.detail.entry;
|
||||
this._configEntries = this._configEntries!.map((entry) =>
|
||||
entry.entry_id === newEntry.entry_id
|
||||
@ -599,6 +519,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
private _handleFlowUpdated() {
|
||||
this._loadConfigEntries();
|
||||
getConfigFlowInProgressCollection(this.hass.connection).refresh();
|
||||
this._fetchManifests();
|
||||
}
|
||||
|
||||
private _createFlow() {
|
||||
@ -608,50 +529,14 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
},
|
||||
showAdvanced: this.showAdvanced,
|
||||
});
|
||||
// For config entries. Also loading config flow ones for add integration
|
||||
// For config entries. Also loading config flow ones for added integration
|
||||
this.hass.loadBackendTranslation("title", undefined, true);
|
||||
}
|
||||
|
||||
private _continueFlow(ev: Event) {
|
||||
showConfigFlowDialog(this, {
|
||||
continueFlowId: (ev.target! as any).flowId,
|
||||
dialogClosedCallback: () => {
|
||||
this._handleFlowUpdated();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async _ignoreFlow(ev: Event) {
|
||||
const flow = (ev.target! as any).flow;
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.hass!.localize(
|
||||
"ui.panel.config.integrations.ignore.confirm_ignore_title",
|
||||
"name",
|
||||
localizeConfigFlowTitle(this.hass.localize, flow)
|
||||
),
|
||||
text: this.hass!.localize(
|
||||
"ui.panel.config.integrations.ignore.confirm_ignore"
|
||||
),
|
||||
confirmText: this.hass!.localize(
|
||||
"ui.panel.config.integrations.ignore.ignore"
|
||||
),
|
||||
});
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
await ignoreConfigFlow(
|
||||
this.hass,
|
||||
flow.flow_id,
|
||||
localizeConfigFlowTitle(this.hass.localize, flow)
|
||||
);
|
||||
this._loadConfigEntries();
|
||||
getConfigFlowInProgressCollection(this.hass.connection).refresh();
|
||||
}
|
||||
|
||||
private _handleMenuAction(ev: CustomEvent<ActionDetail>) {
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
this._toggleShowIgnored();
|
||||
this._showIgnored = !this._showIgnored;
|
||||
break;
|
||||
case 1:
|
||||
this._toggleShowDisabled();
|
||||
@ -659,54 +544,14 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
private _toggleShowIgnored() {
|
||||
this._showIgnored = !this._showIgnored;
|
||||
}
|
||||
|
||||
private _toggleShowDisabled() {
|
||||
this._showDisabled = !this._showDisabled;
|
||||
}
|
||||
|
||||
private async _removeIgnoredIntegration(ev: Event) {
|
||||
const entry = (ev.target! as any).entry;
|
||||
showConfirmationDialog(this, {
|
||||
title: this.hass!.localize(
|
||||
"ui.panel.config.integrations.ignore.confirm_delete_ignore_title",
|
||||
"name",
|
||||
this.hass.localize(`component.${entry.domain}.title`)
|
||||
),
|
||||
text: this.hass!.localize(
|
||||
"ui.panel.config.integrations.ignore.confirm_delete_ignore"
|
||||
),
|
||||
confirmText: this.hass!.localize(
|
||||
"ui.panel.config.integrations.ignore.stop_ignore"
|
||||
),
|
||||
confirm: async () => {
|
||||
const result = await deleteConfigEntry(this.hass, entry.entry_id);
|
||||
if (result.require_restart) {
|
||||
alert(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.restart_confirm"
|
||||
)
|
||||
);
|
||||
}
|
||||
this._loadConfigEntries();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _handleSearchChange(ev: CustomEvent) {
|
||||
this._filter = ev.detail.value;
|
||||
}
|
||||
|
||||
private _onImageLoad(ev) {
|
||||
ev.target.style.visibility = "initial";
|
||||
}
|
||||
|
||||
private _onImageError(ev) {
|
||||
ev.target.style.visibility = "hidden";
|
||||
}
|
||||
|
||||
private async _highlightEntry() {
|
||||
await nextRender();
|
||||
const entryId = this._searchParms.get("config_entry")!;
|
||||
@ -769,66 +614,18 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
padding: 8px 16px 16px;
|
||||
margin-bottom: 64px;
|
||||
}
|
||||
ha-card {
|
||||
.container > * {
|
||||
max-width: 500px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.attention {
|
||||
--ha-card-border-color: var(--error-color);
|
||||
}
|
||||
.attention .header {
|
||||
background: var(--error-color);
|
||||
color: var(--text-primary-color);
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
.attention mwc-button {
|
||||
--mdc-theme-primary: var(--error-color);
|
||||
}
|
||||
.discovered {
|
||||
--ha-card-border-color: var(--primary-color);
|
||||
}
|
||||
.discovered .header {
|
||||
background: var(--primary-color);
|
||||
color: var(--text-primary-color);
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
.ignored {
|
||||
--ha-card-border-color: var(--light-theme-disabled-color);
|
||||
}
|
||||
.ignored img {
|
||||
filter: grayscale(1);
|
||||
}
|
||||
.ignored .header {
|
||||
background: var(--light-theme-disabled-color);
|
||||
color: var(--text-primary-color);
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
.card-content {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
margin-top: 0;
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.image {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 60px;
|
||||
margin-bottom: 16px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.none-found {
|
||||
|
||||
.empty-message {
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
}
|
||||
.empty-message h1 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
search-input.header {
|
||||
display: block;
|
||||
position: relative;
|
||||
@ -848,27 +645,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
img {
|
||||
max-height: 100%;
|
||||
max-width: 90%;
|
||||
}
|
||||
.none-found {
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
}
|
||||
h1 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
h2 {
|
||||
margin-top: 0;
|
||||
word-wrap: break-word;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.active-filters {
|
||||
color: var(--primary-text-color);
|
||||
position: relative;
|
||||
|
@ -0,0 +1,95 @@
|
||||
import {
|
||||
customElement,
|
||||
LitElement,
|
||||
property,
|
||||
css,
|
||||
html,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { deleteConfigEntry } from "../../../data/config_entries";
|
||||
import type { IntegrationManifest } from "../../../data/integration";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { ConfigEntryExtended } from "./ha-config-integrations";
|
||||
import "./ha-integration-action-card";
|
||||
|
||||
@customElement("ha-ignored-config-entry-card")
|
||||
export class HaIgnoredConfigEntryCard extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public entry!: ConfigEntryExtended;
|
||||
|
||||
@property() public manifest?: IntegrationManifest;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-integration-action-card
|
||||
.hass=${this.hass}
|
||||
.manifest=${this.manifest}
|
||||
.banner=${this.hass.localize(
|
||||
"ui.panel.config.integrations.ignore.ignored"
|
||||
)}
|
||||
.domain=${this.entry.domain}
|
||||
.label=${this.entry.title === "Ignored"
|
||||
? // In 2020.2 we added support for entry.title. All ignored entries before
|
||||
// that have title "Ignored" so we fallback to localized domain name.
|
||||
this.entry.localized_domain_name
|
||||
: this.entry.title}
|
||||
>
|
||||
<mwc-button
|
||||
unelevated
|
||||
@click=${this._removeIgnoredIntegration}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.integrations.ignore.stop_ignore"
|
||||
)}
|
||||
></mwc-button>
|
||||
</ha-integration-action-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _removeIgnoredIntegration() {
|
||||
showConfirmationDialog(this, {
|
||||
title: this.hass!.localize(
|
||||
"ui.panel.config.integrations.ignore.confirm_delete_ignore_title",
|
||||
"name",
|
||||
this.hass.localize(`component.${this.entry.domain}.title`)
|
||||
),
|
||||
text: this.hass!.localize(
|
||||
"ui.panel.config.integrations.ignore.confirm_delete_ignore"
|
||||
),
|
||||
confirmText: this.hass!.localize(
|
||||
"ui.panel.config.integrations.ignore.stop_ignore"
|
||||
),
|
||||
confirm: async () => {
|
||||
const result = await deleteConfigEntry(this.hass, this.entry.entry_id);
|
||||
if (result.require_restart) {
|
||||
alert(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.restart_confirm"
|
||||
)
|
||||
);
|
||||
}
|
||||
fireEvent(this, "change", undefined, {
|
||||
bubbles: false,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
--state-color: var(--divider-color, #e0e0e0);
|
||||
}
|
||||
|
||||
mwc-button {
|
||||
--mdc-theme-primary: var(--primary-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-ignored-config-entry-card": HaIgnoredConfigEntryCard;
|
||||
}
|
||||
}
|
114
src/panels/config/integrations/ha-integration-action-card.ts
Normal file
114
src/panels/config/integrations/ha-integration-action-card.ts
Normal file
@ -0,0 +1,114 @@
|
||||
import {
|
||||
customElement,
|
||||
LitElement,
|
||||
property,
|
||||
CSSResult,
|
||||
css,
|
||||
} from "lit-element";
|
||||
import { TemplateResult, html } from "lit-html";
|
||||
import { IntegrationManifest } from "../../../data/integration";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { brandsUrl } from "../../../util/brands-url";
|
||||
import {
|
||||
haConfigIntegrationRenderIcons,
|
||||
haConfigIntegrationsStyles,
|
||||
} from "./ha-config-integrations-common";
|
||||
|
||||
@customElement("ha-integration-action-card")
|
||||
export class HaIntegrationActionCard extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public banner!: string;
|
||||
|
||||
@property() public domain!: string;
|
||||
|
||||
@property() public label!: string;
|
||||
|
||||
@property() public manifest?: IntegrationManifest;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-card outlined>
|
||||
<div class="banner">
|
||||
${this.banner}
|
||||
</div>
|
||||
<div class="content">
|
||||
${haConfigIntegrationRenderIcons(this.hass, this.manifest)}
|
||||
<div class="image">
|
||||
<img
|
||||
src=${brandsUrl(this.domain, "logo")}
|
||||
referrerpolicy="no-referrer"
|
||||
@error=${this._onImageError}
|
||||
@load=${this._onImageLoad}
|
||||
/>
|
||||
</div>
|
||||
<h2>${this.label}</h2>
|
||||
</div>
|
||||
<div class="actions"><slot></slot></div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private _onImageLoad(ev) {
|
||||
ev.target.style.visibility = "initial";
|
||||
}
|
||||
|
||||
private _onImageError(ev) {
|
||||
ev.target.style.visibility = "hidden";
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haConfigIntegrationsStyles,
|
||||
css`
|
||||
ha-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
--ha-card-border-color: var(--state-color);
|
||||
--mdc-theme-primary: var(--state-color);
|
||||
}
|
||||
.content {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
}
|
||||
.image {
|
||||
height: 60px;
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
}
|
||||
img {
|
||||
max-width: 90%;
|
||||
max-height: 100%;
|
||||
}
|
||||
h2 {
|
||||
text-align: center;
|
||||
margin: 16px 8px 0;
|
||||
}
|
||||
.attention {
|
||||
--state-color: var(--error-color);
|
||||
--text-on-state-color: var(--text-primary-color);
|
||||
}
|
||||
.discovered {
|
||||
--state-color: var(--primary-color);
|
||||
--text-on-state-color: var(--text-primary-color);
|
||||
}
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 6px 0;
|
||||
height: 48px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-integration-action-card": HaIntegrationActionCard;
|
||||
}
|
||||
}
|
@ -1,4 +1,8 @@
|
||||
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "@polymer/paper-listbox";
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-item";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import { mdiAlertCircle, mdiDotsVertical, mdiOpenInNew } from "@mdi/js";
|
||||
import {
|
||||
@ -14,7 +18,9 @@ import { classMap } from "lit-html/directives/class-map";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
|
||||
import "../../../components/ha-icon-next";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-card";
|
||||
import {
|
||||
ConfigEntry,
|
||||
deleteConfigEntry,
|
||||
@ -23,8 +29,8 @@ import {
|
||||
reloadConfigEntry,
|
||||
updateConfigEntry,
|
||||
} from "../../../data/config_entries";
|
||||
import { DeviceRegistryEntry } from "../../../data/device_registry";
|
||||
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||
import type { DeviceRegistryEntry } from "../../../data/device_registry";
|
||||
import type { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||
import { domainToName, IntegrationManifest } from "../../../data/integration";
|
||||
import { showConfigEntrySystemOptionsDialog } from "../../../dialogs/config-entry-system-options/show-dialog-config-entry-system-options";
|
||||
import { showOptionsFlowDialog } from "../../../dialogs/config-flow/show-dialog-options-flow";
|
||||
@ -37,48 +43,25 @@ import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { brandsUrl } from "../../../util/brands-url";
|
||||
import { ConfigEntryExtended } from "./ha-config-integrations";
|
||||
import {
|
||||
haConfigIntegrationRenderIcons,
|
||||
haConfigIntegrationsStyles,
|
||||
} from "./ha-config-integrations-common";
|
||||
|
||||
export interface ConfigEntryUpdatedEvent {
|
||||
entry: ConfigEntry;
|
||||
}
|
||||
|
||||
export interface ConfigEntryRemovedEvent {
|
||||
entryId: string;
|
||||
}
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"entry-updated": ConfigEntryUpdatedEvent;
|
||||
"entry-removed": ConfigEntryRemovedEvent;
|
||||
}
|
||||
}
|
||||
const ERROR_STATES: ConfigEntry["state"][] = [
|
||||
"failed_unload",
|
||||
"migration_error",
|
||||
"setup_error",
|
||||
"setup_retry",
|
||||
];
|
||||
|
||||
const integrationsWithPanel = {
|
||||
hassio: {
|
||||
buttonLocalizeKey: "ui.panel.config.hassio.button",
|
||||
path: "/hassio/dashboard",
|
||||
},
|
||||
mqtt: {
|
||||
buttonLocalizeKey: "ui.panel.config.mqtt.button",
|
||||
path: "/config/mqtt",
|
||||
},
|
||||
zha: {
|
||||
buttonLocalizeKey: "ui.panel.config.zha.button",
|
||||
path: "/config/zha/dashboard",
|
||||
},
|
||||
ozw: {
|
||||
buttonLocalizeKey: "ui.panel.config.ozw.button",
|
||||
path: "/config/ozw/dashboard",
|
||||
},
|
||||
zwave: {
|
||||
buttonLocalizeKey: "ui.panel.config.zwave.button",
|
||||
path: "/config/zwave",
|
||||
},
|
||||
zwave_js: {
|
||||
buttonLocalizeKey: "ui.panel.config.zwave_js.button",
|
||||
path: "/config/zwave_js/dashboard",
|
||||
},
|
||||
hassio: "/hassio/dashboard",
|
||||
mqtt: "/config/mqtt",
|
||||
zha: "/config/zha/dashboard",
|
||||
ozw: "/config/ozw/dashboard",
|
||||
zwave: "/config/zwave",
|
||||
zwave_js: "/config/zwave_js/dashboard",
|
||||
};
|
||||
|
||||
@customElement("ha-integration-card")
|
||||
@ -89,7 +72,7 @@ export class HaIntegrationCard extends LitElement {
|
||||
|
||||
@property() public items!: ConfigEntryExtended[];
|
||||
|
||||
@property() public manifest!: IntegrationManifest;
|
||||
@property() public manifest?: IntegrationManifest;
|
||||
|
||||
@property() public entityRegistryEntries!: EntityRegistryEntry[];
|
||||
|
||||
@ -99,291 +82,325 @@ export class HaIntegrationCard extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
let item = this._selectededConfigEntry;
|
||||
|
||||
if (this.items.length === 1) {
|
||||
return this._renderSingleEntry(this.items[0]);
|
||||
}
|
||||
if (this.selectedConfigEntryId) {
|
||||
const configEntry = this.items.find(
|
||||
item = this.items[0];
|
||||
} else if (this.selectedConfigEntryId) {
|
||||
item = this.items.find(
|
||||
(entry) => entry.entry_id === this.selectedConfigEntryId
|
||||
);
|
||||
if (configEntry) {
|
||||
return this._renderSingleEntry(configEntry);
|
||||
}
|
||||
}
|
||||
return this._renderGroupedIntegration();
|
||||
}
|
||||
|
||||
private _renderGroupedIntegration(): TemplateResult {
|
||||
let primary: string;
|
||||
let secondary: string | undefined;
|
||||
|
||||
if (item) {
|
||||
primary = item.title || item.localized_domain_name || this.domain;
|
||||
if (primary !== item.localized_domain_name) {
|
||||
secondary = item.localized_domain_name;
|
||||
}
|
||||
} else {
|
||||
primary = domainToName(this.hass.localize, this.domain, this.manifest);
|
||||
}
|
||||
|
||||
const hasItem = item !== undefined;
|
||||
|
||||
return html`
|
||||
<ha-card outlined class="group ${classMap({ disabled: this.disabled })}">
|
||||
<ha-card
|
||||
outlined
|
||||
class="${classMap({
|
||||
single: hasItem,
|
||||
group: !hasItem,
|
||||
hasMultiple: this.items.length > 1,
|
||||
disabled: this.disabled,
|
||||
"state-not-loaded": hasItem && item!.state === "not_loaded",
|
||||
"state-error": hasItem && ERROR_STATES.includes(item!.state),
|
||||
})}"
|
||||
.configEntry=${item}
|
||||
>
|
||||
${this.disabled
|
||||
? html`<div class="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.disable.disabled"
|
||||
)}
|
||||
</div>`
|
||||
? html`
|
||||
<div class="banner">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.disable.disabled"
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<div class="group-header">
|
||||
${this.items.length > 1
|
||||
? html`
|
||||
<div class="back-btn">
|
||||
<ha-icon-button
|
||||
icon="hass:chevron-left"
|
||||
@click=${this._back}
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<div class="header">
|
||||
<img
|
||||
src=${brandsUrl(this.domain, "icon")}
|
||||
referrerpolicy="no-referrer"
|
||||
@error=${this._onImageError}
|
||||
@load=${this._onImageLoad}
|
||||
/>
|
||||
<h2>
|
||||
${domainToName(this.hass.localize, this.domain)}
|
||||
</h2>
|
||||
<div class="info">
|
||||
<div class="primary">${primary}</div>
|
||||
${secondary ? html`<div class="secondary">${secondary}</div>` : ""}
|
||||
</div>
|
||||
${haConfigIntegrationRenderIcons(this.hass, this.manifest)}
|
||||
</div>
|
||||
<paper-listbox>
|
||||
${this.items.map(
|
||||
(item) =>
|
||||
html`<paper-item
|
||||
.entryId=${item.entry_id}
|
||||
@click=${this._selectConfigEntry}
|
||||
><paper-item-body
|
||||
>${item.title ||
|
||||
this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.unnamed_entry"
|
||||
)}</paper-item-body
|
||||
>
|
||||
${item.state === "not_loaded"
|
||||
? html`<span>
|
||||
<ha-svg-icon
|
||||
class="error"
|
||||
.path=${mdiAlertCircle}
|
||||
></ha-svg-icon
|
||||
><paper-tooltip animation-delay="0" position="left">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.not_loaded",
|
||||
"logs_link",
|
||||
this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.logs"
|
||||
)
|
||||
)}
|
||||
</paper-tooltip>
|
||||
</span>`
|
||||
: ""}
|
||||
<ha-icon-next></ha-icon-next>
|
||||
</paper-item>`
|
||||
)}
|
||||
</paper-listbox>
|
||||
${item
|
||||
? this._renderSingleEntry(item)
|
||||
: this._renderGroupedIntegration()}
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderGroupedIntegration(): TemplateResult {
|
||||
return html`
|
||||
<paper-listbox>
|
||||
${this.items.map(
|
||||
(item) =>
|
||||
html`<paper-item
|
||||
.entryId=${item.entry_id}
|
||||
@click=${this._selectConfigEntry}
|
||||
><paper-item-body
|
||||
>${item.title ||
|
||||
this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.unnamed_entry"
|
||||
)}</paper-item-body
|
||||
>
|
||||
${ERROR_STATES.includes(item.state)
|
||||
? html`<span>
|
||||
<ha-svg-icon
|
||||
class="error"
|
||||
.path=${mdiAlertCircle}
|
||||
></ha-svg-icon
|
||||
><paper-tooltip animation-delay="0" position="left">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.state.${item.state}`
|
||||
)}
|
||||
</paper-tooltip>
|
||||
</span>`
|
||||
: ""}
|
||||
<ha-icon-next></ha-icon-next>
|
||||
</paper-item>`
|
||||
)}
|
||||
</paper-listbox>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderSingleEntry(item: ConfigEntryExtended): TemplateResult {
|
||||
const devices = this._getDevices(item);
|
||||
const services = this._getServices(item);
|
||||
const entities = this._getEntities(item);
|
||||
|
||||
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}`,
|
||||
];
|
||||
stateTextExtra = html`
|
||||
<br />
|
||||
<a href="/config/logs"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.check_the_logs"
|
||||
)}</a
|
||||
>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-card
|
||||
outlined
|
||||
class="single integration ${classMap({
|
||||
disabled: Boolean(item.disabled_by),
|
||||
"not-loaded": !item.disabled_by && item.state === "not_loaded",
|
||||
})}"
|
||||
.configEntry=${item}
|
||||
.id=${item.entry_id}
|
||||
>
|
||||
${this.items.length > 1
|
||||
? html`<ha-icon-button
|
||||
class="back-btn"
|
||||
icon="hass:chevron-left"
|
||||
@click=${this._back}
|
||||
></ha-icon-button>`
|
||||
<div class="content">
|
||||
${stateText
|
||||
? html`
|
||||
<div class="message">
|
||||
${this.hass.localize(...stateText)}${stateTextExtra}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${item.disabled_by
|
||||
? html`<div class="header">
|
||||
${this.hass.localize(
|
||||
"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
|
||||
)}
|
||||
</div>`
|
||||
: item.state === "not_loaded"
|
||||
? html`<div class="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.not_loaded",
|
||||
"logs_link",
|
||||
html`<a href="/config/logs"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.logs"
|
||||
)}</a
|
||||
>`
|
||||
)}
|
||||
</div>`
|
||||
${devices.length || services.length || entities.length
|
||||
? html`
|
||||
<div>
|
||||
${devices.length
|
||||
? html`
|
||||
<a
|
||||
href=${`/config/devices/dashboard?historyBack=1&config_entry=${item.entry_id}`}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.devices",
|
||||
"count",
|
||||
devices.length
|
||||
)}</a
|
||||
>${services.length ? "," : ""}
|
||||
`
|
||||
: ""}
|
||||
${services.length
|
||||
? html`
|
||||
<a
|
||||
href=${`/config/devices/dashboard?historyBack=1&config_entry=${item.entry_id}`}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.services",
|
||||
"count",
|
||||
services.length
|
||||
)}</a
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
${(devices.length || services.length) && entities.length
|
||||
? this.hass.localize("ui.common.and")
|
||||
: ""}
|
||||
${entities.length
|
||||
? 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
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<div class="card-content">
|
||||
<div class="image">
|
||||
<img
|
||||
src=${brandsUrl(item.domain, "logo")}
|
||||
referrerpolicy="no-referrer"
|
||||
@error=${this._onImageError}
|
||||
@load=${this._onImageLoad}
|
||||
/>
|
||||
</div>
|
||||
<h2>
|
||||
${item.localized_domain_name}
|
||||
</h2>
|
||||
<h3>
|
||||
${item.localized_domain_name === item.title ? "" : item.title}
|
||||
</h3>
|
||||
${devices.length || services.length || entities.length
|
||||
</div>
|
||||
<div class="actions">
|
||||
<div>
|
||||
${item.disabled_by === "user"
|
||||
? html`<mwc-button unelevated @click=${this._handleEnable}>
|
||||
${this.hass.localize("ui.common.enable")}
|
||||
</mwc-button>`
|
||||
: item.domain in integrationsWithPanel
|
||||
? html`<a
|
||||
href=${`${
|
||||
integrationsWithPanel[item.domain].path
|
||||
}?config_entry=${item.entry_id}`}
|
||||
><mwc-button>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.configure"
|
||||
)}
|
||||
</mwc-button></a
|
||||
>`
|
||||
: item.supports_options
|
||||
? html`
|
||||
<div>
|
||||
${devices.length
|
||||
? html`
|
||||
<a
|
||||
href=${`/config/devices/dashboard?historyBack=1&config_entry=${item.entry_id}`}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.devices",
|
||||
"count",
|
||||
devices.length
|
||||
)}</a
|
||||
>${services.length ? "," : ""}
|
||||
`
|
||||
: ""}
|
||||
${services.length
|
||||
? html`
|
||||
<a
|
||||
href=${`/config/devices/dashboard?historyBack=1&config_entry=${item.entry_id}`}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.services",
|
||||
"count",
|
||||
services.length
|
||||
)}</a
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
${(devices.length || services.length) && entities.length
|
||||
? this.hass.localize("ui.common.and")
|
||||
: ""}
|
||||
${entities.length
|
||||
? 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
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<mwc-button @click=${this._showOptions}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.configure"
|
||||
)}
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<div>
|
||||
${item.disabled_by === "user"
|
||||
? html`<mwc-button unelevated @click=${this._handleEnable}>
|
||||
${this.hass.localize("ui.common.enable")}
|
||||
</mwc-button>`
|
||||
: ""}
|
||||
<mwc-button @click=${this._editEntryName}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.rename"
|
||||
)}
|
||||
</mwc-button>
|
||||
${item.domain in integrationsWithPanel
|
||||
? html`<a
|
||||
href=${`${
|
||||
integrationsWithPanel[item.domain].path
|
||||
}?config_entry=${item.entry_id}`}
|
||||
><mwc-button>
|
||||
${!this.manifest
|
||||
? ""
|
||||
: html`
|
||||
<ha-button-menu corner="BOTTOM_START">
|
||||
<mwc-icon-button
|
||||
.title=${this.hass.localize("ui.common.menu")}
|
||||
.label=${this.hass.localize("ui.common.overflow_menu")}
|
||||
slot="trigger"
|
||||
>
|
||||
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<mwc-list-item @request-selected="${this._editEntryName}">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.rename"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
<mwc-list-item @request-selected="${this._handleSystemOptions}">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.system_options"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
|
||||
<a
|
||||
href=${this.manifest.documentation}
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<mwc-list-item hasMeta>
|
||||
${this.hass.localize(
|
||||
integrationsWithPanel[item.domain].buttonLocalizeKey
|
||||
)}
|
||||
</mwc-button></a
|
||||
>`
|
||||
: item.supports_options
|
||||
? html`
|
||||
<mwc-button @click=${this._showOptions}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.options"
|
||||
)}
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<ha-button-menu corner="BOTTOM_START">
|
||||
<mwc-icon-button
|
||||
.title=${this.hass.localize("ui.common.menu")}
|
||||
.label=${this.hass.localize("ui.common.overflow_menu")}
|
||||
slot="trigger"
|
||||
>
|
||||
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<mwc-list-item @request-selected="${this._handleSystemOptions}">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.system_options"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
${!this.manifest
|
||||
? ""
|
||||
: html`
|
||||
<a
|
||||
href=${this.manifest.documentation}
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<mwc-list-item hasMeta>
|
||||
"ui.panel.config.integrations.config_entry.documentation"
|
||||
)}<ha-svg-icon
|
||||
slot="meta"
|
||||
.path=${mdiOpenInNew}
|
||||
></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
</a>
|
||||
${!item.disabled_by &&
|
||||
item.state === "loaded" &&
|
||||
item.supports_unload &&
|
||||
item.source !== "system"
|
||||
? html`<mwc-list-item
|
||||
@request-selected="${this._handleReload}"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.documentation"
|
||||
)}<ha-svg-icon
|
||||
slot="meta"
|
||||
.path=${mdiOpenInNew}
|
||||
></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
</a>
|
||||
`}
|
||||
${!item.disabled_by &&
|
||||
item.state === "loaded" &&
|
||||
item.supports_unload &&
|
||||
item.source !== "system"
|
||||
? html`<mwc-list-item @request-selected="${this._handleReload}">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.reload"
|
||||
)}
|
||||
</mwc-list-item>`
|
||||
: ""}
|
||||
${item.disabled_by === "user"
|
||||
? html`<mwc-list-item @request-selected="${this._handleEnable}">
|
||||
${this.hass.localize("ui.common.enable")}
|
||||
</mwc-list-item>`
|
||||
: item.source !== "system"
|
||||
? html`<mwc-list-item
|
||||
class="warning"
|
||||
@request-selected="${this._handleDisable}"
|
||||
>
|
||||
${this.hass.localize("ui.common.disable")}
|
||||
</mwc-list-item>`
|
||||
: ""}
|
||||
${item.source !== "system"
|
||||
? html`<mwc-list-item
|
||||
class="warning"
|
||||
@request-selected="${this._handleDelete}"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.delete"
|
||||
)}
|
||||
</mwc-list-item>`
|
||||
: ""}
|
||||
</ha-button-menu>
|
||||
</div>
|
||||
</ha-card>
|
||||
"ui.panel.config.integrations.config_entry.reload"
|
||||
)}
|
||||
</mwc-list-item>`
|
||||
: ""}
|
||||
${item.disabled_by === "user"
|
||||
? html`<mwc-list-item
|
||||
@request-selected="${this._handleEnable}"
|
||||
>
|
||||
${this.hass.localize("ui.common.enable")}
|
||||
</mwc-list-item>`
|
||||
: item.source !== "system"
|
||||
? html`<mwc-list-item
|
||||
class="warning"
|
||||
@request-selected="${this._handleDisable}"
|
||||
>
|
||||
${this.hass.localize("ui.common.disable")}
|
||||
</mwc-list-item>`
|
||||
: ""}
|
||||
${item.source !== "system"
|
||||
? html`<mwc-list-item
|
||||
class="warning"
|
||||
@request-selected="${this._handleDelete}"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.delete"
|
||||
)}
|
||||
</mwc-list-item>`
|
||||
: ""}
|
||||
</ha-button-menu>
|
||||
`}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private get _selectededConfigEntry(): ConfigEntryExtended | undefined {
|
||||
return this.items.length === 1
|
||||
? this.items[0]
|
||||
: this.selectedConfigEntryId
|
||||
? this.items.find(
|
||||
(entry) => entry.entry_id === this.selectedConfigEntryId
|
||||
)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
private _selectConfigEntry(ev: Event) {
|
||||
this.selectedConfigEntryId = (ev.currentTarget as any).entryId;
|
||||
}
|
||||
@ -588,109 +605,109 @@ export class HaIntegrationCard extends LitElement {
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
haConfigIntegrationsStyles,
|
||||
css`
|
||||
:host {
|
||||
max-width: 500px;
|
||||
}
|
||||
ha-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
--state-color: var(--divider-color, #e0e0e0);
|
||||
--ha-card-border-color: var(--state-color);
|
||||
--state-message-color: var(--state-color);
|
||||
}
|
||||
ha-card.single {
|
||||
justify-content: space-between;
|
||||
.state-error {
|
||||
--state-color: var(--error-color);
|
||||
--text-on-state-color: var(--text-primary-color);
|
||||
}
|
||||
.state-not-loaded {
|
||||
--state-message-color: var(--primary-text-color);
|
||||
}
|
||||
:host(.highlight) ha-card {
|
||||
border: 1px solid var(--accent-color);
|
||||
--state-color: var(--accent-color);
|
||||
--text-on-state-color: var(--text-primary-color);
|
||||
}
|
||||
.disabled {
|
||||
--ha-card-border-color: var(--warning-color);
|
||||
ha-card.group {
|
||||
max-height: 200px;
|
||||
}
|
||||
.not-loaded {
|
||||
--ha-card-border-color: var(--error-color);
|
||||
|
||||
.back-btn {
|
||||
background-color: var(--state-color);
|
||||
color: var(--text-on-state-color);
|
||||
--mdc-icon-button-size: 32px;
|
||||
transition: height 0.1s;
|
||||
overflow: hidden;
|
||||
}
|
||||
.hasMultiple.single .back-btn {
|
||||
height: 32px;
|
||||
}
|
||||
.hasMultiple.group .back-btn {
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
.disabled .header {
|
||||
background: var(--warning-color);
|
||||
color: var(--text-primary-color);
|
||||
}
|
||||
.not-loaded .header {
|
||||
background: var(--error-color);
|
||||
color: var(--text-primary-color);
|
||||
}
|
||||
.not-loaded .header a {
|
||||
color: var(--text-primary-color);
|
||||
}
|
||||
.card-content {
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
ha-card.integration .card-content {
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
.card-actions {
|
||||
border-top: none;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
padding-right: 5px;
|
||||
padding: 16px 8px 8px 16px;
|
||||
}
|
||||
.group-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.group.disabled .header {
|
||||
padding-top: 8px;
|
||||
}
|
||||
.header img {
|
||||
margin-right: 16px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
padding: 16px 16px 8px 16px;
|
||||
justify-content: center;
|
||||
}
|
||||
.group-header h1 {
|
||||
margin: 0;
|
||||
}
|
||||
.group-header img {
|
||||
margin-right: 8px;
|
||||
}
|
||||
.image {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 60px;
|
||||
margin-bottom: 16px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
img {
|
||||
max-height: 100%;
|
||||
max-width: 90%;
|
||||
}
|
||||
.none-found {
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
}
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
h1 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
h2 {
|
||||
min-height: 24px;
|
||||
}
|
||||
h3 {
|
||||
.header .info div,
|
||||
paper-item-body {
|
||||
word-wrap: break-word;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.primary {
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.secondary {
|
||||
font-size: 14px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.message {
|
||||
font-weight: bold;
|
||||
padding-bottom: 16px;
|
||||
color: var(--state-message-color);
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
padding: 0px 16px 0 72px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 0 0 8px;
|
||||
height: 48px;
|
||||
}
|
||||
.actions a {
|
||||
text-decoration: none;
|
||||
}
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
ha-button-menu {
|
||||
color: var(--secondary-text-color);
|
||||
--mdc-menu-min-width: 200px;
|
||||
}
|
||||
@media (min-width: 563px) {
|
||||
paper-listbox {
|
||||
max-height: 150px;
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
@ -701,11 +718,6 @@ export class HaIntegrationCard extends LitElement {
|
||||
mwc-list-item ha-svg-icon {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.back-btn {
|
||||
position: absolute;
|
||||
background: rgba(var(--rgb-card-background-color), 0.6);
|
||||
border-radius: 50%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -177,7 +177,7 @@ class ZHADeviceCard extends SubscribeMixin(LitElement) {
|
||||
});
|
||||
}
|
||||
|
||||
private _computeEntityName(entity: EntityRegistryEntry): string {
|
||||
private _computeEntityName(entity: EntityRegistryEntry): string | null {
|
||||
if (this.hass.states[entity.entity_id]) {
|
||||
return computeStateName(this.hass.states[entity.entity_id]);
|
||||
}
|
||||
|
@ -2142,7 +2142,7 @@
|
||||
"entities": "{count} {count, plural,\n one {entity}\n other {entities}\n}",
|
||||
"services": "{count} {count, plural,\n one {service}\n other {services}\n}",
|
||||
"rename": "Rename",
|
||||
"options": "Options",
|
||||
"configure": "Configure",
|
||||
"system_options": "System options",
|
||||
"documentation": "Documentation",
|
||||
"delete": "Delete",
|
||||
@ -2161,8 +2161,8 @@
|
||||
"entity_unavailable": "Entity unavailable",
|
||||
"area": "In {area}",
|
||||
"no_area": "No Area",
|
||||
"not_loaded": "Not loaded, check the {logs_link}",
|
||||
"logs": "logs",
|
||||
"not_loaded": "Not loaded",
|
||||
"check_the_logs": "Check the logs",
|
||||
"disable": {
|
||||
"disabled": "Disabled",
|
||||
"disabled_cause": "Disabled by {cause}",
|
||||
@ -2172,6 +2172,16 @@
|
||||
"device": "device"
|
||||
},
|
||||
"disable_confirm": "Are you sure you want to disable this config entry? Its devices and entities will be disabled."
|
||||
},
|
||||
"provided_by_custom_component": "Provided by a custom component",
|
||||
"depends_on_cloud": "Depends on the cloud",
|
||||
"state": {
|
||||
"loaded": "Not loaded",
|
||||
"setup_error": "Failed to set up",
|
||||
"migration_error": "Migration error",
|
||||
"setup_retry": "Retrying to set up",
|
||||
"not_loaded": "Not loaded",
|
||||
"failed_unload": "Failed to unload"
|
||||
}
|
||||
},
|
||||
"config_flow": {
|
||||
@ -2243,11 +2253,7 @@
|
||||
"create": "Create"
|
||||
}
|
||||
},
|
||||
"hassio": {
|
||||
"button": "Configure"
|
||||
},
|
||||
"mqtt": {
|
||||
"button": "Configure",
|
||||
"title": "MQTT",
|
||||
"description_publish": "Publish a packet",
|
||||
"topic": "topic",
|
||||
@ -2261,7 +2267,6 @@
|
||||
"message_received": "Message {id} received on {topic} at {time}:"
|
||||
},
|
||||
"ozw": {
|
||||
"button": "Configure",
|
||||
"common": {
|
||||
"zwave": "Z-Wave",
|
||||
"node_id": "Node ID",
|
||||
@ -2376,7 +2381,6 @@
|
||||
}
|
||||
},
|
||||
"zha": {
|
||||
"button": "Configure",
|
||||
"common": {
|
||||
"clusters": "Clusters",
|
||||
"manufacturer_code_override": "Manufacturer Code Override",
|
||||
@ -2464,7 +2468,6 @@
|
||||
}
|
||||
},
|
||||
"zwave": {
|
||||
"button": "Configure",
|
||||
"description": "Manage your Z-Wave network",
|
||||
"learn_more": "Learn more about Z-Wave",
|
||||
"common": {
|
||||
@ -2555,7 +2558,6 @@
|
||||
}
|
||||
},
|
||||
"zwave_js": {
|
||||
"button": "Configure",
|
||||
"navigation": {
|
||||
"network": "Network"
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user