mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-27 11:16:35 +00:00
Display MQTT debug info (#5375)
* Add MQTT debug info * Refactor * Fix mistake * Rewrite, improve display. * Tweak translations * Add mqtt-payload.ts * Apply suggestions from code review Co-Authored-By: Zack Arnett <arnett.zackary@gmail.com> * Tweak after adding review comments. * Rewrite to only render the messages when details is opened * Adapt to core PR #33752 * Address review comments * Lint * Lint * Address review comments Co-authored-by: Zack Arnett <arnett.zackary@gmail.com>
This commit is contained in:
parent
1dfb632fc4
commit
9a00078169
@ -7,6 +7,31 @@ export interface MQTTMessage {
|
|||||||
retain: number;
|
retain: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MQTTTopicDebugInfo {
|
||||||
|
topic: string;
|
||||||
|
messages: MQTTMessage[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MQTTDiscoveryDebugInfo {
|
||||||
|
topic: string;
|
||||||
|
payload: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MQTTEntityDebugInfo {
|
||||||
|
entity_id: string;
|
||||||
|
discovery_data: MQTTDiscoveryDebugInfo;
|
||||||
|
subscriptions: MQTTTopicDebugInfo[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MQTTTriggerDebugInfo {
|
||||||
|
discovery_data: MQTTDiscoveryDebugInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MQTTDeviceDebugInfo {
|
||||||
|
entities: MQTTEntityDebugInfo[];
|
||||||
|
triggers: MQTTTriggerDebugInfo[];
|
||||||
|
}
|
||||||
|
|
||||||
export const subscribeMQTTTopic = (
|
export const subscribeMQTTTopic = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
topic: string,
|
topic: string,
|
||||||
@ -26,3 +51,12 @@ export const removeMQTTDeviceEntry = (
|
|||||||
type: "mqtt/device/remove",
|
type: "mqtt/device/remove",
|
||||||
device_id: deviceId,
|
device_id: deviceId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const fetchMQTTDebugInfo = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
deviceId: string
|
||||||
|
): Promise<MQTTDeviceDebugInfo> =>
|
||||||
|
hass.callWS<MQTTDeviceDebugInfo>({
|
||||||
|
type: "mqtt/device/debug_info",
|
||||||
|
device_id: deviceId,
|
||||||
|
});
|
||||||
|
@ -0,0 +1,215 @@
|
|||||||
|
import {
|
||||||
|
LitElement,
|
||||||
|
css,
|
||||||
|
html,
|
||||||
|
CSSResult,
|
||||||
|
TemplateResult,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
|
} from "lit-element";
|
||||||
|
import "../../components/ha-dialog";
|
||||||
|
import "../../components/ha-switch";
|
||||||
|
import { computeDeviceName } from "../../data/device_registry";
|
||||||
|
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||||
|
import { haStyleDialog } from "../../resources/styles";
|
||||||
|
import type { HaSwitch } from "../../components/ha-switch";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
import { MQTTDeviceDebugInfoDialogParams } from "./show-dialog-mqtt-device-debug-info";
|
||||||
|
import { MQTTDeviceDebugInfo, fetchMQTTDebugInfo } from "../../data/mqtt";
|
||||||
|
import "./mqtt-messages";
|
||||||
|
import "./mqtt-discovery-payload";
|
||||||
|
|
||||||
|
@customElement("dialog-mqtt-device-debug-info")
|
||||||
|
class DialogMQTTDeviceDebugInfo extends LitElement {
|
||||||
|
public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() private _params?: MQTTDeviceDebugInfoDialogParams;
|
||||||
|
|
||||||
|
@property() private _debugInfo?: MQTTDeviceDebugInfo;
|
||||||
|
|
||||||
|
@property() private _showAsYaml = true;
|
||||||
|
|
||||||
|
@property() private _showDeserialized = true;
|
||||||
|
|
||||||
|
public async showDialog(
|
||||||
|
params: MQTTDeviceDebugInfoDialogParams
|
||||||
|
): Promise<void> {
|
||||||
|
this._params = params;
|
||||||
|
fetchMQTTDebugInfo(this.hass, params.device.id).then((results) => {
|
||||||
|
this._debugInfo = results;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
if (!this._params || !this._debugInfo) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-dialog
|
||||||
|
open
|
||||||
|
@closing=${this._close}
|
||||||
|
.heading="${this.hass!.localize(
|
||||||
|
"ui.dialogs.mqtt_device_debug_info.title",
|
||||||
|
"device",
|
||||||
|
computeDeviceName(this._params.device, this.hass)
|
||||||
|
)}"
|
||||||
|
>
|
||||||
|
<h4>
|
||||||
|
${this.hass!.localize(
|
||||||
|
"ui.dialogs.mqtt_device_debug_info.payload_display"
|
||||||
|
)}
|
||||||
|
</h4>
|
||||||
|
<ha-switch
|
||||||
|
.checked=${this._showDeserialized}
|
||||||
|
@change=${this._showDeserializedChanged}
|
||||||
|
>
|
||||||
|
${this.hass!.localize(
|
||||||
|
"ui.dialogs.mqtt_device_debug_info.deserialize"
|
||||||
|
)}
|
||||||
|
</ha-switch>
|
||||||
|
<ha-switch
|
||||||
|
.checked=${this._showAsYaml}
|
||||||
|
@change=${this._showAsYamlChanged}
|
||||||
|
>
|
||||||
|
${this.hass!.localize(
|
||||||
|
"ui.dialogs.mqtt_device_debug_info.show_as_yaml"
|
||||||
|
)}
|
||||||
|
</ha-switch>
|
||||||
|
<h4>
|
||||||
|
${this.hass!.localize("ui.dialogs.mqtt_device_debug_info.entities")}
|
||||||
|
</h4>
|
||||||
|
<ul>
|
||||||
|
${this._debugInfo.entities.length
|
||||||
|
? this._renderEntities()
|
||||||
|
: html`
|
||||||
|
${this.hass!.localize(
|
||||||
|
"ui.dialogs.mqtt_device_debug_info.no_entities"
|
||||||
|
)}
|
||||||
|
`}
|
||||||
|
</ul>
|
||||||
|
<h4>
|
||||||
|
${this.hass!.localize("ui.dialogs.mqtt_device_debug_info.triggers")}
|
||||||
|
</h4>
|
||||||
|
<ul>
|
||||||
|
${this._debugInfo.triggers.length
|
||||||
|
? this._renderTriggers()
|
||||||
|
: html`
|
||||||
|
${this.hass!.localize(
|
||||||
|
"ui.dialogs.mqtt_device_debug_info.no_triggers"
|
||||||
|
)}
|
||||||
|
`}
|
||||||
|
</ul>
|
||||||
|
<mwc-button slot="primaryAction" @click=${this._close}>
|
||||||
|
${this.hass!.localize("ui.dialogs.generic.close")}
|
||||||
|
</mwc-button>
|
||||||
|
</ha-dialog>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _close(): void {
|
||||||
|
this._params = undefined;
|
||||||
|
this._debugInfo = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _showAsYamlChanged(ev: Event): void {
|
||||||
|
this._showAsYaml = (ev.target as HaSwitch).checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _showDeserializedChanged(ev: Event): void {
|
||||||
|
this._showDeserialized = (ev.target as HaSwitch).checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderEntities(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
${this._debugInfo!.entities.map(
|
||||||
|
(entity) => html`
|
||||||
|
<li>
|
||||||
|
'${computeStateName(this.hass.states[entity.entity_id])}'
|
||||||
|
(<code>${entity.entity_id}</code>)
|
||||||
|
<br />MQTT discovery data:
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Topic:
|
||||||
|
<code>${entity.discovery_data.topic}</code>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<mqtt-discovery-payload
|
||||||
|
.hass=${this.hass}
|
||||||
|
.payload=${entity.discovery_data.payload}
|
||||||
|
.showAsYaml=${this._showAsYaml}
|
||||||
|
.summary=${"Payload"}
|
||||||
|
>
|
||||||
|
</mqtt-discovery-payload>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
Subscribed topics:
|
||||||
|
<ul>
|
||||||
|
${entity.subscriptions.map(
|
||||||
|
(topic) => html`
|
||||||
|
<li>
|
||||||
|
<code>${topic.topic}</code>
|
||||||
|
<mqtt-messages
|
||||||
|
.hass=${this.hass}
|
||||||
|
.messages=${topic.messages}
|
||||||
|
.showDeserialized=${this._showDeserialized}
|
||||||
|
.showAsYaml=${this._showAsYaml}
|
||||||
|
.subscribedTopic=${topic.topic}
|
||||||
|
.summary=${this.hass!.localize(
|
||||||
|
"ui.dialogs.mqtt_device_debug_info.recent_messages",
|
||||||
|
"n",
|
||||||
|
topic.messages.length
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
</mqtt-messages>
|
||||||
|
</li>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderTriggers(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
${this._debugInfo!.triggers.map(
|
||||||
|
(trigger) => html`
|
||||||
|
<li>
|
||||||
|
Discovery topic:
|
||||||
|
<code>${trigger.discovery_data.topic}</code>
|
||||||
|
<mqtt-discovery-payload
|
||||||
|
.hass=${this.hass}
|
||||||
|
.payload=${trigger.discovery_data.payload}
|
||||||
|
.showAsYaml=${this._showAsYaml}
|
||||||
|
.summary="Discovery payload"
|
||||||
|
>
|
||||||
|
</mqtt-discovery-payload>
|
||||||
|
</li>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult[] {
|
||||||
|
return [
|
||||||
|
haStyleDialog,
|
||||||
|
css`
|
||||||
|
ha-dialog {
|
||||||
|
--mdc-dialog-max-width: 95%;
|
||||||
|
--mdc-dialog-min-width: 640px;
|
||||||
|
}
|
||||||
|
ha-switch {
|
||||||
|
margin: 16px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"dialog-mqtt-device-debug-info": DialogMQTTDeviceDebugInfo;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
import {
|
||||||
|
LitElement,
|
||||||
|
html,
|
||||||
|
TemplateResult,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
|
} from "lit-element";
|
||||||
|
import { safeDump } from "js-yaml";
|
||||||
|
|
||||||
|
@customElement("mqtt-discovery-payload")
|
||||||
|
class MQTTDiscoveryPayload extends LitElement {
|
||||||
|
@property() public payload!: object;
|
||||||
|
|
||||||
|
@property() public showAsYaml = false;
|
||||||
|
|
||||||
|
@property() public summary!: string;
|
||||||
|
|
||||||
|
@property() private _open = false;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<details @toggle=${this._handleToggle}>
|
||||||
|
<summary>
|
||||||
|
${this.summary}
|
||||||
|
</summary>
|
||||||
|
${this._open ? this._renderPayload() : ""}
|
||||||
|
</details>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderPayload(): TemplateResult {
|
||||||
|
const payload = this.payload;
|
||||||
|
return html`
|
||||||
|
${this.showAsYaml
|
||||||
|
? html` <pre>${safeDump(payload)}</pre> `
|
||||||
|
: html` <pre>${JSON.stringify(payload, null, 2)}</pre> `}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleToggle(ev) {
|
||||||
|
this._open = ev.target.open;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"mqtt-discovery-payload": MQTTDiscoveryPayload;
|
||||||
|
}
|
||||||
|
}
|
130
src/dialogs/mqtt-device-debug-info-dialog/mqtt-messages.ts
Normal file
130
src/dialogs/mqtt-device-debug-info-dialog/mqtt-messages.ts
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
import {
|
||||||
|
LitElement,
|
||||||
|
html,
|
||||||
|
TemplateResult,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
|
} from "lit-element";
|
||||||
|
import { safeDump } from "js-yaml";
|
||||||
|
import { MQTTMessage } from "../../data/mqtt";
|
||||||
|
|
||||||
|
@customElement("mqtt-messages")
|
||||||
|
class MQTTMessages extends LitElement {
|
||||||
|
@property() public messages!: MQTTMessage[];
|
||||||
|
|
||||||
|
@property() public showAsYaml = false;
|
||||||
|
|
||||||
|
@property() public showDeserialized = false;
|
||||||
|
|
||||||
|
@property() public subscribedTopic!: string;
|
||||||
|
|
||||||
|
@property() public summary!: string;
|
||||||
|
|
||||||
|
@property() private _open = false;
|
||||||
|
|
||||||
|
@property() private _payloadsJson = new WeakMap();
|
||||||
|
|
||||||
|
@property() private _showTopic = false;
|
||||||
|
|
||||||
|
protected firstUpdated(): void {
|
||||||
|
this.messages.forEach((message) => {
|
||||||
|
// If any message's topic differs from the subscribed topic, show topics + payload
|
||||||
|
if (this.subscribedTopic !== message.topic) {
|
||||||
|
this._showTopic = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<details @toggle=${this._handleToggle}>
|
||||||
|
<summary>
|
||||||
|
${this.summary}
|
||||||
|
</summary>
|
||||||
|
${this._open
|
||||||
|
? html`
|
||||||
|
<ul>
|
||||||
|
${this.messages.map(
|
||||||
|
(message) => html`
|
||||||
|
<li>
|
||||||
|
${this._renderSingleMessage(message)}
|
||||||
|
</li>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</details>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderSingleMessage(message): TemplateResult {
|
||||||
|
const topic = message.topic;
|
||||||
|
return this._showTopic
|
||||||
|
? html`
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Topic: ${topic}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Payload: ${this._renderSinglePayload(message)}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
`
|
||||||
|
: this._renderSinglePayload(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderSinglePayload(message): TemplateResult {
|
||||||
|
let json;
|
||||||
|
|
||||||
|
if (this.showDeserialized) {
|
||||||
|
if (!this._payloadsJson.has(message)) {
|
||||||
|
json = this._tryParseJson(message.payload);
|
||||||
|
this._payloadsJson.set(message, json);
|
||||||
|
} else {
|
||||||
|
json = this._payloadsJson.get(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return json
|
||||||
|
? html`
|
||||||
|
${this.showAsYaml
|
||||||
|
? html` <pre>${safeDump(json)}</pre> `
|
||||||
|
: html` <pre>${JSON.stringify(json, null, 2)}</pre> `}
|
||||||
|
`
|
||||||
|
: html` <code>${message.payload}</code> `;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _tryParseJson(payload) {
|
||||||
|
let jsonPayload = null;
|
||||||
|
let o = payload;
|
||||||
|
|
||||||
|
// If the payload is a string, determine if the payload is valid JSON and if it
|
||||||
|
// is, assign the object representation to this._payloadJson.
|
||||||
|
if (typeof payload === "string") {
|
||||||
|
try {
|
||||||
|
o = JSON.parse(payload);
|
||||||
|
} catch (e) {
|
||||||
|
o = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Handle non-exception-throwing cases:
|
||||||
|
// Neither JSON.parse(false) or JSON.parse(1234) throw errors, hence the type-checking,
|
||||||
|
// but... JSON.parse(null) returns null, and typeof null === "object",
|
||||||
|
// so we must check for that, too. Thankfully, null is falsey, so this suffices:
|
||||||
|
if (o && typeof o === "object") {
|
||||||
|
jsonPayload = o;
|
||||||
|
}
|
||||||
|
return jsonPayload;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleToggle(ev) {
|
||||||
|
this._open = ev.target.open;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"mqtt-messages": MQTTMessages;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import { DeviceRegistryEntry } from "../../data/device_registry";
|
||||||
|
|
||||||
|
export interface MQTTDeviceDebugInfoDialogParams {
|
||||||
|
device: DeviceRegistryEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const loadMQTTDeviceDebugInfoDialog = () =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "dialog-mqtt-device-debug-info" */ "./dialog-mqtt-device-debug-info"
|
||||||
|
);
|
||||||
|
|
||||||
|
export const showMQTTDeviceDebugInfoDialog = (
|
||||||
|
element: HTMLElement,
|
||||||
|
mqttDeviceInfoParams: MQTTDeviceDebugInfoDialogParams
|
||||||
|
): void => {
|
||||||
|
fireEvent(element, "show-dialog", {
|
||||||
|
dialogTag: "dialog-mqtt-device-debug-info",
|
||||||
|
dialogImport: loadMQTTDeviceDebugInfoDialog,
|
||||||
|
dialogParams: mqttDeviceInfoParams,
|
||||||
|
});
|
||||||
|
};
|
@ -1,16 +1,17 @@
|
|||||||
import {
|
|
||||||
CSSResult,
|
|
||||||
customElement,
|
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
property,
|
|
||||||
TemplateResult,
|
|
||||||
} from "lit-element";
|
|
||||||
import { DeviceRegistryEntry } from "../../../../data/device_registry";
|
import { DeviceRegistryEntry } from "../../../../data/device_registry";
|
||||||
import { removeMQTTDeviceEntry } from "../../../../data/mqtt";
|
import { removeMQTTDeviceEntry } from "../../../../data/mqtt";
|
||||||
|
import {
|
||||||
|
LitElement,
|
||||||
|
html,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
|
TemplateResult,
|
||||||
|
CSSResult,
|
||||||
|
} from "lit-element";
|
||||||
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
|
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||||
import { haStyle } from "../../../../resources/styles";
|
import { showMQTTDeviceDebugInfoDialog } from "../../../../dialogs/mqtt-device-debug-info-dialog/show-dialog-mqtt-device-debug-info";
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../../../types";
|
||||||
|
import { haStyle } from "../../../../resources/styles";
|
||||||
|
|
||||||
@customElement("ha-device-card-mqtt")
|
@customElement("ha-device-card-mqtt")
|
||||||
export class HaDeviceCardMqtt extends LitElement {
|
export class HaDeviceCardMqtt extends LitElement {
|
||||||
@ -20,6 +21,9 @@ export class HaDeviceCardMqtt extends LitElement {
|
|||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
|
<mwc-button @click=${this._showDebugInfo}>
|
||||||
|
MQTT Info
|
||||||
|
</mwc-button>
|
||||||
<mwc-button class="warning" @click="${this._confirmDeleteEntry}">
|
<mwc-button class="warning" @click="${this._confirmDeleteEntry}">
|
||||||
${this.hass.localize("ui.panel.config.devices.delete")}
|
${this.hass.localize("ui.panel.config.devices.delete")}
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
@ -38,6 +42,11 @@ export class HaDeviceCardMqtt extends LitElement {
|
|||||||
await removeMQTTDeviceEntry(this.hass!, this.device.id);
|
await removeMQTTDeviceEntry(this.hass!, this.device.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _showDebugInfo(): Promise<void> {
|
||||||
|
const device = this.device;
|
||||||
|
await showMQTTDeviceDebugInfoDialog(this, { device });
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResult {
|
static get styles(): CSSResult {
|
||||||
return haStyle;
|
return haStyle;
|
||||||
}
|
}
|
||||||
|
@ -456,6 +456,17 @@
|
|||||||
},
|
},
|
||||||
"domain_toggler": {
|
"domain_toggler": {
|
||||||
"title": "Toggle Domains"
|
"title": "Toggle Domains"
|
||||||
|
},
|
||||||
|
"mqtt_device_debug_info": {
|
||||||
|
"title": "{device} debug info",
|
||||||
|
"deserialize": "Attempt to parse MQTT messages as JSON",
|
||||||
|
"entities": "Entities",
|
||||||
|
"no_entities": "No entities",
|
||||||
|
"no_triggers": "No triggers",
|
||||||
|
"payload_display": "Payload display",
|
||||||
|
"recent_messages": "{n} most recently received message(s)",
|
||||||
|
"show_as_yaml": "Show as YAML",
|
||||||
|
"triggers": "Triggers"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"duration": {
|
"duration": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user