mirror of
https://github.com/home-assistant/frontend.git
synced 2026-08-18 20:50:04 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9fd639bb7 | ||
|
|
2925dfc14e |
@@ -25,6 +25,7 @@ export type LocalizeKeys =
|
||||
| `ui.dialogs.unsupported.reasons.${string}`
|
||||
| `ui.panel.config.${string}.${"caption" | "description"}`
|
||||
| `ui.panel.config.dashboard.${string}`
|
||||
| `ui.panel.config.mqtt.${string}`
|
||||
| `ui.panel.config.storage.segments.${string}`
|
||||
| `ui.panel.config.zha.${string}`
|
||||
| `ui.panel.config.zwave_js.${string}`
|
||||
|
||||
@@ -33,6 +33,7 @@ import { fireEvent } from "../common/dom/fire_event";
|
||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||
import { getEntityContext } from "../common/entity/context/get_entity_context";
|
||||
import { computeDeviceName } from "../common/entity/compute_device_name";
|
||||
import { computeEntityName } from "../common/entity/compute_entity_name";
|
||||
import { computeAreaName } from "../common/entity/compute_area_name";
|
||||
import { computeFloorName } from "../common/entity/compute_floor_name";
|
||||
import { copyToClipboard } from "../common/util/copy-clipboard";
|
||||
@@ -752,6 +753,19 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
this._states![key]
|
||||
);
|
||||
|
||||
const entityName = computeEntityName(
|
||||
this._states![key],
|
||||
this._registries!.entities,
|
||||
this._registries!.devices
|
||||
);
|
||||
const deviceName = context.device
|
||||
? computeDeviceName(context.device)
|
||||
: undefined;
|
||||
const areaName = context.area ? computeAreaName(context.area) : undefined;
|
||||
const floorName = context.floor
|
||||
? computeFloorName(context.floor)
|
||||
: undefined;
|
||||
|
||||
const completionItems: CompletionItem[] = [
|
||||
{
|
||||
label: this._i18n!.localize(
|
||||
@@ -759,31 +773,39 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
),
|
||||
value: formattedState,
|
||||
subValue:
|
||||
// If the state exactly matches the formatted state, don't show the raw state
|
||||
this._states![key].state === formattedState
|
||||
? undefined
|
||||
: this._states![key].state,
|
||||
},
|
||||
];
|
||||
|
||||
if (context.device && context.device.name) {
|
||||
if (entityName) {
|
||||
completionItems.push({
|
||||
label: this._i18n!.localize(
|
||||
"ui.components.entity.entity-picker.entity"
|
||||
),
|
||||
value: entityName,
|
||||
});
|
||||
}
|
||||
|
||||
if (deviceName) {
|
||||
completionItems.push({
|
||||
label: this._i18n!.localize("ui.components.device-picker.device"),
|
||||
value: context.device.name,
|
||||
value: deviceName,
|
||||
});
|
||||
}
|
||||
|
||||
if (context.area && context.area.name) {
|
||||
if (areaName) {
|
||||
completionItems.push({
|
||||
label: this._i18n!.localize("ui.components.area-picker.area"),
|
||||
value: context.area.name,
|
||||
value: areaName,
|
||||
});
|
||||
}
|
||||
|
||||
if (context.floor && context.floor.name) {
|
||||
if (floorName) {
|
||||
completionItems.push({
|
||||
label: this._i18n!.localize("ui.components.floor-picker.floor"),
|
||||
value: context.floor.name,
|
||||
value: floorName,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@ const CUSTOM_ICONS: Record<string, () => Promise<string>> = {
|
||||
import("../resources/esphome-logo-svg").then((mod) => mod.mdiEsphomeLogo),
|
||||
matter: () =>
|
||||
import("../resources/matter-logo-svg").then((mod) => mod.mdiMatterLogo),
|
||||
mqtt: () =>
|
||||
import("../resources/mqtt-logo-svg").then((mod) => mod.mdiMqttLogo),
|
||||
};
|
||||
|
||||
@customElement("ha-icon")
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
import memoizeOne from "memoize-one";
|
||||
import type { PageNavigation } from "../../layouts/hass-tabs-subpage";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { mdiMqttLogo } from "../../resources/mqtt-logo-svg";
|
||||
|
||||
const getHasDomainCheck = (domain: string) => {
|
||||
const prefix = `${domain}.`;
|
||||
@@ -142,6 +143,14 @@ export const configSections: Record<string, PageNavigation[]> = {
|
||||
translationKey: "knx",
|
||||
adminOnly: true,
|
||||
},
|
||||
{
|
||||
path: "/config/mqtt",
|
||||
iconPath: mdiMqttLogo,
|
||||
iconColor: "#660066",
|
||||
component: "mqtt",
|
||||
translationKey: "mqtt",
|
||||
adminOnly: true,
|
||||
},
|
||||
{
|
||||
path: "/config/thread",
|
||||
iconPath:
|
||||
|
||||
@@ -1,14 +1,27 @@
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import {
|
||||
mdiAlertCircleOutline,
|
||||
mdiCheck,
|
||||
mdiDevices,
|
||||
mdiShape,
|
||||
mdiTune,
|
||||
} from "@mdi/js";
|
||||
import { storage } from "../../../../../common/decorators/storage";
|
||||
import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-code-editor";
|
||||
import "../../../../../components/ha-formfield";
|
||||
import "../../../../../components/ha-icon-next";
|
||||
import "../../../../../components/ha-md-list";
|
||||
import "../../../../../components/ha-md-list-item";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import type { HaSelectSelectEvent } from "../../../../../components/ha-select";
|
||||
import "../../../../../components/ha-switch";
|
||||
import "../../../../../components/input/ha-input";
|
||||
import type { ConfigEntry } from "../../../../../data/config_entries";
|
||||
import { getConfigEntries } from "../../../../../data/config_entries";
|
||||
import type { Action } from "../../../../../data/script";
|
||||
import { callExecuteScript } from "../../../../../data/service";
|
||||
@@ -18,6 +31,10 @@ import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import { showToast } from "../../../../../util/toast";
|
||||
import "./mqtt-subscribe-card";
|
||||
import { brandsUrl } from "../../../../../util/brands-url";
|
||||
import { showConfigFlowDialog } from "../../../../../dialogs/config-flow/show-dialog-config-flow";
|
||||
import { fetchIntegrationManifest } from "../../../../../data/integration";
|
||||
import { mdiMqttLogo } from "../../../../../resources/mqtt-logo-svg";
|
||||
|
||||
const qosLevel = ["0", "1", "2"];
|
||||
|
||||
@@ -59,78 +76,240 @@ export class MQTTConfigPanel extends LitElement {
|
||||
})
|
||||
private _retain = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
@state() private _configEntry?: ConfigEntry;
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues<this>) {
|
||||
super.firstUpdated(changedProperties);
|
||||
if (this.hass) {
|
||||
this._fetchConfigEntry();
|
||||
}
|
||||
}
|
||||
|
||||
private _MQTTDeviceIds = memoizeOne(
|
||||
(
|
||||
devices: HomeAssistant["devices"],
|
||||
configEntryId?: string
|
||||
): Set<string> => {
|
||||
if (!configEntryId) {
|
||||
return new Set();
|
||||
}
|
||||
return new Set(
|
||||
Object.values(devices)
|
||||
.filter((device) => device.config_entries.includes(configEntryId))
|
||||
.map((device) => device.id)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
private _entityCount = memoizeOne(
|
||||
(entities: HomeAssistant["entities"], deviceIds: Set<string>): number =>
|
||||
Object.values(entities).filter(
|
||||
(entity) => entity.device_id && deviceIds.has(entity.device_id)
|
||||
).length
|
||||
);
|
||||
|
||||
protected render(): TemplateResult | typeof nothing {
|
||||
if (!this._configEntry) {
|
||||
return nothing;
|
||||
}
|
||||
const isOnline = this._configEntry.state === "loaded";
|
||||
const deviceIds = this._MQTTDeviceIds(
|
||||
this.hass.devices,
|
||||
this._configEntry.entry_id
|
||||
);
|
||||
const entityCount = this._entityCount(this.hass.entities, deviceIds);
|
||||
|
||||
return html`
|
||||
<hass-subpage
|
||||
.narrow=${this.narrow}
|
||||
.hass=${this.hass}
|
||||
header="MQTT"
|
||||
back-path="/config/integrations/integration/mqtt"
|
||||
has-fab
|
||||
>
|
||||
<div class="content">
|
||||
<ha-card
|
||||
.header=${this.hass.localize("ui.panel.config.mqtt.settings_title")}
|
||||
>
|
||||
<div class="card-actions">
|
||||
<ha-button appearance="plain" @click=${this._openOptionFlow}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.mqtt.option_flow"
|
||||
)}</ha-button
|
||||
>
|
||||
</div>
|
||||
</ha-card>
|
||||
<ha-card
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.mqtt.description_publish"
|
||||
)}
|
||||
>
|
||||
<div class="card-content">
|
||||
<div class="panel-dev-mqtt-fields">
|
||||
<ha-input
|
||||
.label=${this.hass.localize("ui.panel.config.mqtt.topic")}
|
||||
.value=${this._topic}
|
||||
@change=${this._handleTopic}
|
||||
></ha-input>
|
||||
<ha-select
|
||||
.label=${this.hass.localize("ui.panel.config.mqtt.qos")}
|
||||
.value=${this._qos}
|
||||
@selected=${this._handleQos}
|
||||
.options=${qosLevel}
|
||||
>
|
||||
</ha-select>
|
||||
<ha-formfield
|
||||
label=${this.hass!.localize("ui.panel.config.mqtt.retain")}
|
||||
>
|
||||
<ha-switch
|
||||
@change=${this._handleRetain}
|
||||
.checked=${this._retain}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
<p>${this.hass.localize("ui.panel.config.mqtt.payload")}</p>
|
||||
<ha-code-editor
|
||||
mode="jinja2"
|
||||
autocomplete-entities
|
||||
autocomplete-icons
|
||||
.value=${this._payload}
|
||||
@value-changed=${this._handlePayload}
|
||||
dir="ltr"
|
||||
></ha-code-editor>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-button appearance="plain" @click=${this._publish}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.mqtt.publish"
|
||||
)}</ha-button
|
||||
>
|
||||
</div>
|
||||
</ha-card>
|
||||
|
||||
<mqtt-subscribe-card .hass=${this.hass}></mqtt-subscribe-card>
|
||||
<div class="container">
|
||||
${this._renderNetworkStatus(isOnline, deviceIds.size)}
|
||||
${this._renderMyNetworkCard(deviceIds.size, entityCount)}
|
||||
${this._renderNavigationCard()} ${this._renderPublishCard()}
|
||||
<mqtt-subscribe-card .hass=${this.hass}></mqtt-subscribe-card>
|
||||
</div>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderNetworkStatus(isOnline: boolean, deviceCount: number) {
|
||||
return html`
|
||||
<ha-card class="content network-status">
|
||||
<div class="card-content">
|
||||
<div class="heading">
|
||||
<div class="icon ${isOnline ? "success" : "error"}">
|
||||
<ha-svg-icon
|
||||
.path=${isOnline ? mdiCheck : mdiAlertCircleOutline}
|
||||
></ha-svg-icon>
|
||||
</div>
|
||||
<div class="details">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.mqtt.status_${isOnline ? "online" : "offline"}`
|
||||
)}<br />
|
||||
<small>
|
||||
${this.hass.localize("ui.panel.config.mqtt.devices", {
|
||||
count: deviceCount,
|
||||
})}
|
||||
</small>
|
||||
</div>
|
||||
<img
|
||||
class="logo"
|
||||
alt="MQTT"
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
src=${brandsUrl(
|
||||
{
|
||||
domain: "mqtt",
|
||||
type: "icon",
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
},
|
||||
this.hass.auth.data.hassUrl
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderMyNetworkCard(deviceCount: number, entityCount: number) {
|
||||
return html`
|
||||
<ha-card class="nav-card">
|
||||
<div class="card-header">
|
||||
${this.hass.localize("ui.panel.config.mqtt.my_network_title")}
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<ha-md-list>
|
||||
<ha-md-list-item
|
||||
type="link"
|
||||
href=${`/config/devices/dashboard?historyBack=1&config_entry=${this._configEntry?.entry_id}`}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiDevices}></ha-svg-icon>
|
||||
<div slot="headline">
|
||||
${this.hass.localize("ui.panel.config.mqtt.device_count", {
|
||||
count: deviceCount,
|
||||
})}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item
|
||||
type="link"
|
||||
href=${`/config/entities/dashboard?historyBack=1&config_entry=${this._configEntry?.entry_id}`}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiShape}></ha-svg-icon>
|
||||
<div slot="headline">
|
||||
${this.hass.localize("ui.panel.config.mqtt.entity_count", {
|
||||
count: entityCount,
|
||||
})}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderNavigationCard() {
|
||||
return html`
|
||||
<ha-card class="nav-card">
|
||||
<div class="card-content">
|
||||
<ha-md-list>
|
||||
<ha-md-list-item type="link" @click=${this._openOptionFlow}>
|
||||
<ha-svg-icon slot="start" .path=${mdiTune}></ha-svg-icon>
|
||||
<div slot="headline">
|
||||
${this.hass.localize("ui.panel.config.mqtt.option_flow")}
|
||||
</div>
|
||||
<div slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.mqtt.option_flow_description"
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item type="link" @click=${this._openConfigFlow}>
|
||||
<ha-svg-icon slot="start" .path=${mdiMqttLogo}></ha-svg-icon>
|
||||
<div slot="headline">
|
||||
${this.hass.localize("ui.panel.config.mqtt.config_flow")}
|
||||
</div>
|
||||
<div slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.mqtt.config_flow_description"
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderPublishCard() {
|
||||
return html`
|
||||
<ha-card
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.mqtt.description_publish"
|
||||
)}
|
||||
>
|
||||
<div class="card-content">
|
||||
<div class="panel-dev-mqtt-fields">
|
||||
<ha-input
|
||||
.label=${this.hass.localize("ui.panel.config.mqtt.topic")}
|
||||
.value=${this._topic}
|
||||
@change=${this._handleTopic}
|
||||
></ha-input>
|
||||
<ha-select
|
||||
.label=${this.hass.localize("ui.panel.config.mqtt.qos")}
|
||||
.value=${this._qos}
|
||||
@selected=${this._handleQos}
|
||||
.options=${qosLevel}
|
||||
>
|
||||
</ha-select>
|
||||
<ha-formfield
|
||||
label=${this.hass!.localize("ui.panel.config.mqtt.retain")}
|
||||
>
|
||||
<ha-switch
|
||||
@change=${this._handleRetain}
|
||||
.checked=${this._retain}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
<p>${this.hass.localize("ui.panel.config.mqtt.payload")}</p>
|
||||
<ha-code-editor
|
||||
mode="jinja2"
|
||||
autocomplete-entities
|
||||
autocomplete-icons
|
||||
.value=${this._payload}
|
||||
@value-changed=${this._handlePayload}
|
||||
dir="ltr"
|
||||
></ha-code-editor>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-button appearance="plain" @click=${this._publish}
|
||||
>${this.hass.localize("ui.panel.config.mqtt.publish")}</ha-button
|
||||
>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _fetchConfigEntry(): Promise<void> {
|
||||
const configEntries = await getConfigEntries(this.hass, {
|
||||
domain: "mqtt",
|
||||
});
|
||||
this._configEntry = configEntries.find(
|
||||
(entry) => entry.disabled_by === null && entry.source !== "ignore"
|
||||
);
|
||||
}
|
||||
|
||||
private _handleTopic(ev: InputEvent) {
|
||||
this._topic = (ev.target as HTMLInputElement).value;
|
||||
}
|
||||
@@ -175,19 +354,23 @@ export class MQTTConfigPanel extends LitElement {
|
||||
}
|
||||
|
||||
private async _openOptionFlow() {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
if (!searchParams.has("config_entry")) {
|
||||
showOptionsFlowDialog(this, this._configEntry!);
|
||||
}
|
||||
|
||||
private _openConfigFlow = async () => {
|
||||
if (!this._configEntry) {
|
||||
return;
|
||||
}
|
||||
const configEntryId = searchParams.get("config_entry") as string;
|
||||
const configEntries = await getConfigEntries(this.hass, {
|
||||
domain: "mqtt",
|
||||
showConfigFlowDialog(this, {
|
||||
startFlowHandler: this._configEntry.domain,
|
||||
manifest: await fetchIntegrationManifest(
|
||||
this.hass,
|
||||
this._configEntry.domain
|
||||
),
|
||||
entryId: this._configEntry.entry_id,
|
||||
navigateToResult: true,
|
||||
});
|
||||
const configEntry = configEntries.find(
|
||||
(entry) => entry.entry_id === configEntryId
|
||||
);
|
||||
showOptionsFlowDialog(this, configEntry!);
|
||||
}
|
||||
};
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
@@ -199,17 +382,37 @@ export class MQTTConfigPanel extends LitElement {
|
||||
-moz-user-select: initial;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 24px 0 32px;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
direction: ltr;
|
||||
.nav-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.nav-card .card-content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.nav-card .card-header {
|
||||
padding-bottom: var(--ha-space-2);
|
||||
}
|
||||
.content {
|
||||
margin-top: var(--ha-space-6);
|
||||
}
|
||||
|
||||
.panel-dev-mqtt-fields {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
ha-card {
|
||||
margin: 0 auto var(--ha-space-4);
|
||||
max-width: 600px;
|
||||
}
|
||||
ha-md-list {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
ha-md-list-item {
|
||||
--md-item-overflow: visible;
|
||||
}
|
||||
ha-select {
|
||||
width: 96px;
|
||||
margin: 0 8px;
|
||||
@@ -236,6 +439,78 @@ export class MQTTConfigPanel extends LitElement {
|
||||
display: block;
|
||||
margin: 16px auto;
|
||||
}
|
||||
.network-status div.heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: var(--ha-space-4);
|
||||
}
|
||||
|
||||
.network-status div.heading .logo {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
margin-inline-start: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.network-status div.heading .icon {
|
||||
position: relative;
|
||||
border-radius: var(--ha-border-radius-2xl);
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
--icon-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.network-status div.heading .icon.success {
|
||||
--icon-color: var(--success-color);
|
||||
}
|
||||
|
||||
.network-status div.heading .icon.error {
|
||||
--icon-color: var(--error-color);
|
||||
}
|
||||
|
||||
.network-status div.heading .icon::before {
|
||||
display: block;
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-color: var(--icon-color);
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.network-status div.heading .icon ha-svg-icon {
|
||||
color: var(--icon-color);
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.network-status div.heading .details {
|
||||
font-size: var(--ha-font-size-xl);
|
||||
font-weight: var(--ha-font-weight-normal);
|
||||
line-height: var(--ha-line-height-condensed);
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
.network-status small {
|
||||
font-size: var(--ha-font-size-m);
|
||||
font-weight: var(--ha-font-weight-normal);
|
||||
line-height: var(--ha-line-height-condensed);
|
||||
letter-spacing: 0.25px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: var(--ha-space-2) var(--ha-space-4)
|
||||
calc(var(--ha-space-16) + var(--safe-area-inset-bottom, 0px));
|
||||
}
|
||||
|
||||
a[slot="fab"] {
|
||||
text-decoration: none;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { mdiContentCopy } from "@mdi/js";
|
||||
import { formatTime } from "../../../../../common/datetime/format_time";
|
||||
import { copyToClipboard } from "../../../../../common/util/copy-clipboard";
|
||||
import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import "../../../../../components/ha-markdown";
|
||||
import type { HaSelectSelectEvent } from "../../../../../components/ha-select";
|
||||
import "../../../../../components/ha-select";
|
||||
import "../../../../../components/input/ha-input";
|
||||
import type { MQTTMessage } from "../../../../../data/mqtt";
|
||||
import { subscribeMQTTTopic } from "../../../../../data/mqtt";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import { showToast } from "../../../../../util/toast";
|
||||
|
||||
import { storage } from "../../../../../common/decorators/storage";
|
||||
import "../../../../../components/ha-formfield";
|
||||
@@ -68,53 +73,56 @@ class MqttSubscribeCard extends LitElement {
|
||||
return html`
|
||||
<ha-card
|
||||
header=${this.hass.localize("ui.panel.config.mqtt.description_listen")}
|
||||
class="content_subscribe_panel"
|
||||
>
|
||||
<form>
|
||||
<p>
|
||||
<ha-formfield
|
||||
label=${this.hass!.localize(
|
||||
"ui.panel.config.mqtt.json_formatting"
|
||||
)}
|
||||
>
|
||||
<ha-switch
|
||||
@change=${this._handleJSONFormat}
|
||||
.checked=${this._json_format}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
</p>
|
||||
<div class="panel-dev-mqtt-subscribe-fields">
|
||||
<ha-input
|
||||
.label=${
|
||||
this._subscribed
|
||||
? this.hass.localize("ui.panel.config.mqtt.listening_to")
|
||||
: this.hass.localize("ui.panel.config.mqtt.subscribe_to")
|
||||
}
|
||||
.disabled=${this._subscribed !== undefined}
|
||||
.value=${this._topic}
|
||||
@change=${this._handleTopic}
|
||||
></ha-input>
|
||||
<ha-select
|
||||
.label=${this.hass.localize("ui.panel.config.mqtt.qos")}
|
||||
.disabled=${this._subscribed !== undefined}
|
||||
.value=${this._qos}
|
||||
@selected=${this._handleQos}
|
||||
.options=${qosLevel}
|
||||
>
|
||||
</ha-select>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="s"
|
||||
.disabled=${this._topic === ""}
|
||||
@click=${this._handleSubmit}
|
||||
>
|
||||
${
|
||||
this._subscribed
|
||||
? this.hass.localize("ui.panel.config.mqtt.stop_listening")
|
||||
: this.hass.localize("ui.panel.config.mqtt.start_listening")
|
||||
}
|
||||
</ha-button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="card-content">
|
||||
<form>
|
||||
<p>
|
||||
<ha-formfield
|
||||
label=${this.hass!.localize(
|
||||
"ui.panel.config.mqtt.json_formatting"
|
||||
)}
|
||||
>
|
||||
<ha-switch
|
||||
@change=${this._handleJSONFormat}
|
||||
.checked=${this._json_format}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
</p>
|
||||
<div class="panel-dev-mqtt-subscribe-fields">
|
||||
<ha-input
|
||||
.label=${
|
||||
this._subscribed
|
||||
? this.hass.localize("ui.panel.config.mqtt.listening_to")
|
||||
: this.hass.localize("ui.panel.config.mqtt.subscribe_to")
|
||||
}
|
||||
.disabled=${this._subscribed !== undefined}
|
||||
.value=${this._topic}
|
||||
@change=${this._handleTopic}
|
||||
></ha-input>
|
||||
<ha-select
|
||||
.label=${this.hass.localize("ui.panel.config.mqtt.qos")}
|
||||
.disabled=${this._subscribed !== undefined}
|
||||
.value=${this._qos}
|
||||
@selected=${this._handleQos}
|
||||
.options=${qosLevel}
|
||||
>
|
||||
</ha-select>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="s"
|
||||
.disabled=${this._topic === ""}
|
||||
@click=${this._handleSubmit}
|
||||
>
|
||||
${
|
||||
this._subscribed
|
||||
? this.hass.localize("ui.panel.config.mqtt.stop_listening")
|
||||
: this.hass.localize("ui.panel.config.mqtt.start_listening")
|
||||
}
|
||||
</ha-button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="events">
|
||||
${this._messages.map(
|
||||
(msg) => html`
|
||||
@@ -128,7 +136,17 @@ class MqttSubscribeCard extends LitElement {
|
||||
this.hass!.config
|
||||
),
|
||||
})}
|
||||
<pre>${msg.payload}</pre>
|
||||
<div class="code-block">
|
||||
<ha-icon-button
|
||||
class="copy-button"
|
||||
.path=${mdiContentCopy}
|
||||
@click=${this._handleCopyClick}
|
||||
data-payload=${msg.payload}
|
||||
></ha-icon-button>
|
||||
<ha-markdown
|
||||
.content=${`\`\`\`${this._json_format ? "json" : ""}\n${msg.payload}\n\`\`\``}
|
||||
></ha-markdown>
|
||||
</div>
|
||||
<div class="bottom">
|
||||
QoS: ${msg.message.qos} - Retain:
|
||||
${Boolean(msg.message.retain)}
|
||||
@@ -156,6 +174,16 @@ class MqttSubscribeCard extends LitElement {
|
||||
this._json_format = (ev.target! as any).checked;
|
||||
}
|
||||
|
||||
private async _handleCopyClick(ev: Event): Promise<void> {
|
||||
const payload = (ev.target as HTMLElement).getAttribute("data-payload");
|
||||
if (payload) {
|
||||
await copyToClipboard(payload);
|
||||
showToast(this, {
|
||||
message: this.hass.localize("ui.common.copied_clipboard"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _handleSubmit(): Promise<void> {
|
||||
if (this._subscribed) {
|
||||
this._subscribed();
|
||||
@@ -199,6 +227,12 @@ class MqttSubscribeCard extends LitElement {
|
||||
padding: var(--ha-space-4);
|
||||
padding-bottom: var(--ha-space-8);
|
||||
}
|
||||
.content_subscribe_panel {
|
||||
margin-top: var(--ha-space-6);
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
direction: ltr;
|
||||
}
|
||||
.events {
|
||||
margin: -16px 0;
|
||||
padding: 0 16px;
|
||||
@@ -230,6 +264,20 @@ class MqttSubscribeCard extends LitElement {
|
||||
ha-input {
|
||||
flex: 1;
|
||||
}
|
||||
.code-block {
|
||||
position: relative;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.code-block ha-markdown {
|
||||
padding-right: 40px;
|
||||
}
|
||||
.copy-button {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
z-index: 1;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
@media screen and (max-width: 600px) {
|
||||
ha-select {
|
||||
display: block;
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export const mdiMqttLogo =
|
||||
"M1.68,0.5325c-0.63,0-1.1475,0.51-1.1475,1.1475v1.125c11.445,0.0675,20.745,9.3,20.82,20.67h0.975c0.63,0,1.1475-0.51,1.1475-1.1475V14.5125C20.8575,8.04,15.5475,2.9175,8.925,0.5325H1.68z M0.5325,6.3075v3.735c7.425,0.0675,13.455,6.0525,13.53,13.4325h3.8775C17.865,13.995,10.0875,6.315,0.5325,6.3075z M0.5325,13.545v8.7825c0,0.63,0.51,1.1475,1.1475,1.1475H10.65C10.575,17.985,6.0675,13.5525,0.5325,13.545z M16.53,0.5325c1.35,0.945,2.715,2.01,3.915,3.2025c1.0875,1.08,2.145,2.3775,3.03,3.585V1.68c0-0.63-0.51-1.1475-1.1475-1.1475H16.53z";
|
||||
@@ -1608,6 +1608,7 @@
|
||||
"labs": "[%key:ui::panel::config::labs::caption%]",
|
||||
"tools": "[%key:ui::panel::config::dashboard::tools::main%]",
|
||||
"matter": "[%key:ui::panel::config::dashboard::matter::main%]",
|
||||
"mqtt": "[%key:ui::panel::config::dashboard::mqtt::main%]",
|
||||
"zha": "[%key:ui::panel::config::dashboard::zha::main%]",
|
||||
"zwave_js": "[%key:ui::panel::config::dashboard::zwave_js::main%]",
|
||||
"thread": "[%key:ui::panel::config::dashboard::thread::main%]",
|
||||
@@ -2736,6 +2737,10 @@
|
||||
"main": "Matter",
|
||||
"secondary": "Cross-vendor smart home standard"
|
||||
},
|
||||
"mqtt": {
|
||||
"main": "MQTT",
|
||||
"secondary": "Lightweight network protocol designed for IoT devices"
|
||||
},
|
||||
"thread": {
|
||||
"main": "Thread",
|
||||
"secondary": "Mesh network often used for Matter devices"
|
||||
@@ -7400,8 +7405,16 @@
|
||||
},
|
||||
"mqtt": {
|
||||
"title": "MQTT",
|
||||
"settings_title": "MQTT settings",
|
||||
"option_flow": "Configure MQTT options",
|
||||
"option_flow": "MQTT options",
|
||||
"option_flow_description": "Set discovery options",
|
||||
"config_flow": "Connection",
|
||||
"config_flow_description": "Broker connection settings",
|
||||
"status_online": "Online",
|
||||
"status_offline": "Offline",
|
||||
"my_network_title": "My network",
|
||||
"devices": "{count, plural,\n one {# device}\n other {# devices}\n}",
|
||||
"device_count": "{count, plural,\n one {# device}\n other {# devices}\n}",
|
||||
"entity_count": "{count, plural,\n one {# entity}\n other {# entities}\n}",
|
||||
"description_publish": "Publish a packet",
|
||||
"topic": "Topic",
|
||||
"payload": "Payload (template allowed)",
|
||||
|
||||
Reference in New Issue
Block a user