From d122af6b97f2800660cd2487b2dcb75ffa9e6a74 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Mon, 26 Feb 2024 07:56:35 -0800 Subject: [PATCH] Live: Improve the debug panel and add a devenv dashbaord (#83350) --- .betterer.results | 14 +- devenv/dev-dashboards/live/live-publish.json | 447 ++++++++++++++++++ devenv/jsonnet/dev-dashboards.libsonnet | 1 + public/app/features/live/info.ts | 44 ++ .../grafana/components/QueryEditor.tsx | 36 +- .../plugins/panel/live/LiveChannelEditor.tsx | 178 +++---- public/app/plugins/panel/live/LivePanel.tsx | 69 +-- public/app/plugins/panel/live/LivePublish.tsx | 67 +++ public/app/plugins/panel/live/module.tsx | 17 +- public/app/plugins/panel/live/types.ts | 12 +- 10 files changed, 704 insertions(+), 181 deletions(-) create mode 100644 devenv/dev-dashboards/live/live-publish.json create mode 100644 public/app/features/live/info.ts create mode 100644 public/app/plugins/panel/live/LivePublish.tsx diff --git a/.betterer.results b/.betterer.results index 7436903ef0d..4eabd94f2c3 100644 --- a/.betterer.results +++ b/.betterer.results @@ -4864,14 +4864,11 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "0"] ], "public/app/plugins/datasource/grafana/components/QueryEditor.tsx:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Do not use any type assertions.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"], + [0, 0, 0, "Do not use any type assertions.", "0"], + [0, 0, 0, "Unexpected any. Specify a different type.", "1"], + [0, 0, 0, "Do not use any type assertions.", "2"], [0, 0, 0, "Do not use any type assertions.", "3"], - [0, 0, 0, "Unexpected any. Specify a different type.", "4"], - [0, 0, 0, "Do not use any type assertions.", "5"], - [0, 0, 0, "Do not use any type assertions.", "6"], - [0, 0, 0, "Styles should be written using objects.", "7"] + [0, 0, 0, "Styles should be written using objects.", "4"] ], "public/app/plugins/datasource/grafana/components/TimePickerInput.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], @@ -5942,9 +5939,6 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "6"], [0, 0, 0, "Styles should be written using objects.", "7"] ], - "public/app/plugins/panel/live/types.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"] - ], "public/app/plugins/panel/logs/LogsPanel.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], diff --git a/devenv/dev-dashboards/live/live-publish.json b/devenv/dev-dashboards/live/live-publish.json new file mode 100644 index 00000000000..1e7e26b8dfb --- /dev/null +++ b/devenv/dev-dashboards/live/live-publish.json @@ -0,0 +1,447 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 209, + "links": [], + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 9, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "## This dashboard requires alpha panels to be enabled!", + "mode": "markdown" + }, + "pluginVersion": "11.0.0-pre", + "type": "text" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 4, + "w": 15, + "x": 0, + "y": 2 + }, + "id": 2, + "options": { + "channel": { + "namespace": "devenv", + "path": "weather", + "scope": "stream" + }, + "display": "none", + "json": { + "hello": "world" + }, + "message": "weather,location=west,sensor=A temperature=82\nweather,location=east,sensor=A temperature=76", + "publish": "influx" + }, + "title": "Panel Title", + "type": "live" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 9, + "x": 15, + "y": 2 + }, + "id": 4, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.0.0-pre", + "targets": [ + { + "channel": "stream/devenv/weather", + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "measurements", + "refId": "A" + } + ], + "title": "Weather (values)", + "transformations": [ + { + "id": "sortBy", + "options": { + "fields": {}, + "sort": [ + { + "desc": true, + "field": "time" + } + ] + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 4, + "w": 15, + "x": 0, + "y": 6 + }, + "id": 5, + "options": { + "channel": { + "namespace": "devenv", + "path": "weather", + "scope": "stream" + }, + "display": "none", + "json": { + "hello": "world" + }, + "message": "weather,location=west,sensor=A temperature=90\nweather,location=east,sensor=A temperature=80", + "publish": "influx" + }, + "title": "Panel Title", + "type": "live" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 10 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "channel": "stream/devenv/weather", + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "measurements", + "refId": "A" + }, + { + "channel": "stream/devenv/weatherX", + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "hide": false, + "queryType": "measurements", + "refId": "B" + } + ], + "title": "Panel Title", + "type": "timeseries" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 4, + "w": 15, + "x": 0, + "y": 17 + }, + "id": 6, + "options": { + "channel": { + "namespace": "devenv", + "path": "weather", + "scope": "stream" + }, + "display": "none", + "json": { + "hello": "world" + }, + "message": "weatherX,location=west,sensor=X temperature=82\nweatherX,location=east,sensor=X temperature=76", + "publish": "influx" + }, + "title": "Panel Title", + "type": "live" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 9, + "x": 15, + "y": 17 + }, + "id": 7, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.0.0-pre", + "targets": [ + { + "channel": "stream/devenv/weatherX", + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "measurements", + "refId": "A" + } + ], + "title": "WeatherX (values)", + "transformations": [ + { + "id": "sortBy", + "options": { + "fields": {}, + "sort": [ + { + "desc": true, + "field": "time" + } + ] + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 4, + "w": 15, + "x": 0, + "y": 21 + }, + "id": 8, + "options": { + "channel": { + "namespace": "devenv", + "path": "weather", + "scope": "stream" + }, + "display": "none", + "json": { + "hello": "world" + }, + "message": "weatherX,location=west,sensor=X temperature=90\nweatherX,location=east,sensor=X temperature=22", + "publish": "influx" + }, + "title": "Panel Title", + "type": "live" + } + ], + "schemaVersion": 39, + "tags": [ + "gdev", + "live-tests" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-1m", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "live test", + "uid": "addoomtlivedev", + "version": 17, + "weekStart": "" + } \ No newline at end of file diff --git a/devenv/jsonnet/dev-dashboards.libsonnet b/devenv/jsonnet/dev-dashboards.libsonnet index 7ab736b308d..d0efa7f9062 100644 --- a/devenv/jsonnet/dev-dashboards.libsonnet +++ b/devenv/jsonnet/dev-dashboards.libsonnet @@ -58,6 +58,7 @@ "join-by-labels": (import '../dev-dashboards/transforms/join-by-labels.json'), "lazy_loading": (import '../dev-dashboards/panel-common/lazy_loading.json'), "linked-viz": (import '../dev-dashboards/panel-common/linked-viz.json'), + "live-publish": (import '../dev-dashboards/live/live-publish.json'), "loki_fakedata": (import '../dev-dashboards/datasource-loki/loki_fakedata.json'), "loki_query_splitting": (import '../dev-dashboards/datasource-loki/loki_query_splitting.json'), "migrations": (import '../dev-dashboards/migrations/migrations.json'), diff --git a/public/app/features/live/info.ts b/public/app/features/live/info.ts new file mode 100644 index 00000000000..19bab8db168 --- /dev/null +++ b/public/app/features/live/info.ts @@ -0,0 +1,44 @@ +import { SelectableValue, dataFrameFromJSON } from '@grafana/data'; +import { getBackendSrv } from '@grafana/runtime'; + +interface ChannelInfo { + channel: string; + minute_rate: number; // + data: unknown; // the last payload +} + +interface ManagedChannels { + channels: ChannelInfo[]; +} + +interface ChannelSelectionInfo { + channels: Array>; + channelFields: Record>>; +} + +export async function getManagedChannelInfo(): Promise { + return getBackendSrv() + .get('api/live/list') + .then((v) => { + const channelInfo = v.channels ?? []; + const channelFields: Record>> = {}; + const channels: Array> = channelInfo.map((c) => { + if (c.data) { + const distinctFields = new Set(); + const frame = dataFrameFromJSON(c.data); + for (const f of frame.fields) { + distinctFields.add(f.name); + } + channelFields[c.channel] = Array.from(distinctFields).map((n) => ({ + value: n, + label: n, + })); + } + return { + value: c.channel, + label: c.channel + ' [' + c.minute_rate + ' msg/min]', + }; + }); + return { channelFields, channels }; + }); +} diff --git a/public/app/plugins/datasource/grafana/components/QueryEditor.tsx b/public/app/plugins/datasource/grafana/components/QueryEditor.tsx index 80722f4a652..6ce90c71097 100644 --- a/public/app/plugins/datasource/grafana/components/QueryEditor.tsx +++ b/public/app/plugins/datasource/grafana/components/QueryEditor.tsx @@ -6,7 +6,6 @@ import { DropEvent, FileRejection } from 'react-dropzone'; import { QueryEditorProps, SelectableValue, - dataFrameFromJSON, rangeUtil, DataQueryRequest, DataFrame, @@ -16,7 +15,7 @@ import { getValueFormat, formattedValueToString, } from '@grafana/data'; -import { config, getBackendSrv, getDataSourceSrv, reportInteraction } from '@grafana/runtime'; +import { config, getDataSourceSrv, reportInteraction } from '@grafana/runtime'; import { InlineField, Select, @@ -33,6 +32,7 @@ import { } from '@grafana/ui'; import { hasAlphaPanels } from 'app/core/config'; import * as DFImport from 'app/features/dataframe-import'; +import { getManagedChannelInfo } from 'app/features/live/info'; import { SearchQuery } from 'app/features/search/service'; import { GrafanaDatasource } from '../datasource'; @@ -91,35 +91,9 @@ export class UnthemedQueryEditor extends PureComponent { } loadChannelInfo() { - getBackendSrv() - .fetch({ url: 'api/live/list' }) - .subscribe({ - next: (v: any) => { - const channelInfo = v.data?.channels as any[]; - if (channelInfo?.length) { - const channelFields: Record>> = {}; - const channels: Array> = channelInfo.map((c) => { - if (c.data) { - const distinctFields = new Set(); - const frame = dataFrameFromJSON(c.data); - for (const f of frame.fields) { - distinctFields.add(f.name); - } - channelFields[c.channel] = Array.from(distinctFields).map((n) => ({ - value: n, - label: n, - })); - } - return { - value: c.channel, - label: c.channel + ' [' + c.minute_rate + ' msg/min]', - }; - }); - - this.setState({ channelFields, channels }); - } - }, - }); + getManagedChannelInfo().then((v) => { + this.setState(v); + }); } loadFolderInfo() { diff --git a/public/app/plugins/panel/live/LiveChannelEditor.tsx b/public/app/plugins/panel/live/LiveChannelEditor.tsx index 02a49f6974d..1252d48debd 100644 --- a/public/app/plugins/panel/live/LiveChannelEditor.tsx +++ b/public/app/plugins/panel/live/LiveChannelEditor.tsx @@ -1,5 +1,5 @@ import { css } from '@emotion/css'; -import React, { PureComponent } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { LiveChannelScope, @@ -7,9 +7,11 @@ import { SelectableValue, StandardEditorProps, GrafanaTheme2, + parseLiveChannelAddress, } from '@grafana/data'; import { Select, Alert, Label, stylesFactory } from '@grafana/ui'; import { config } from 'app/core/config'; +import { getManagedChannelInfo } from 'app/features/live/info'; import { LivePanelOptions } from './types'; @@ -19,39 +21,54 @@ const scopes: Array> = [ { label: 'Grafana', value: LiveChannelScope.Grafana, description: 'Core grafana live features' }, { label: 'Data Sources', value: LiveChannelScope.DataSource, description: 'Data sources with live support' }, { label: 'Plugins', value: LiveChannelScope.Plugin, description: 'Plugins with live support' }, + { label: 'Stream', value: LiveChannelScope.Stream, description: 'data streams (eg, influx style)' }, ]; -interface State { - namespaces: Array>; - paths: Array>; -} - -export class LiveChannelEditor extends PureComponent { - state: State = { - namespaces: [], - paths: [], - }; - - async componentDidMount() { - this.updateSelectOptions(); - } - - async componentDidUpdate(oldProps: Props) { - if (this.props.value !== oldProps.value) { - this.updateSelectOptions(); +export function LiveChannelEditor(props: Props) { + const [channels, setChannels] = useState>>([]); + const [namespaces, paths] = useMemo(() => { + const namespaces: Array> = []; + const paths: Array> = []; + const scope = props.value.scope; + const namespace = props.value.namespace; + if (!scope?.length) { + return [namespaces, paths]; } - } + const used: Record = {}; - async updateSelectOptions() { - this.setState({ - namespaces: [], - paths: [], + for (let channel of channels) { + const addr = parseLiveChannelAddress(channel.value); + if (!addr || addr.scope !== scope) { + continue; + } + + if (!used[addr.namespace]) { + namespaces.push({ + value: addr.namespace, + label: addr.namespace, + }); + used[addr.namespace] = true; + } + + if (namespace?.length && namespace === addr.namespace) { + paths.push({ + ...channel, + value: addr.path, + }); + } + } + return [namespaces, paths]; + }, [channels, props.value.scope, props.value.namespace]); + + useEffect(() => { + getManagedChannelInfo().then((v) => { + setChannels(v.channels); }); - } + }, [props.value.scope]); - onScopeChanged = (v: SelectableValue) => { + const onScopeChanged = (v: SelectableValue) => { if (v.value) { - this.props.onChange({ + props.onChange({ scope: v.value, namespace: undefined, path: undefined, @@ -59,73 +76,72 @@ export class LiveChannelEditor extends PureComponent { } }; - onNamespaceChanged = (v: SelectableValue) => { - this.props.onChange({ - scope: this.props.value?.scope, - namespace: v.value, + const onNamespaceChanged = (v: SelectableValue) => { + props.onChange({ + scope: props.value?.scope, + namespace: v?.value, path: undefined, }); }; - onPathChanged = (v: SelectableValue) => { - const { value, onChange } = this.props; + const onPathChanged = (v: SelectableValue) => { + const { value, onChange } = props; onChange({ scope: value.scope, namespace: value.namespace, - path: v.value, + path: v?.value, }); }; - render() { - const { namespaces, paths } = this.state; - const { scope, namespace, path } = this.props.value; - const style = getStyles(config.theme2); + const { scope, namespace, path } = props.value; + const style = getStyles(config.theme2); - return ( - <> - - This supports real-time event streams in grafana core. This feature is under heavy development. Expect the - intefaces and structures to change as this becomes more production ready. - + return ( + <> + + This supports real-time event streams in grafana core. This feature is under heavy development. Expect the + intefaces and structures to change as this becomes more production ready. + -
-
- - s.value === namespace) ?? - (namespace ? { label: namespace, value: namespace } : undefined) - } - onChange={this.onNamespaceChanged} - allowCustomValue={true} - backspaceRemovesValue={true} - /> -
- )} - - {scope && namespace && ( -
- - s.value === scope)} onChange={onScopeChanged} />
- - ); - } + + {scope && ( +
+ + +
+ )} +
+ + ); } function findPathOption(paths: Array>, path?: string): SelectableValue | undefined { diff --git a/public/app/plugins/panel/live/LivePanel.tsx b/public/app/plugins/panel/live/LivePanel.tsx index ed9f0b48ecb..7bb237d0a11 100644 --- a/public/app/plugins/panel/live/LivePanel.tsx +++ b/public/app/plugins/panel/live/LivePanel.tsx @@ -19,11 +19,12 @@ import { StreamingDataFrame, } from '@grafana/data'; import { config, getGrafanaLiveSrv } from '@grafana/runtime'; -import { Alert, stylesFactory, Button, JSONFormatter, CustomScrollbar, CodeEditor } from '@grafana/ui'; +import { Alert, stylesFactory, JSONFormatter, CustomScrollbar } from '@grafana/ui'; import { TablePanel } from '../table/TablePanel'; -import { LivePanelOptions, MessageDisplayMode } from './types'; +import { LivePublish } from './LivePublish'; +import { LivePanelOptions, MessageDisplayMode, MessagePublishMode } from './types'; interface Props extends PanelProps {} @@ -133,34 +134,6 @@ export class LivePanel extends PureComponent { ); } - onSaveJSON = (text: string) => { - const { options, onOptionsChange } = this.props; - - try { - const json = JSON.parse(text); - onOptionsChange({ ...options, json }); - } catch (err) { - console.log('Error reading JSON', err); - } - }; - - onPublishClicked = async () => { - const { addr } = this.state; - if (!addr) { - console.log('invalid address'); - return; - } - - const data = this.props.options?.json; - if (!data) { - console.log('nothing to publish'); - return; - } - - const rsp = await getGrafanaLiveSrv().publish(addr, data); - console.log('onPublishClicked (response from publish)', rsp); - }; - renderMessage(height: number) { const { options } = this.props; const { message } = this.state; @@ -174,11 +147,11 @@ export class LivePanel extends PureComponent { ); } - if (options.message === MessageDisplayMode.JSON) { + if (options.display === MessageDisplayMode.JSON) { return ; } - if (options.message === MessageDisplayMode.Auto) { + if (options.display === MessageDisplayMode.Auto) { if (message instanceof StreamingDataFrame) { const data: PanelData = { series: applyFieldOverrides({ @@ -206,20 +179,13 @@ export class LivePanel extends PureComponent { renderPublish(height: number) { const { options } = this.props; return ( - <> - -
- -
- + this.props.onOptionsChange({ ...options, message })} + addr={this.state.addr} + /> ); } @@ -239,12 +205,13 @@ export class LivePanel extends PureComponent { renderBody() { const { status } = this.state; const { options, height } = this.props; + const publish = options.publish === MessagePublishMode.JSON || options.publish === MessagePublishMode.Influx; - if (options.publish) { - // Only the publish form - if (options.message === MessageDisplayMode.None) { - return
{this.renderPublish(height)}
; + if (publish) { + if (options.display === MessageDisplayMode.None) { + return this.renderPublish(height); } + // Both message and publish const halfHeight = height / 2; return ( @@ -258,7 +225,7 @@ export class LivePanel extends PureComponent { ); } - if (options.message === MessageDisplayMode.None) { + if (options.display === MessageDisplayMode.None) { return
{JSON.stringify(status)}
; } diff --git a/public/app/plugins/panel/live/LivePublish.tsx b/public/app/plugins/panel/live/LivePublish.tsx new file mode 100644 index 00000000000..6d7e8b148ff --- /dev/null +++ b/public/app/plugins/panel/live/LivePublish.tsx @@ -0,0 +1,67 @@ +import React, { useMemo } from 'react'; + +import { LiveChannelAddress, isValidLiveChannelAddress } from '@grafana/data'; +import { getBackendSrv, getGrafanaLiveSrv } from '@grafana/runtime'; +import { CodeEditor, Button } from '@grafana/ui'; + +import { MessagePublishMode } from './types'; + +interface Props { + height: number; + addr?: LiveChannelAddress; + mode: MessagePublishMode; + body?: string | object; + onSave: (v: string | object) => void; +} + +export function LivePublish({ height, mode, body, addr, onSave }: Props) { + const txt = useMemo(() => { + if (mode === MessagePublishMode.JSON) { + return body ? JSON.stringify(body, null, 2) : '{ }'; + } + return body == null ? '' : `${body}`; + }, [mode, body]); + + const doSave = (v: string) => { + if (mode === MessagePublishMode.JSON) { + onSave(JSON.parse(v)); + } else { + onSave(v); + } + }; + + const onPublishClicked = async () => { + if (mode === MessagePublishMode.Influx) { + if (addr?.scope !== 'stream') { + alert('expected stream scope!'); + return; + } + return getBackendSrv().post(`api/live/push/${addr.namespace}`, body); + } + + if (!isValidLiveChannelAddress(addr)) { + alert('invalid address'); + return; + } + + const rsp = await getGrafanaLiveSrv().publish(addr, body); + console.log('onPublishClicked (response from publish)', rsp); + }; + + return ( + <> + +
+ +
+ + ); +} diff --git a/public/app/plugins/panel/live/module.tsx b/public/app/plugins/panel/live/module.tsx index 596083f450c..e77d6c3fa18 100644 --- a/public/app/plugins/panel/live/module.tsx +++ b/public/app/plugins/panel/live/module.tsx @@ -2,7 +2,7 @@ import { PanelPlugin } from '@grafana/data'; import { LiveChannelEditor } from './LiveChannelEditor'; import { LivePanel } from './LivePanel'; -import { LivePanelOptions, MessageDisplayMode } from './types'; +import { LivePanelOptions, MessageDisplayMode, MessagePublishMode } from './types'; export const plugin = new PanelPlugin(LivePanel).setPanelOptions((builder) => { builder.addCustomEditor({ @@ -16,7 +16,7 @@ export const plugin = new PanelPlugin(LivePanel).setPanelOptio builder .addRadio({ - path: 'message', + path: 'display', name: 'Show Message', description: 'Display the last message received on this channel', settings: { @@ -29,10 +29,17 @@ export const plugin = new PanelPlugin(LivePanel).setPanelOptio }, defaultValue: MessageDisplayMode.JSON, }) - .addBooleanSwitch({ + .addRadio({ path: 'publish', - name: 'Show Publish', + name: 'Publish', description: 'Display a form to publish values', - defaultValue: false, + settings: { + options: [ + { value: MessagePublishMode.None, label: 'None' }, + { value: MessagePublishMode.JSON, label: 'JSON' }, + { value: MessagePublishMode.Influx, label: 'Influx' }, + ], + }, + defaultValue: MessagePublishMode.None, }); }); diff --git a/public/app/plugins/panel/live/types.ts b/public/app/plugins/panel/live/types.ts index a4aba607542..abf0ce68bed 100644 --- a/public/app/plugins/panel/live/types.ts +++ b/public/app/plugins/panel/live/types.ts @@ -7,9 +7,15 @@ export enum MessageDisplayMode { None = 'none', // do not display } +export enum MessagePublishMode { + None = 'none', // do not display + JSON = 'json', // formatted JSON + Influx = 'influx', // influx line protocol +} + export interface LivePanelOptions { channel?: LiveChannelAddress; - message?: MessageDisplayMode; - publish?: boolean; - json?: any; // object + display?: MessageDisplayMode; + publish?: MessagePublishMode; + message?: string | object; // likely JSON }