Browse Source

Merge branch 'more-options' into 'main'

UI for all options

See merge request chartwerk/grafana-chartwerk-panel!6
merge-requests/7/merge
Alexander Velikiy 3 years ago
parent
commit
8b50017393
  1. 102
      src/components/editors/IconsEditor.tsx
  2. 5
      src/components/editors/NotSupportedText.tsx
  3. 132
      src/components/editors/ThresholdsEditor.tsx
  4. 62
      src/components/editors/UseMetricEditor.tsx
  5. 85
      src/module.ts

102
src/components/editors/IconsEditor.tsx

@ -121,7 +121,6 @@ export function IconsEditor({ onChange, value, context }: StandardEditorProps<Ic
}; };
const onConditionChange = (iconIdx: number, conditionIdx: number, field: keyof IconConfig, value: any) => { const onConditionChange = (iconIdx: number, conditionIdx: number, field: keyof IconConfig, value: any) => {
console.log(value);
// @ts-ignore // @ts-ignore
icons[iconIdx][field][conditionIdx] = value; icons[iconIdx][field][conditionIdx] = value;
@ -138,52 +137,63 @@ export function IconsEditor({ onChange, value, context }: StandardEditorProps<Ic
{icons.map((icon, iconIdx) => { {icons.map((icon, iconIdx) => {
return ( return (
<div key={iconIdx} className={styles.icon}> <div key={iconIdx} className={styles.icon}>
<IconButton name="trash-alt" onClick={() => removeIcon(iconIdx)}></IconButton> <IconButton
<Input name="trash-alt"
type="url" onClick={() => removeIcon(iconIdx)}
placeholder="Image URL" tooltip="Delete Icon"
value={icon.url} ></IconButton>
onChange={(evt) => onIconFieldChange(iconIdx, 'url', (evt.target as any).value)} <div className={styles.row}>
/> <Input
type="url"
placeholder="Image URL"
value={icon.url}
onChange={(evt) => onIconFieldChange(iconIdx, 'url', (evt.target as any).value)}
/>
</div>
<RadioButtonGroup <RadioButtonGroup
value={icon.position} value={icon.position}
options={positionOptions} options={positionOptions}
onChange={(newVal) => onIconFieldChange(iconIdx, 'position', newVal)} onChange={(newVal) => onIconFieldChange(iconIdx, 'position', newVal)}
/> />
<Slider <div className={styles.slider}>
value={icon.size} <Slider
min={1} value={icon.size}
max={100} min={1}
step={1} max={100}
onAfterChange={(newVal) => onIconFieldChange(iconIdx, 'size', newVal)} step={1}
/> onAfterChange={(newVal) => onIconFieldChange(iconIdx, 'size', newVal)}
/>
</div>
{icon.conditions.map((condition, conditionIdx) => { {icon.conditions.map((condition, conditionIdx) => {
return ( return (
<HorizontalGroup key={conditionIdx}> <div className={styles.condition}>
<FieldNamePicker <HorizontalGroup key={conditionIdx} >
context={context} <FieldNamePicker
value={icon.metrics[conditionIdx]} context={context}
onChange={(newVal: any) => onConditionChange(iconIdx, conditionIdx, 'metrics', newVal)} value={icon.metrics[conditionIdx]}
item={fieldNamePickerSettings} onChange={(newVal: any) => onConditionChange(iconIdx, conditionIdx, 'metrics', newVal)}
/> item={fieldNamePickerSettings}
<RadioButtonGroup />
value={icon.conditions[conditionIdx]} <RadioButtonGroup
options={conditionOptions} value={icon.conditions[conditionIdx]}
onChange={(newVal) => onConditionChange(iconIdx, conditionIdx, 'conditions', newVal)} options={conditionOptions}
/> onChange={(newVal) => onConditionChange(iconIdx, conditionIdx, 'conditions', newVal)}
<Input />
placeholder="value" <Input
value={icon.values[conditionIdx]} placeholder="value"
onChange={(evt) => value={icon.values[conditionIdx]}
onConditionChange(iconIdx, conditionIdx, 'values', (evt.target as any).value) onChange={(evt) =>
} onConditionChange(iconIdx, conditionIdx, 'values', (evt.target as any).value)
/> }
<IconButton />
name="trash-alt" <IconButton
onClick={() => removeCondition(iconIdx, conditionIdx)} name="trash-alt"
></IconButton> onClick={() => removeCondition(iconIdx, conditionIdx)}
</HorizontalGroup> tooltip="Delete Condition"
></IconButton>
</HorizontalGroup>
</div>
); );
})} })}
<Button variant="secondary" onClick={() => addCondition(iconIdx)}> <Button variant="secondary" onClick={() => addCondition(iconIdx)}>
@ -206,6 +216,9 @@ export function IconsEditor({ onChange, value, context }: StandardEditorProps<Ic
interface IconsEditorStyles { interface IconsEditorStyles {
icons: string; icons: string;
icon: string; icon: string;
condition: string;
slider: string;
row: string;
} }
const getStyles = stylesFactory((theme: GrafanaTheme): IconsEditorStyles => { const getStyles = stylesFactory((theme: GrafanaTheme): IconsEditorStyles => {
@ -221,9 +234,18 @@ const getStyles = stylesFactory((theme: GrafanaTheme): IconsEditorStyles => {
padding-bottom: ${theme.spacing.formSpacingBase * 2}px; padding-bottom: ${theme.spacing.formSpacingBase * 2}px;
&:last-child { &:last-child {
border: none;
margin-bottom: 0; margin-bottom: 0;
} }
`, `,
condition: css`
margin-bottom: ${theme.spacing.xxs};
`,
slider: css`
padding-bottom: ${theme.spacing.xxs};
`,
row: css`
margin-bottom: ${theme.spacing.sm};
`,
}; };
}); });

5
src/components/editors/NotSupportedText.tsx

@ -0,0 +1,5 @@
import React from 'react';
export function NotSupportedText() {
return <div>To be supported soon...</div>
}

132
src/components/editors/ThresholdsEditor.tsx

@ -1,21 +1,25 @@
import { FieldNamePicker } from '../../grafana/MatchersUI/FieldNamePicker'; import { FieldNamePicker } from '../../grafana/MatchersUI/FieldNamePicker';
import { StandardEditorProps } from '@grafana/data'; import { GrafanaTheme, StandardEditorProps } from '@grafana/data';
import { import {
Button, Button,
ColorPicker, ColorPicker,
HorizontalGroup, HorizontalGroup,
IconButton, IconButton,
InlineField, InlineField,
InlineLabel,
InlineSwitch, InlineSwitch,
Input, Input,
stylesFactory,
ThemeContext, ThemeContext,
} from '@grafana/ui'; } from '@grafana/ui';
import React from 'react'; import React from 'react';
import { css } from 'emotion';
import * as _ from 'lodash'; import * as _ from 'lodash';
interface Props { interface Props {
defaultColor: string; defaultColor: string;
arcBackground: string; arcBackground: string;
@ -65,76 +69,72 @@ export function ThresholdsEditor({ onChange, value, context }: StandardEditorPro
return ( return (
<ThemeContext.Consumer> <ThemeContext.Consumer>
{() => { {(theme) => {
// const styles = getStyles(theme.v1); const styles = getStyles(theme.v1);
return ( return (
<div> <div>
<Input <InlineField label="Default Gauge Color">
type="text" <InlineLabel>
value={'Default Gauge Color'} <ColorPicker
disabled color={config.defaultColor}
prefix={ onChange={(val) => onFieldChange('defaultColor', val)}
<div> enableNamedColors={false}
<ColorPicker />
color={config.defaultColor} </InlineLabel>
onChange={(val) => onFieldChange('defaultColor', val)} </InlineField>
enableNamedColors={true}
/> <InlineField label="Arc Background">
</div> <InlineLabel>
} <ColorPicker
/> color={config.arcBackground}
<Input onChange={(val) => onFieldChange('arcBackground', val)}
type="text" enableNamedColors={false}
value={'Arc Background'} />
disabled </InlineLabel>
prefix={ </InlineField>
<div>
<ColorPicker
color={config.arcBackground}
onChange={(val) => onFieldChange('arcBackground', val)}
enableNamedColors={true}
/>
</div>
}
/>
{config.thresholds.map((threshold, thresholdIdx) => { {config.thresholds.map((threshold, thresholdIdx) => {
return ( return (
<HorizontalGroup key={thresholdIdx}> <HorizontalGroup key={thresholdIdx}>
<Input
type="text"
value={'Color'}
disabled
prefix={
<div>
<ColorPicker
color={threshold.color}
onChange={(newVal) => onThresholdFieldChange(thresholdIdx, 'color', newVal)}
enableNamedColors={true}
/>
</div>
}
/>
<InlineField label="Use metric"> <InlineField label="Use metric">
<InlineSwitch <InlineSwitch
value={threshold.useMetric} value={threshold.useMetric}
onChange={(evt) => onThresholdFieldChange(thresholdIdx, 'useMetric', (evt.target as any).checked)} onChange={(evt) => onThresholdFieldChange(thresholdIdx, 'useMetric', (evt.target as any).checked)}
/> />
</InlineField> </InlineField>
{threshold.useMetric ? ( <InlineField>
<FieldNamePicker {threshold.useMetric ? (
context={context} <FieldNamePicker
value={threshold.metricName as string} context={context}
onChange={(newVal: any) => onThresholdFieldChange(thresholdIdx, 'metricName', newVal)} value={threshold.metricName as string}
item={fieldNamePickerSettings} onChange={(newVal: any) => onThresholdFieldChange(thresholdIdx, 'metricName', newVal)}
/> item={fieldNamePickerSettings}
) : ( />
<Input ) : (
placeholder="value" <Input
value={threshold.value} placeholder="value"
onChange={(evt) => onThresholdFieldChange(thresholdIdx, 'value', (evt.target as any).value)} value={threshold.value}
/> onChange={(evt) => onThresholdFieldChange(thresholdIdx, 'value', (evt.target as any).value)}
)} />
<IconButton name="trash-alt" onClick={() => removeThreshold(thresholdIdx)}></IconButton> )}
</InlineField>
<InlineField label="Color">
<InlineLabel>
<ColorPicker
color={threshold.color}
onChange={(newVal) => onThresholdFieldChange(thresholdIdx, 'color', newVal)}
enableNamedColors={false}
/>
</InlineLabel>
</InlineField>
<InlineField>
<InlineLabel width={0} className={styles.deleteButton}>
<IconButton
name="trash-alt"
onClick={() => removeThreshold(thresholdIdx)}
tooltip="Delete Threshold"
></IconButton>
</InlineLabel>
</InlineField>
</HorizontalGroup> </HorizontalGroup>
); );
})} })}
@ -147,3 +147,15 @@ export function ThresholdsEditor({ onChange, value, context }: StandardEditorPro
</ThemeContext.Consumer> </ThemeContext.Consumer>
); );
} }
interface ThresholdsEditorStyles {
deleteButton: string;
}
const getStyles = stylesFactory((theme: GrafanaTheme): ThresholdsEditorStyles => {
return {
deleteButton: css`
margin-right: 0px!important;
`,
};
});

62
src/components/editors/UseMetricEditor.tsx

@ -0,0 +1,62 @@
import { FieldNamePicker } from '../../grafana/MatchersUI/FieldNamePicker';
import { StandardEditorProps } from '@grafana/data';
import {
HorizontalGroup,
InlineField,
InlineSwitch,
Input,
} from '@grafana/ui';
import React from 'react';
import * as _ from 'lodash';
type UseMetricConfig = {
useMetric: boolean;
value?: number;
metricName?: string;
};
const fieldNamePickerSettings = {
settings: { width: 24 },
} as any;
export function UseMetricEditor({ onChange, value, context }: StandardEditorProps<UseMetricConfig>) {
const config = value;
const onFieldChange = (field: keyof UseMetricConfig, value: any) => {
// @ts-ignore
config[field] = value;
onChange(config);
}
return (
<HorizontalGroup>
<InlineField label="Use metric">
<InlineSwitch
value={config.useMetric}
onChange={(evt) => onFieldChange('useMetric', (evt.target as any).checked)}
/>
</InlineField>
<InlineField>
{config.useMetric ? (
<FieldNamePicker
context={context}
value={config.metricName as string}
onChange={(newVal: any) => onFieldChange('metricName', newVal)}
item={fieldNamePickerSettings}
/>
) : (
<Input
placeholder="value"
value={config.value}
onChange={(evt) => onFieldChange('value', (evt.target as any).value)}
/>
)}
</InlineField>
</HorizontalGroup>
)
}

85
src/module.ts

@ -1,11 +1,15 @@
import { PanelOptions, Pod } from './types'; import { PanelOptions, Pod } from './types';
import { Panel } from './components/Panel'; import { Panel } from './components/Panel';
import { IconsEditor } from './components/editors/IconsEditor'; import { IconsEditor } from './components/editors/IconsEditor';
import { ThresholdsEditor } from './components/editors/ThresholdsEditor'; import { ThresholdsEditor } from './components/editors/ThresholdsEditor';
import { NotSupportedText } from './components/editors/NotSupportedText';
import { UseMetricEditor } from './components/editors/UseMetricEditor';
import { PanelPlugin } from '@grafana/data'; import { PanelPlugin } from '@grafana/data';
export const plugin = new PanelPlugin<PanelOptions>(Panel).setPanelOptions((builder) => { export const plugin = new PanelPlugin<PanelOptions>(Panel).setPanelOptions((builder) => {
return builder return builder
.addRadio({ .addRadio({
@ -18,21 +22,26 @@ export const plugin = new PanelPlugin<PanelOptions>(Panel).setPanelOptions((buil
{ {
label: 'Gauge', label: 'Gauge',
value: Pod.GAUGE, value: Pod.GAUGE,
description: 'Enable gauge pod',
}, },
{ {
label: 'Line', label: 'Line',
value: Pod.LINE, value: Pod.LINE,
description: 'Enable line pod',
}, },
{ {
label: 'Bar', label: 'Bar',
value: Pod.BAR, value: Pod.BAR,
description: 'Enable bar pod',
}, },
], ],
}, },
}) })
.addCustomEditor({
id: 'notSupportedText',
name: 'This visualization is not supported',
category: ['Visualization'],
path: '',
showIf: (config) => config.visualizationType !== Pod.GAUGE,
editor: NotSupportedText as any,
})
.addFieldNamePicker({ .addFieldNamePicker({
name: 'Value', name: 'Value',
@ -40,46 +49,55 @@ export const plugin = new PanelPlugin<PanelOptions>(Panel).setPanelOptions((buil
category: ['Extremum'], category: ['Extremum'],
showIf: (config) => config.visualizationType === Pod.GAUGE, showIf: (config) => config.visualizationType === Pod.GAUGE,
}) })
// TODO: defaults?
.addNumberInput({ .addCustomEditor({
path: 'gauge.min.value', id: 'min',
name: 'Min',
category: ['Extremum'],
showIf: (config) => config.visualizationType === Pod.GAUGE && !config.gauge.min.useMetric,
})
.addFieldNamePicker({
name: 'Min', name: 'Min',
path: 'gauge.min.metricName', path: 'gauge.min',
category: ['Extremum'], category: ['Extremum'],
showIf: (config) => config.visualizationType === Pod.GAUGE && config.gauge.min.useMetric, showIf: (config) => config.visualizationType === Pod.GAUGE,
}) editor: UseMetricEditor as any,
.addBooleanSwitch({ }).
path: 'gauge.min.useMetric', addCustomEditor({
name: 'Use metric', id: 'max',
defaultValue: false, name: 'Max',
path: 'gauge.max',
category: ['Extremum'], category: ['Extremum'],
showIf: (config) => config.visualizationType === Pod.GAUGE, showIf: (config) => config.visualizationType === Pod.GAUGE,
editor: UseMetricEditor as any,
}) })
.addNumberInput({ // note: `gauge.unit` will contain unit name, not it's string representation
path: 'gauge.max.value', // to format value with unit, use `getValueFormat` function from `@grafana/data`
name: 'Max', .addUnitPicker({
category: ['Extremum'], path: 'gauge.unit',
showIf: (config) => config.visualizationType === Pod.GAUGE && !config.gauge.max.useMetric, name: 'Unit',
category: ['Value Format'],
showIf: (config) => config.visualizationType === Pod.GAUGE,
}) })
.addFieldNamePicker({ .addNumberInput({
name: 'Max', path: 'gauge.decimals',
path: 'gauge.max.metricName', name: 'Decimals',
category: ['Extremum'], settings: {
showIf: (config) => config.visualizationType === Pod.GAUGE && config.gauge.max.useMetric, placeholder: 'auto',
min: 0,
max: 5,
},
category: ['Value Format'],
showIf: (config) => config.visualizationType === Pod.GAUGE,
}) })
.addBooleanSwitch({ .addSliderInput({
path: 'gauge.max.useMetric', path: 'gauge.size',
name: 'Use metric', defaultValue: 20,
defaultValue: false, name: 'Size (px)',
category: ['Extremum'], settings: {
min: 1,
max: 50,
},
category: ['Value Format'],
showIf: (config) => config.visualizationType === Pod.GAUGE, showIf: (config) => config.visualizationType === Pod.GAUGE,
}) })
.addBooleanSwitch({ .addBooleanSwitch({
path: 'gauge.reversed', path: 'gauge.reversed',
name: 'Reversed', name: 'Reversed',
@ -87,6 +105,7 @@ export const plugin = new PanelPlugin<PanelOptions>(Panel).setPanelOptions((buil
category: ['Direction'], category: ['Direction'],
showIf: (config) => config.visualizationType === Pod.GAUGE, showIf: (config) => config.visualizationType === Pod.GAUGE,
}) })
.addCustomEditor({ .addCustomEditor({
id: 'icons', id: 'icons',
path: 'gauge.icons', path: 'gauge.icons',

Loading…
Cancel
Save