mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
TextPanel: Support code formats (#53850)
This commit is contained in:
parent
4d4ecd7fec
commit
fe61a97c9d
322
devenv/dev-dashboards/panel-text/text-options.json
Normal file
322
devenv/dev-dashboards/panel-text/text-options.json
Normal file
@ -0,0 +1,322 @@
|
||||
{
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "grafana"
|
||||
},
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"target": {
|
||||
"limit": 100,
|
||||
"matchAny": false,
|
||||
"tags": [],
|
||||
"type": "dashboard"
|
||||
},
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 0,
|
||||
"id": 1348,
|
||||
"links": [],
|
||||
"liveNow": false,
|
||||
"panels": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 4,
|
||||
"options": {
|
||||
"code": {
|
||||
"language": "plaintext",
|
||||
"showLineNumbers": false,
|
||||
"showMiniMap": false
|
||||
},
|
||||
"content": "## Data center = $datacenter\n\n### server = $server\n\n#### pod = $pod\n\n---\ntext = $Text",
|
||||
"mode": "markdown"
|
||||
},
|
||||
"pluginVersion": "9.2.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk"
|
||||
}
|
||||
],
|
||||
"title": "Markdown (with variables)",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 0
|
||||
},
|
||||
"id": 5,
|
||||
"options": {
|
||||
"code": {
|
||||
"language": "json",
|
||||
"showLineNumbers": true,
|
||||
"showMiniMap": false
|
||||
},
|
||||
"content": "{\n \"datacenter\": $datacenter,\n \"server\": $server,\n \"pod\": $pod\n \"text\": $Text\n}\n",
|
||||
"mode": "code"
|
||||
},
|
||||
"pluginVersion": "9.2.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk"
|
||||
}
|
||||
],
|
||||
"title": "JSON (with variables)",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 9
|
||||
},
|
||||
"id": 6,
|
||||
"options": {
|
||||
"code": {
|
||||
"language": "plaintext",
|
||||
"showLineNumbers": false,
|
||||
"showMiniMap": false
|
||||
},
|
||||
"content": "<h3>Data center</h3>\n<p>$datacenter</p>\n\n<h3>server</h3>\n<p>$server</p>\n\n<h3>pod</h3>\n<p>$pod</p>\n\n<h3>Text</h3>\n<p>$Text</p>",
|
||||
"mode": "html"
|
||||
},
|
||||
"pluginVersion": "9.2.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk"
|
||||
}
|
||||
],
|
||||
"title": "HTML (with variables)",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 9
|
||||
},
|
||||
"id": 7,
|
||||
"options": {
|
||||
"code": {
|
||||
"language": "markdown",
|
||||
"showLineNumbers": true,
|
||||
"showMiniMap": true
|
||||
},
|
||||
"content": "## Data center\n$datacenter\n\n### server\n$server\n\n#### pod = \n$pod\n",
|
||||
"mode": "code"
|
||||
},
|
||||
"pluginVersion": "9.2.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk"
|
||||
}
|
||||
],
|
||||
"title": "Markdown (code w/ with variables)",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"refresh": false,
|
||||
"schemaVersion": 37,
|
||||
"style": "dark",
|
||||
"tags": [],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"current": {
|
||||
"selected": false,
|
||||
"text": [
|
||||
"All"
|
||||
],
|
||||
"value": [
|
||||
"$__all"
|
||||
]
|
||||
},
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"definition": "*",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"multi": true,
|
||||
"name": "datacenter",
|
||||
"options": [],
|
||||
"query": {
|
||||
"query": "*",
|
||||
"refId": "gdev-testdata-datacenter-Variable-Query"
|
||||
},
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 0,
|
||||
"tagValuesQuery": "",
|
||||
"tagsQuery": "",
|
||||
"type": "query",
|
||||
"useTags": false
|
||||
},
|
||||
{
|
||||
"current": {
|
||||
"selected": false,
|
||||
"text": [
|
||||
"All"
|
||||
],
|
||||
"value": [
|
||||
"$__all"
|
||||
]
|
||||
},
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"definition": "$datacenter.*",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"multi": true,
|
||||
"name": "server",
|
||||
"options": [],
|
||||
"query": {
|
||||
"query": "$datacenter.*",
|
||||
"refId": "gdev-testdata-server-Variable-Query"
|
||||
},
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 0,
|
||||
"tagValuesQuery": "",
|
||||
"tagsQuery": "",
|
||||
"type": "query",
|
||||
"useTags": false
|
||||
},
|
||||
{
|
||||
"current": {
|
||||
"selected": false,
|
||||
"text": [
|
||||
"AAA",
|
||||
"ACB"
|
||||
],
|
||||
"value": [
|
||||
"AAA",
|
||||
"ACB"
|
||||
]
|
||||
},
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"definition": "$datacenter.$server.*",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"multi": true,
|
||||
"name": "pod",
|
||||
"options": [],
|
||||
"query": {
|
||||
"query": "$datacenter.$server.*",
|
||||
"refId": "gdev-testdata-pod-Variable-Query"
|
||||
},
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 0,
|
||||
"tagValuesQuery": "",
|
||||
"tagsQuery": "",
|
||||
"type": "query",
|
||||
"useTags": false
|
||||
},
|
||||
{
|
||||
"current": {
|
||||
"selected": false,
|
||||
"text": "temp",
|
||||
"value": "temp"
|
||||
},
|
||||
"hide": 0,
|
||||
"name": "Text",
|
||||
"options": [
|
||||
{
|
||||
"selected": true,
|
||||
"text": "temp",
|
||||
"value": "temp"
|
||||
}
|
||||
],
|
||||
"query": "temp",
|
||||
"skipUrlSync": false,
|
||||
"type": "textbox"
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"from": "now-1h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {
|
||||
"refresh_intervals": [
|
||||
"10s",
|
||||
"30s",
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"1h",
|
||||
"2h",
|
||||
"1d"
|
||||
]
|
||||
},
|
||||
"timezone": "",
|
||||
"title": "Text options",
|
||||
"uid": "WZ7AhQiVz",
|
||||
"version": 1,
|
||||
"weekStart": ""
|
||||
}
|
@ -15,6 +15,26 @@ weight: 1100
|
||||
|
||||
# Text
|
||||
|
||||
The text panel visualization lets you make information and description panels for your dashboards.
|
||||
The text panel enables you to directly include text or HTML in your dashboards. This can be used to add contextual information and descriptions or embed complex HTML.
|
||||
|
||||
In **Mode**, select whether you want to use markdown or HTML to style your text, then enter content in the box below. Grafana includes a title and paragraph to help you get started, or you can paste content in from another editor.
|
||||
## Mode
|
||||
|
||||
**Mode** determines how embedded content appears.
|
||||
|
||||
### Markdown
|
||||
|
||||
This option formats the content as [markdown](https://en.wikipedia.org/wiki/Markdown).
|
||||
|
||||
### HTML
|
||||
|
||||
This setting renders the content as [sanitized](https://github.com/grafana/grafana/blob/code-in-text-panel/packages/grafana-data/src/text/sanitize.ts) HTML. If you require more direct control over the output, you can set the
|
||||
[disable_sanitize_html]({{< relref "../setup-grafana/configure-grafana/#disable_sanitize_html" >}}) flag which enables you to directly enter HTML.
|
||||
|
||||
### Code
|
||||
|
||||
This setting renders content inside a read-only code editor. Select an appropriate language to apply syntax highlighting
|
||||
to the embedded text.
|
||||
|
||||
## Variables
|
||||
|
||||
[Variables]({{< relref "../variables/syntax/" >}}) in the content will be expanded for display.
|
||||
|
@ -4,13 +4,13 @@ import DangerouslySetHtmlContent from 'dangerously-set-html-content';
|
||||
import { debounce } from 'lodash';
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { PanelProps, renderTextPanelMarkdown, textUtil } from '@grafana/data';
|
||||
import { GrafanaTheme2, PanelProps, renderTextPanelMarkdown, textUtil } from '@grafana/data';
|
||||
// Utils
|
||||
import { CustomScrollbar, stylesFactory } from '@grafana/ui';
|
||||
import { CustomScrollbar, CodeEditor, stylesFactory, ThemeContext } from '@grafana/ui';
|
||||
import config from 'app/core/config';
|
||||
|
||||
// Types
|
||||
import { PanelOptions, TextMode } from './models.gen';
|
||||
import { defaultCodeOptions, PanelOptions, TextMode } from './models.gen';
|
||||
|
||||
export interface Props extends PanelProps<PanelOptions> {}
|
||||
|
||||
@ -19,6 +19,8 @@ interface State {
|
||||
}
|
||||
|
||||
export class TextPanel extends PureComponent<Props, State> {
|
||||
static contextType = ThemeContext;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
@ -63,8 +65,8 @@ export class TextPanel extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
interpolateString(content: string): string {
|
||||
const { replaceVariables } = this.props;
|
||||
return replaceVariables(content, {}, 'html');
|
||||
const { replaceVariables, options } = this.props;
|
||||
return replaceVariables(content, {}, options.code?.language === 'json' ? 'json' : 'html');
|
||||
}
|
||||
|
||||
sanitizeString(content: string): string {
|
||||
@ -80,6 +82,8 @@ export class TextPanel extends PureComponent<Props, State> {
|
||||
|
||||
if (mode === TextMode.HTML) {
|
||||
return this.prepareHTML(content);
|
||||
} else if (mode === TextMode.Code) {
|
||||
return this.interpolateString(content);
|
||||
}
|
||||
|
||||
return this.prepareMarkdown(content);
|
||||
@ -87,23 +91,46 @@ export class TextPanel extends PureComponent<Props, State> {
|
||||
|
||||
render() {
|
||||
const { html } = this.state;
|
||||
const styles = getStyles();
|
||||
const { options } = this.props;
|
||||
const styles = getStyles(this.context);
|
||||
|
||||
if (options.mode === TextMode.Code) {
|
||||
const { width, height } = this.props;
|
||||
const code = options.code ?? defaultCodeOptions;
|
||||
return (
|
||||
<CodeEditor
|
||||
key={`${code.showLineNumbers}/${code.showMiniMap}`} // will reinit-on change
|
||||
value={html}
|
||||
language={code.language ?? defaultCodeOptions.language!}
|
||||
width={width}
|
||||
height={height}
|
||||
containerStyles={styles.codeEditorContainer}
|
||||
showMiniMap={code.showMiniMap}
|
||||
showLineNumbers={code.showLineNumbers}
|
||||
readOnly={true} // future
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CustomScrollbar autoHeightMin="100%">
|
||||
<DangerouslySetHtmlContent
|
||||
html={html}
|
||||
className={cx('markdown-html', styles.content)}
|
||||
data-testid="TextPanel-converted-content"
|
||||
/>
|
||||
<DangerouslySetHtmlContent html={html} className={styles.markdown} data-testid="TextPanel-converted-content" />
|
||||
</CustomScrollbar>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyles = stylesFactory(() => {
|
||||
return {
|
||||
content: css`
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme2) => ({
|
||||
codeEditorContainer: css`
|
||||
.monaco-editor .margin,
|
||||
.monaco-editor-background {
|
||||
background-color: ${theme.colors.background.primary};
|
||||
}
|
||||
`,
|
||||
markdown: cx(
|
||||
'markdown-html',
|
||||
css`
|
||||
height: 100%;
|
||||
`,
|
||||
};
|
||||
});
|
||||
`
|
||||
),
|
||||
}));
|
||||
|
@ -22,9 +22,20 @@ Panel: thema.#Lineage & {
|
||||
{
|
||||
schemas: [
|
||||
{
|
||||
TextMode: "html" | "markdown" @cuetsy(kind="enum",memberNames="HTML|Markdown")
|
||||
TextMode: "html" | "markdown" | "code" @cuetsy(kind="enum",memberNames="HTML|Markdown|Code")
|
||||
|
||||
CodeLanguage: "json" | "yaml" | "xml" | "typescript" | "sql" | "go" | "markdown" | "html" | *"plaintext" @cuetsy(kind="enum")
|
||||
|
||||
CodeOptions: {
|
||||
// The language passed to monaco code editor
|
||||
language: CodeLanguage
|
||||
showLineNumbers: bool | *false
|
||||
showMiniMap: bool | *false
|
||||
} @cuetsy(kind="interface")
|
||||
|
||||
PanelOptions: {
|
||||
mode: TextMode | *"markdown"
|
||||
code?: CodeOptions
|
||||
content: string | *"""
|
||||
# Title
|
||||
|
||||
|
@ -9,11 +9,39 @@ export const PanelModelVersion = Object.freeze([0, 0]);
|
||||
|
||||
|
||||
export enum TextMode {
|
||||
Code = 'code',
|
||||
HTML = 'html',
|
||||
Markdown = 'markdown',
|
||||
}
|
||||
|
||||
export enum CodeLanguage {
|
||||
Go = 'go',
|
||||
Html = 'html',
|
||||
Json = 'json',
|
||||
Markdown = 'markdown',
|
||||
Plaintext = 'plaintext',
|
||||
Sql = 'sql',
|
||||
Typescript = 'typescript',
|
||||
Xml = 'xml',
|
||||
Yaml = 'yaml',
|
||||
}
|
||||
|
||||
export const defaultCodeLanguage: CodeLanguage = CodeLanguage.Plaintext;
|
||||
|
||||
export interface CodeOptions {
|
||||
language: CodeLanguage;
|
||||
showLineNumbers: boolean;
|
||||
showMiniMap: boolean;
|
||||
}
|
||||
|
||||
export const defaultCodeOptions: Partial<CodeOptions> = {
|
||||
language: CodeLanguage.Plaintext,
|
||||
showLineNumbers: false,
|
||||
showMiniMap: false,
|
||||
};
|
||||
|
||||
export interface PanelOptions {
|
||||
code?: CodeOptions;
|
||||
content: string;
|
||||
mode: TextMode;
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import { PanelPlugin } from '@grafana/data';
|
||||
|
||||
import { TextPanel } from './TextPanel';
|
||||
import { TextPanelEditor } from './TextPanelEditor';
|
||||
import { defaultPanelOptions, PanelOptions, TextMode } from './models.gen';
|
||||
import { CodeLanguage, defaultCodeOptions, defaultPanelOptions, PanelOptions, TextMode } from './models.gen';
|
||||
import { textPanelMigrationHandler } from './textPanelMigrationHandler';
|
||||
|
||||
export const plugin = new PanelPlugin<PanelOptions>(TextPanel)
|
||||
@ -16,10 +16,35 @@ export const plugin = new PanelPlugin<PanelOptions>(TextPanel)
|
||||
options: [
|
||||
{ value: TextMode.Markdown, label: 'Markdown' },
|
||||
{ value: TextMode.HTML, label: 'HTML' },
|
||||
{ value: TextMode.Code, label: 'Code' },
|
||||
],
|
||||
},
|
||||
defaultValue: defaultPanelOptions.mode,
|
||||
})
|
||||
.addSelect({
|
||||
path: 'code.language',
|
||||
name: 'Language',
|
||||
settings: {
|
||||
options: Object.values(CodeLanguage).map((v) => ({
|
||||
value: v,
|
||||
label: v,
|
||||
})),
|
||||
},
|
||||
defaultValue: defaultCodeOptions.language,
|
||||
showIf: (v) => v.mode === TextMode.Code,
|
||||
})
|
||||
.addBooleanSwitch({
|
||||
path: 'code.showLineNumbers',
|
||||
name: 'Show line numbers',
|
||||
defaultValue: defaultCodeOptions.showLineNumbers,
|
||||
showIf: (v) => v.mode === TextMode.Code,
|
||||
})
|
||||
.addBooleanSwitch({
|
||||
path: 'code.showMiniMap',
|
||||
name: 'Show mini map',
|
||||
defaultValue: defaultCodeOptions.showMiniMap,
|
||||
showIf: (v) => v.mode === TextMode.Code,
|
||||
})
|
||||
.addCustomEditor({
|
||||
id: 'content',
|
||||
path: 'content',
|
||||
|
Loading…
Reference in New Issue
Block a user