mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
NewPanelEdit: Organise sidebar in tabs (#22870)
* Refactor value mappings UI to work better with new panel edit * TS fix * Experimenting with tabs in the sidebar * Small refactor and added Panel general settings * Merge fixes * fix fieldOptions being used instead of fieldConfig * Added icons to tabs (testing) * Only 3 tabs i think, panel specific options need ot exist in first tab, some style tweaks * Moved title and no value up * Updated * Render panel options in Options tab and add old options styles hack to display those vertically * Basic settings to Panel settings * Make nullcheck pass * Snaps bump * Fix standard configs not update * Organise sidebar better, add tmp NewPanelEditorContext to hide duplicate legacy options Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
This commit is contained in:
parent
c600a08524
commit
bdb5659977
@ -10,7 +10,7 @@ import { DataLinksListItem } from './DataLinksListItem';
|
||||
import { DataLinkEditorModalContent } from './DataLinkEditorModalContent';
|
||||
|
||||
interface DataLinksInlineEditorProps {
|
||||
links: DataLink[];
|
||||
links?: DataLink[];
|
||||
onChange: (links: DataLink[]) => void;
|
||||
suggestions: VariableSuggestion[];
|
||||
data: DataFrame[];
|
||||
@ -23,6 +23,9 @@ export const DataLinksInlineEditor: React.FC<DataLinksInlineEditorProps> = ({ li
|
||||
const styles = getDataLinksInlineEditorStyles(theme);
|
||||
|
||||
const onDataLinkChange = (index: number, link: DataLink) => {
|
||||
if (!links) {
|
||||
return;
|
||||
}
|
||||
const update = cloneDeep(links);
|
||||
update[index] = link;
|
||||
onChange(update);
|
||||
@ -48,6 +51,9 @@ export const DataLinksInlineEditor: React.FC<DataLinksInlineEditorProps> = ({ li
|
||||
};
|
||||
|
||||
const onDataLinkRemove = (index: number) => {
|
||||
if (!links) {
|
||||
return;
|
||||
}
|
||||
const update = cloneDeep(links);
|
||||
update.splice(index, 1);
|
||||
onChange(update);
|
||||
@ -55,7 +61,7 @@ export const DataLinksInlineEditor: React.FC<DataLinksInlineEditorProps> = ({ li
|
||||
|
||||
return (
|
||||
<>
|
||||
{links && (
|
||||
{links && links.length > 0 && (
|
||||
<div className={styles.wrapper}>
|
||||
{links.map((l, i) => {
|
||||
return (
|
||||
@ -84,7 +90,7 @@ export const DataLinksInlineEditor: React.FC<DataLinksInlineEditorProps> = ({ li
|
||||
>
|
||||
<DataLinkEditorModalContent
|
||||
index={editIndex}
|
||||
link={links[editIndex]}
|
||||
link={links![editIndex]}
|
||||
data={data}
|
||||
onChange={onDataLinkChange}
|
||||
onClose={() => setEditIndex(null)}
|
||||
|
@ -158,5 +158,5 @@ export const getStandardFieldConfigs = () => {
|
||||
shouldApply: () => true,
|
||||
};
|
||||
|
||||
return [unit, min, max, decimals, thresholds, mappings, title, noValue, links];
|
||||
return [unit, min, max, decimals, title, noValue, thresholds, mappings, links];
|
||||
};
|
||||
|
@ -17,6 +17,11 @@ export interface LayoutProps {
|
||||
justify?: Justify;
|
||||
}
|
||||
|
||||
export interface ContainerProps {
|
||||
padding?: Spacing;
|
||||
margin?: Spacing;
|
||||
}
|
||||
|
||||
export const Layout: React.FC<LayoutProps> = ({
|
||||
children,
|
||||
orientation = Orientation.Horizontal,
|
||||
@ -45,6 +50,12 @@ export const VerticalGroup: React.FC<Omit<LayoutProps, 'orientation'>> = ({ chil
|
||||
</Layout>
|
||||
);
|
||||
|
||||
export const Container: React.FC<ContainerProps> = ({ children, padding, margin }) => {
|
||||
const theme = useTheme();
|
||||
const styles = getContainerStyles(theme, padding, margin);
|
||||
return <div className={styles.wrapper}>{children}</div>;
|
||||
};
|
||||
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme, orientation: Orientation, spacing: Spacing, justify: Justify) => {
|
||||
return {
|
||||
layout: css`
|
||||
@ -64,3 +75,14 @@ const getStyles = stylesFactory((theme: GrafanaTheme, orientation: Orientation,
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
||||
const getContainerStyles = stylesFactory((theme: GrafanaTheme, padding?: Spacing, margin?: Spacing) => {
|
||||
const paddingSize = (padding && theme.spacing[padding]) || 0;
|
||||
const marginSize = (margin && theme.spacing[margin]) || 0;
|
||||
return {
|
||||
wrapper: css`
|
||||
margin: ${marginSize};
|
||||
padding: ${paddingSize};
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { FC } from 'react';
|
||||
import { css, cx } from 'emotion';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { selectThemeVariant, stylesFactory, useTheme } from '../../themes';
|
||||
import { stylesFactory, useTheme } from '../../themes';
|
||||
|
||||
export interface TabProps {
|
||||
label: string;
|
||||
@ -12,7 +12,6 @@ export interface TabProps {
|
||||
|
||||
const getTabStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
const colors = theme.colors;
|
||||
const tabBorderColor = selectThemeVariant({ dark: colors.dark9, light: colors.gray5 }, theme.type);
|
||||
|
||||
return {
|
||||
tabItem: css`
|
||||
@ -42,7 +41,7 @@ const getTabStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
}
|
||||
`,
|
||||
activeStyle: css`
|
||||
border-color: ${colors.orange} ${tabBorderColor} transparent;
|
||||
border-color: ${colors.orange} ${colors.pageHeaderBorder} transparent;
|
||||
background: ${colors.pageBg};
|
||||
color: ${colors.link};
|
||||
overflow: hidden;
|
||||
|
@ -155,7 +155,7 @@ exports[`TimePicker renders buttons correctly 1`] = `
|
||||
"orange": "#eb7b18",
|
||||
"orangeDark": "#ff780a",
|
||||
"pageBg": "#161719",
|
||||
"pageHeaderBorder": "#343436",
|
||||
"pageHeaderBorder": "#202226",
|
||||
"panelBg": "#212124",
|
||||
"purple": "#9933cc",
|
||||
"queryGreen": "#74e680",
|
||||
@ -464,7 +464,7 @@ exports[`TimePicker renders content correctly after beeing open 1`] = `
|
||||
"orange": "#eb7b18",
|
||||
"orangeDark": "#ff780a",
|
||||
"pageBg": "#161719",
|
||||
"pageHeaderBorder": "#343436",
|
||||
"pageHeaderBorder": "#202226",
|
||||
"panelBg": "#212124",
|
||||
"purple": "#9933cc",
|
||||
"queryGreen": "#74e680",
|
||||
|
@ -105,6 +105,7 @@ export * from './SingleStatShared/index';
|
||||
export { CallToActionCard } from './CallToActionCard/CallToActionCard';
|
||||
export { ContextMenu, ContextMenuItem, ContextMenuGroup, ContextMenuProps } from './ContextMenu/ContextMenu';
|
||||
export { DataLinksEditor } from './DataLinks/DataLinksEditor';
|
||||
export { DataLinksInlineEditor } from './DataLinks/DataLinksInlineEditor/DataLinksInlineEditor';
|
||||
export { DataLinkInput } from './DataLinks/DataLinkInput';
|
||||
export { DataLinksContextMenu } from './DataLinks/DataLinksContextMenu';
|
||||
export { SeriesIcon } from './Legend/SeriesIcon';
|
||||
@ -152,4 +153,4 @@ export { default as Forms, ButtonVariant } from './Forms';
|
||||
export { ValuePicker } from './ValuePicker/ValuePicker';
|
||||
export { fieldMatchersUI } from './MatchersUI/fieldMatchersUI';
|
||||
export { getStandardFieldConfigs } from './FieldConfigs/standardFieldConfigEditors';
|
||||
export { HorizontalGroup, VerticalGroup } from './Layout/Layout';
|
||||
export { HorizontalGroup, VerticalGroup, Container } from './Layout/Layout';
|
||||
|
@ -74,7 +74,7 @@ const darkTheme: GrafanaTheme = {
|
||||
linkHover: basicColors.white,
|
||||
linkExternal: basicColors.blue,
|
||||
headingColor: basicColors.gray4,
|
||||
pageHeaderBorder: basicColors.dark9,
|
||||
pageHeaderBorder: basicColors.gray15,
|
||||
panelBg: basicColors.dark4,
|
||||
|
||||
// Next-gen forms functional colors
|
||||
|
@ -100,7 +100,7 @@ exports[`ServerStats Should render table with stats 1`] = `
|
||||
className="css-13jkosq"
|
||||
>
|
||||
<li
|
||||
className="css-b418eg"
|
||||
className="css-14totda"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<i
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import {
|
||||
FieldConfigSource,
|
||||
@ -9,10 +9,10 @@ import {
|
||||
PanelPlugin,
|
||||
SelectableValue,
|
||||
} from '@grafana/data';
|
||||
import { Forms, fieldMatchersUI, ValuePicker } from '@grafana/ui';
|
||||
import { Forms, fieldMatchersUI, ValuePicker, useTheme } from '@grafana/ui';
|
||||
import { getDataLinksVariableSuggestions } from '../../../panel/panellinks/link_srv';
|
||||
import { OptionsGroup } from './OptionsGroup';
|
||||
import { OverrideEditor } from './OverrideEditor';
|
||||
import { css } from 'emotion';
|
||||
|
||||
interface Props {
|
||||
plugin: PanelPlugin;
|
||||
@ -26,50 +26,25 @@ interface Props {
|
||||
/**
|
||||
* Expects the container div to have size set and will fill it 100%
|
||||
*/
|
||||
export class FieldConfigEditor extends React.PureComponent<Props> {
|
||||
private setDefaultValue = (name: string, value: any, custom: boolean) => {
|
||||
const defaults = { ...this.props.config.defaults };
|
||||
const remove = value === undefined || value === null || '';
|
||||
export const OverrideFieldConfigEditor: React.FC<Props> = props => {
|
||||
const theme = useTheme();
|
||||
|
||||
if (custom) {
|
||||
if (defaults.custom) {
|
||||
if (remove) {
|
||||
defaults.custom = { ...defaults.custom };
|
||||
delete defaults.custom[name];
|
||||
} else {
|
||||
defaults.custom = { ...defaults.custom, [name]: value };
|
||||
}
|
||||
} else if (!remove) {
|
||||
defaults.custom = { [name]: value };
|
||||
}
|
||||
} else if (remove) {
|
||||
delete (defaults as any)[name];
|
||||
} else {
|
||||
(defaults as any)[name] = value;
|
||||
}
|
||||
|
||||
this.props.onChange({
|
||||
...this.props.config,
|
||||
defaults,
|
||||
});
|
||||
};
|
||||
|
||||
onOverrideChange = (index: number, override: any) => {
|
||||
const { config } = this.props;
|
||||
const onOverrideChange = (index: number, override: any) => {
|
||||
const { config } = props;
|
||||
let overrides = cloneDeep(config.overrides);
|
||||
overrides[index] = override;
|
||||
this.props.onChange({ ...config, overrides });
|
||||
props.onChange({ ...config, overrides });
|
||||
};
|
||||
|
||||
onOverrideRemove = (overrideIndex: number) => {
|
||||
const { config } = this.props;
|
||||
const onOverrideRemove = (overrideIndex: number) => {
|
||||
const { config } = props;
|
||||
let overrides = cloneDeep(config.overrides);
|
||||
overrides.splice(overrideIndex, 1);
|
||||
this.props.onChange({ ...config, overrides });
|
||||
props.onChange({ ...config, overrides });
|
||||
};
|
||||
|
||||
onOverrideAdd = (value: SelectableValue<string>) => {
|
||||
const { onChange, config } = this.props;
|
||||
const onOverrideAdd = (value: SelectableValue<string>) => {
|
||||
const { onChange, config } = props;
|
||||
onChange({
|
||||
...config,
|
||||
overrides: [
|
||||
@ -84,46 +59,8 @@ export class FieldConfigEditor extends React.PureComponent<Props> {
|
||||
});
|
||||
};
|
||||
|
||||
renderEditor(item: FieldPropertyEditorItem, custom: boolean) {
|
||||
const { data } = this.props;
|
||||
const config = this.props.config.defaults;
|
||||
const value = custom ? (config.custom ? config.custom[item.id] : undefined) : (config as any)[item.id];
|
||||
|
||||
return (
|
||||
<Forms.Field label={item.name} description={item.description} key={`${item.id}/${custom}`}>
|
||||
<item.editor
|
||||
item={item}
|
||||
value={value}
|
||||
onChange={v => this.setDefaultValue(item.id, v, custom)}
|
||||
context={{
|
||||
data,
|
||||
getSuggestions: (scope?: VariableSuggestionsScope) => getDataLinksVariableSuggestions(data, scope),
|
||||
}}
|
||||
/>
|
||||
</Forms.Field>
|
||||
);
|
||||
}
|
||||
|
||||
renderStandardConfigs() {
|
||||
const { include } = this.props;
|
||||
if (include) {
|
||||
return include.map(f => this.renderEditor(standardFieldConfigEditorRegistry.get(f), false));
|
||||
}
|
||||
return standardFieldConfigEditorRegistry.list().map(f => this.renderEditor(f, false));
|
||||
}
|
||||
|
||||
renderCustomConfigs() {
|
||||
const { plugin } = this.props;
|
||||
|
||||
if (!plugin.customFieldConfigs) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return plugin.customFieldConfigs.list().map(f => this.renderEditor(f, true));
|
||||
}
|
||||
|
||||
renderOverrides() {
|
||||
const { config, data, plugin } = this.props;
|
||||
const renderOverrides = () => {
|
||||
const { config, data, plugin } = props;
|
||||
const { customFieldConfigs } = plugin;
|
||||
|
||||
if (config.overrides.length === 0) {
|
||||
@ -157,8 +94,8 @@ export class FieldConfigEditor extends React.PureComponent<Props> {
|
||||
key={`${o.matcher.id}/${i}`}
|
||||
data={data}
|
||||
override={o}
|
||||
onChange={value => this.onOverrideChange(i, value)}
|
||||
onRemove={() => this.onOverrideRemove(i)}
|
||||
onChange={value => onOverrideChange(i, value)}
|
||||
onRemove={() => onOverrideRemove(i)}
|
||||
configPropertiesOptions={configPropertiesOptions}
|
||||
customPropertiesRegistry={customFieldConfigs}
|
||||
/>
|
||||
@ -166,9 +103,9 @@ export class FieldConfigEditor extends React.PureComponent<Props> {
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
renderAddOverride = () => {
|
||||
const renderAddOverride = () => {
|
||||
return (
|
||||
<ValuePicker
|
||||
icon="plus"
|
||||
@ -176,29 +113,95 @@ export class FieldConfigEditor extends React.PureComponent<Props> {
|
||||
options={fieldMatchersUI
|
||||
.list()
|
||||
.map<SelectableValue<string>>(i => ({ label: i.name, value: i.id, description: i.description }))}
|
||||
onChange={value => this.onOverrideAdd(value)}
|
||||
onChange={value => onOverrideAdd(value)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { plugin } = this.props;
|
||||
return (
|
||||
<div
|
||||
className={css`
|
||||
padding: ${theme.spacing.md};
|
||||
`}
|
||||
>
|
||||
{renderOverrides()}
|
||||
{renderAddOverride()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{plugin.customFieldConfigs && (
|
||||
<OptionsGroup title={`${plugin.meta.name} options`}>{this.renderCustomConfigs()}</OptionsGroup>
|
||||
)}
|
||||
export const DefaultFieldConfigEditor: React.FC<Props> = ({ include, data, onChange, config, plugin }) => {
|
||||
const setDefaultValue = useCallback(
|
||||
(name: string, value: any, custom: boolean) => {
|
||||
const defaults = { ...config.defaults };
|
||||
const remove = value === undefined || value === null || '';
|
||||
|
||||
<OptionsGroup title="Field defaults">{this.renderStandardConfigs()}</OptionsGroup>
|
||||
if (custom) {
|
||||
if (defaults.custom) {
|
||||
if (remove) {
|
||||
defaults.custom = { ...defaults.custom };
|
||||
delete defaults.custom[name];
|
||||
} else {
|
||||
defaults.custom = { ...defaults.custom, [name]: value };
|
||||
}
|
||||
} else if (!remove) {
|
||||
defaults.custom = { [name]: value };
|
||||
}
|
||||
} else if (remove) {
|
||||
delete (defaults as any)[name];
|
||||
} else {
|
||||
(defaults as any)[name] = value;
|
||||
}
|
||||
|
||||
<OptionsGroup title="Field overrides">
|
||||
{this.renderOverrides()}
|
||||
{this.renderAddOverride()}
|
||||
</OptionsGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
onChange({
|
||||
...config,
|
||||
defaults,
|
||||
});
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
export default FieldConfigEditor;
|
||||
const renderEditor = useCallback(
|
||||
(item: FieldPropertyEditorItem, custom: boolean) => {
|
||||
const defaults = config.defaults;
|
||||
const value = custom ? (defaults.custom ? defaults.custom[item.id] : undefined) : (defaults as any)[item.id];
|
||||
|
||||
return (
|
||||
<Forms.Field label={item.name} description={item.description} key={`${item.id}/${custom}`}>
|
||||
<item.editor
|
||||
item={item}
|
||||
value={value}
|
||||
onChange={v => setDefaultValue(item.id, v, custom)}
|
||||
context={{
|
||||
data,
|
||||
getSuggestions: (scope?: VariableSuggestionsScope) => getDataLinksVariableSuggestions(data, scope),
|
||||
}}
|
||||
/>
|
||||
</Forms.Field>
|
||||
);
|
||||
},
|
||||
[config]
|
||||
);
|
||||
|
||||
const renderStandardConfigs = useCallback(() => {
|
||||
if (include) {
|
||||
return <>{include.map(f => renderEditor(standardFieldConfigEditorRegistry.get(f), false))}</>;
|
||||
}
|
||||
return <>{standardFieldConfigEditorRegistry.list().map(f => renderEditor(f, false))}</>;
|
||||
}, [plugin, config]);
|
||||
|
||||
const renderCustomConfigs = useCallback(() => {
|
||||
if (!plugin.customFieldConfigs) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return plugin.customFieldConfigs.list().map(f => renderEditor(f, true));
|
||||
}, [plugin, config]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{plugin.customFieldConfigs && renderCustomConfigs()}
|
||||
{renderStandardConfigs()}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,210 @@
|
||||
import React, { useCallback, useState, useMemo } from 'react';
|
||||
import { FieldConfigSource, GrafanaTheme, PanelData, PanelPlugin } from '@grafana/data';
|
||||
import { DashboardModel, PanelModel } from '../../state';
|
||||
import {
|
||||
CustomScrollbar,
|
||||
stylesFactory,
|
||||
Tab,
|
||||
TabContent,
|
||||
TabsBar,
|
||||
useTheme,
|
||||
Forms,
|
||||
DataLinksInlineEditor,
|
||||
Container,
|
||||
} from '@grafana/ui';
|
||||
import { DefaultFieldConfigEditor, OverrideFieldConfigEditor } from './FieldConfigEditor';
|
||||
import { AngularPanelOptions } from './AngularPanelOptions';
|
||||
import { css } from 'emotion';
|
||||
import { OptionsGroup } from './OptionsGroup';
|
||||
import { getPanelLinksVariableSuggestions } from '../../../panel/panellinks/link_srv';
|
||||
|
||||
export const OptionsPaneContent: React.FC<{
|
||||
plugin?: PanelPlugin;
|
||||
panel: PanelModel;
|
||||
data: PanelData;
|
||||
dashboard: DashboardModel;
|
||||
onFieldConfigsChange: (config: FieldConfigSource) => void;
|
||||
onPanelOptionsChanged: (options: any) => void;
|
||||
onPanelConfigChange: (configKey: string, value: any) => void;
|
||||
}> = ({ plugin, panel, data, onFieldConfigsChange, onPanelOptionsChanged, onPanelConfigChange, dashboard }) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyles(theme);
|
||||
|
||||
const linkVariablesSuggestions = useMemo(() => getPanelLinksVariableSuggestions(), []);
|
||||
const renderFieldOptions = useCallback(
|
||||
(plugin: PanelPlugin) => {
|
||||
const fieldConfig = panel.getFieldConfig();
|
||||
|
||||
if (!fieldConfig) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Container padding="md">
|
||||
{renderCustomPanelSettings(plugin)}
|
||||
<DefaultFieldConfigEditor
|
||||
config={fieldConfig}
|
||||
plugin={plugin}
|
||||
onChange={onFieldConfigsChange}
|
||||
data={data.series}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
},
|
||||
[data, plugin, panel, onFieldConfigsChange]
|
||||
);
|
||||
const renderFieldOverrideOptions = useCallback(
|
||||
(plugin: PanelPlugin) => {
|
||||
const fieldConfig = panel.getFieldConfig();
|
||||
|
||||
if (!fieldConfig) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<OverrideFieldConfigEditor
|
||||
config={fieldConfig}
|
||||
plugin={plugin}
|
||||
onChange={onFieldConfigsChange}
|
||||
data={data.series}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[data, plugin, panel, onFieldConfigsChange]
|
||||
);
|
||||
|
||||
const renderCustomPanelSettings = useCallback(
|
||||
(plugin: PanelPlugin) => {
|
||||
if (plugin.editor && panel) {
|
||||
return (
|
||||
<div className={styles.legacyOptions}>
|
||||
<plugin.editor
|
||||
data={data}
|
||||
options={panel.getOptions()}
|
||||
onOptionsChange={onPanelOptionsChanged}
|
||||
fieldConfig={panel.getFieldConfig()}
|
||||
onFieldConfigChange={onFieldConfigsChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.legacyOptions}>
|
||||
<AngularPanelOptions panel={panel} dashboard={dashboard} plugin={plugin} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
[data, plugin, panel, onFieldConfigsChange]
|
||||
);
|
||||
|
||||
const renderPanelSettings = useCallback(() => {
|
||||
console.log(panel.transparent);
|
||||
return (
|
||||
<div>
|
||||
<OptionsGroup title="Panel settings">
|
||||
<>
|
||||
<Forms.Field label="Panel title">
|
||||
<Forms.Input
|
||||
defaultValue={panel.title}
|
||||
onBlur={e => onPanelConfigChange('title', e.currentTarget.value)}
|
||||
/>
|
||||
</Forms.Field>
|
||||
<Forms.Field label="Description" description="Panel description supports markdown and links">
|
||||
<Forms.TextArea
|
||||
defaultValue={panel.description}
|
||||
onBlur={e => onPanelConfigChange('description', e.currentTarget.value)}
|
||||
/>
|
||||
</Forms.Field>
|
||||
<Forms.Field label="Transparent" description="Display panel without background">
|
||||
<Forms.Switch
|
||||
value={panel.transparent}
|
||||
onChange={e => onPanelConfigChange('transparent', e.currentTarget.checked)}
|
||||
/>
|
||||
</Forms.Field>
|
||||
</>
|
||||
</OptionsGroup>
|
||||
<OptionsGroup title="Panel links">
|
||||
<DataLinksInlineEditor
|
||||
links={panel.links}
|
||||
onChange={links => onPanelConfigChange('links', links)}
|
||||
suggestions={linkVariablesSuggestions}
|
||||
data={data.series}
|
||||
/>
|
||||
</OptionsGroup>
|
||||
<OptionsGroup title="Panel repeating">
|
||||
<div>TODO</div>
|
||||
</OptionsGroup>
|
||||
</div>
|
||||
);
|
||||
}, [data, plugin, panel, onFieldConfigsChange]);
|
||||
|
||||
const [activeTab, setActiveTab] = useState('defaults');
|
||||
|
||||
return (
|
||||
<div className={styles.panelOptionsPane}>
|
||||
{plugin && (
|
||||
<div className={styles.wrapper}>
|
||||
<TabsBar>
|
||||
<Tab label="Options" active={activeTab === 'defaults'} onChangeTab={() => setActiveTab('defaults')} />
|
||||
<Tab label="Overrides" active={activeTab === 'overrides'} onChangeTab={() => setActiveTab('overrides')} />
|
||||
<Tab label="General" active={activeTab === 'panel'} onChangeTab={() => setActiveTab('panel')} />
|
||||
</TabsBar>
|
||||
<TabContent className={styles.tabContent}>
|
||||
<CustomScrollbar>
|
||||
{activeTab === 'defaults' && renderFieldOptions(plugin)}
|
||||
{activeTab === 'overrides' && renderFieldOverrideOptions(plugin)}
|
||||
{activeTab === 'panel' && renderPanelSettings()}
|
||||
</CustomScrollbar>
|
||||
</TabContent>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
return {
|
||||
wrapper: css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
`,
|
||||
panelOptionsPane: css`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border-bottom: none;
|
||||
`,
|
||||
tabContent: css`
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
min-height: 0;
|
||||
background: ${theme.colors.pageBg};
|
||||
border-left: 1px solid ${theme.colors.pageHeaderBorder};
|
||||
`,
|
||||
legacyOptions: css`
|
||||
label: legacy-options;
|
||||
.panel-options-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.panel-options-group {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.panel-options-group__body {
|
||||
padding: ${theme.spacing.md} 0;
|
||||
}
|
||||
|
||||
.section {
|
||||
display: block;
|
||||
margin: ${theme.spacing.md} 0;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
`,
|
||||
};
|
||||
});
|
@ -1,6 +1,6 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { FieldConfigSource, GrafanaTheme, PanelData, PanelPlugin, SelectableValue } from '@grafana/data';
|
||||
import { CustomScrollbar, Forms, selectThemeVariant, stylesFactory } from '@grafana/ui';
|
||||
import { Forms, selectThemeVariant, stylesFactory } from '@grafana/ui';
|
||||
import { css, cx } from 'emotion';
|
||||
import config from 'app/core/config';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
@ -23,11 +23,9 @@ import { LocationState } from 'app/types';
|
||||
import { calculatePanelSize } from './utils';
|
||||
import { initPanelEditor, panelEditorCleanUp, updatePanelEditorUIState } from './state/actions';
|
||||
import { PanelEditorUIState, setDiscardChanges } from './state/reducers';
|
||||
import { FieldConfigEditor } from './FieldConfigEditor';
|
||||
import { OptionsGroup } from './OptionsGroup';
|
||||
import { getPanelEditorTabs } from './state/selectors';
|
||||
import { getPanelStateById } from '../../state/selectors';
|
||||
import { AngularPanelOptions } from './AngularPanelOptions';
|
||||
import { OptionsPaneContent } from './OptionsPaneContent';
|
||||
|
||||
enum Pane {
|
||||
Right,
|
||||
@ -59,6 +57,9 @@ interface DispatchProps {
|
||||
|
||||
type Props = OwnProps & ConnectedProps & DispatchProps;
|
||||
|
||||
// TODO[NewPanelEdit]: Remove when we switch to new panel editor
|
||||
export const NewPanelEditorContext = React.createContext(false);
|
||||
|
||||
export class PanelEditorUnconnected extends PureComponent<Props> {
|
||||
querySubscription: Unsubscribable;
|
||||
|
||||
@ -98,43 +99,17 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
renderFieldOptions(plugin: PanelPlugin) {
|
||||
const { panel, data } = this.props;
|
||||
const { fieldConfig } = panel;
|
||||
|
||||
if (!fieldConfig) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<FieldConfigEditor config={fieldConfig} plugin={plugin} onChange={this.onFieldConfigChange} data={data.series} />
|
||||
);
|
||||
}
|
||||
|
||||
onPanelOptionsChanged = (options: any) => {
|
||||
this.props.panel.updateOptions(options);
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
renderPanelSettings(plugin: PanelPlugin) {
|
||||
const { data, panel, dashboard } = this.props;
|
||||
|
||||
if (plugin.editor && panel) {
|
||||
return (
|
||||
<div style={{ marginTop: '10px' }}>
|
||||
<plugin.editor
|
||||
data={data}
|
||||
options={panel.getOptions()}
|
||||
onOptionsChange={this.onPanelOptionsChanged}
|
||||
fieldConfig={panel.getFieldConfig()}
|
||||
onFieldConfigChange={this.onFieldConfigChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <AngularPanelOptions panel={panel} dashboard={dashboard} plugin={plugin} />;
|
||||
}
|
||||
onPanelConfigChanged = (configKey: string, value: any) => {
|
||||
// @ts-ignore
|
||||
this.props.panel[configKey] = value;
|
||||
this.props.panel.render();
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
onDragFinished = (pane: Pane, size: number) => {
|
||||
document.body.style.cursor = 'auto';
|
||||
@ -250,20 +225,23 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
|
||||
);
|
||||
}
|
||||
|
||||
renderOptionsPane(styles: any) {
|
||||
const { plugin } = this.props;
|
||||
renderOptionsPane() {
|
||||
const { plugin, dashboard, data, panel } = this.props;
|
||||
|
||||
if (!plugin) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.panelOptionsPane}>
|
||||
<CustomScrollbar>
|
||||
{plugin && (
|
||||
<>
|
||||
{this.renderFieldOptions(plugin)}
|
||||
<OptionsGroup title={`${plugin.meta.name} options`}>{this.renderPanelSettings(plugin)}</OptionsGroup>
|
||||
</>
|
||||
)}
|
||||
</CustomScrollbar>
|
||||
</div>
|
||||
<OptionsPaneContent
|
||||
plugin={plugin}
|
||||
dashboard={dashboard}
|
||||
data={data}
|
||||
panel={panel}
|
||||
onFieldConfigsChange={this.onFieldConfigChange}
|
||||
onPanelOptionsChanged={this.onPanelOptionsChanged}
|
||||
onPanelConfigChange={this.onPanelConfigChanged}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -282,7 +260,7 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
|
||||
onDragFinished={size => this.onDragFinished(Pane.Right, size)}
|
||||
>
|
||||
{this.renderHorizontalSplit(styles)}
|
||||
{this.renderOptionsPane(styles)}
|
||||
{this.renderOptionsPane()}
|
||||
</SplitPane>
|
||||
);
|
||||
}
|
||||
@ -296,12 +274,14 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
{this.renderToolbar()}
|
||||
<div className={styles.panesWrapper}>
|
||||
{uiState.isPanelOptionsVisible ? this.renderWithOptionsPane(styles) : this.renderHorizontalSplit(styles)}
|
||||
<NewPanelEditorContext.Provider value={true}>
|
||||
<div className={styles.wrapper}>
|
||||
{this.renderToolbar()}
|
||||
<div className={styles.panesWrapper}>
|
||||
{uiState.isPanelOptionsVisible ? this.renderWithOptionsPane(styles) : this.renderHorizontalSplit(styles)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</NewPanelEditorContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -402,12 +382,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
`,
|
||||
panelOptionsPane: css`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: ${theme.colors.pageBg};
|
||||
border-bottom: none;
|
||||
`,
|
||||
|
||||
toolbar: css`
|
||||
display: flex;
|
||||
padding: ${theme.spacing.sm};
|
||||
|
@ -34,7 +34,15 @@ export const PanelEditorTabs: React.FC<PanelEditorTabsProps> = ({ panel, dashboa
|
||||
<div className={styles.wrapper}>
|
||||
<TabsBar className={styles.tabBar}>
|
||||
{tabs.map(tab => {
|
||||
return <Tab key={tab.id} label={tab.text} active={tab.active} onChangeTab={() => onChangeTab(tab)} />;
|
||||
return (
|
||||
<Tab
|
||||
key={tab.id}
|
||||
label={tab.text}
|
||||
active={tab.active}
|
||||
onChangeTab={() => onChangeTab(tab)}
|
||||
icon={tab.icon}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</TabsBar>
|
||||
<TabContent className={styles.tabContent}>
|
||||
|
@ -18,12 +18,14 @@ export const getPanelEditorTabs = memoizeOne((location: LocationState, plugin?:
|
||||
tabs.push({
|
||||
id: PanelEditorTabId.Queries,
|
||||
text: 'Queries',
|
||||
icon: 'gicon gicon-datasources',
|
||||
active: false,
|
||||
});
|
||||
|
||||
tabs.push({
|
||||
id: PanelEditorTabId.Transform,
|
||||
text: 'Transform',
|
||||
icon: 'fa fa-exchange',
|
||||
active: false,
|
||||
});
|
||||
}
|
||||
@ -31,6 +33,7 @@ export const getPanelEditorTabs = memoizeOne((location: LocationState, plugin?:
|
||||
tabs.push({
|
||||
id: PanelEditorTabId.Visualization,
|
||||
text: 'Visualization',
|
||||
icon: 'fa fa-bar-chart',
|
||||
active: false,
|
||||
});
|
||||
|
||||
@ -38,6 +41,7 @@ export const getPanelEditorTabs = memoizeOne((location: LocationState, plugin?:
|
||||
tabs.push({
|
||||
id: PanelEditorTabId.Alert,
|
||||
text: 'Alert',
|
||||
icon: 'gicon gicon-alert',
|
||||
active: false,
|
||||
});
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ export interface PanelEditorTab {
|
||||
id: string;
|
||||
text: string;
|
||||
active: boolean;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
export enum PanelEditorTabId {
|
||||
|
@ -27,6 +27,7 @@ import {
|
||||
getCalculationValueDataLinksVariableSuggestions,
|
||||
getDataLinksVariableSuggestions,
|
||||
} from '../../../features/panel/panellinks/link_srv';
|
||||
import { NewPanelEditorContext } from '../../../features/dashboard/components/PanelEditor/PanelEditor';
|
||||
|
||||
export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGaugeOptions>> {
|
||||
onDisplayOptionsChanged = (fieldOptions: FieldDisplayOptions) =>
|
||||
@ -92,64 +93,84 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge
|
||||
: getCalculationValueDataLinksVariableSuggestions(this.props.data.series);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PanelOptionsGrid>
|
||||
<PanelOptionsGroup title="Display">
|
||||
<FieldDisplayEditor onChange={this.onDisplayOptionsChanged} value={fieldOptions} labelWidth={labelWidth} />
|
||||
<div className="form-field">
|
||||
<FormLabel width={labelWidth}>Orientation</FormLabel>
|
||||
<Select
|
||||
width={12}
|
||||
options={orientationOptions}
|
||||
defaultValue={orientationOptions[0]}
|
||||
onChange={this.onOrientationChange}
|
||||
value={orientationOptions.find(item => item.value === options.orientation)}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-field">
|
||||
<FormLabel width={labelWidth}>Mode</FormLabel>
|
||||
<Select
|
||||
width={12}
|
||||
options={displayModes}
|
||||
defaultValue={displayModes[0]}
|
||||
onChange={this.onDisplayModeChange}
|
||||
value={displayModes.find(item => item.value === options.displayMode)}
|
||||
/>
|
||||
</div>
|
||||
<NewPanelEditorContext.Consumer>
|
||||
{useNewEditor => {
|
||||
return (
|
||||
<>
|
||||
{options.displayMode !== 'lcd' && (
|
||||
<Switch
|
||||
label="Unfilled"
|
||||
labelClass={`width-${labelWidth}`}
|
||||
checked={options.showUnfilled}
|
||||
onChange={this.onToggleShowUnfilled}
|
||||
/>
|
||||
<PanelOptionsGrid>
|
||||
<PanelOptionsGroup title="Display">
|
||||
<FieldDisplayEditor
|
||||
onChange={this.onDisplayOptionsChanged}
|
||||
value={fieldOptions}
|
||||
labelWidth={labelWidth}
|
||||
/>
|
||||
<div className="form-field">
|
||||
<FormLabel width={labelWidth}>Orientation</FormLabel>
|
||||
<Select
|
||||
width={12}
|
||||
options={orientationOptions}
|
||||
defaultValue={orientationOptions[0]}
|
||||
onChange={this.onOrientationChange}
|
||||
value={orientationOptions.find(item => item.value === options.orientation)}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-field">
|
||||
<FormLabel width={labelWidth}>Mode</FormLabel>
|
||||
<Select
|
||||
width={12}
|
||||
options={displayModes}
|
||||
defaultValue={displayModes[0]}
|
||||
onChange={this.onDisplayModeChange}
|
||||
value={displayModes.find(item => item.value === options.displayMode)}
|
||||
/>
|
||||
</div>
|
||||
<>
|
||||
{options.displayMode !== 'lcd' && (
|
||||
<Switch
|
||||
label="Unfilled"
|
||||
labelClass={`width-${labelWidth}`}
|
||||
checked={options.showUnfilled}
|
||||
onChange={this.onToggleShowUnfilled}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</PanelOptionsGroup>
|
||||
<>
|
||||
{!useNewEditor && (
|
||||
<>
|
||||
<PanelOptionsGroup title="Field">
|
||||
<FieldPropertiesEditor
|
||||
showMinMax={true}
|
||||
showTitle={true}
|
||||
onChange={this.onDefaultsChange}
|
||||
value={defaults}
|
||||
/>
|
||||
</PanelOptionsGroup>
|
||||
|
||||
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={defaults.thresholds} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
</PanelOptionsGrid>
|
||||
|
||||
{!useNewEditor && (
|
||||
<LegacyValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} />
|
||||
)}
|
||||
|
||||
{!useNewEditor && (
|
||||
<PanelOptionsGroup title="Data links">
|
||||
<DataLinksEditor
|
||||
value={defaults.links}
|
||||
onChange={this.onDataLinksChanged}
|
||||
suggestions={suggestions}
|
||||
maxLinks={10}
|
||||
/>
|
||||
</PanelOptionsGroup>
|
||||
)}
|
||||
</>
|
||||
</PanelOptionsGroup>
|
||||
<PanelOptionsGroup title="Field">
|
||||
<FieldPropertiesEditor
|
||||
showMinMax={true}
|
||||
showTitle={true}
|
||||
onChange={this.onDefaultsChange}
|
||||
value={defaults}
|
||||
/>
|
||||
</PanelOptionsGroup>
|
||||
|
||||
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={defaults.thresholds} />
|
||||
</PanelOptionsGrid>
|
||||
|
||||
<LegacyValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} />
|
||||
|
||||
<PanelOptionsGroup title="Data links">
|
||||
<DataLinksEditor
|
||||
value={defaults.links}
|
||||
onChange={this.onDataLinksChanged}
|
||||
suggestions={suggestions}
|
||||
maxLinks={10}
|
||||
/>
|
||||
</PanelOptionsGroup>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</NewPanelEditorContext.Consumer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import {
|
||||
getCalculationValueDataLinksVariableSuggestions,
|
||||
getDataLinksVariableSuggestions,
|
||||
} from '../../../features/panel/panellinks/link_srv';
|
||||
import { NewPanelEditorContext } from '../../../features/dashboard/components/PanelEditor/PanelEditor';
|
||||
|
||||
export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOptions>> {
|
||||
labelWidth = 6;
|
||||
@ -100,50 +101,67 @@ export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOption
|
||||
const suggestions = fieldOptions.values
|
||||
? getDataLinksVariableSuggestions(this.props.data.series)
|
||||
: getCalculationValueDataLinksVariableSuggestions(this.props.data.series);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PanelOptionsGrid>
|
||||
<PanelOptionsGroup title="Display">
|
||||
<FieldDisplayEditor
|
||||
onChange={this.onDisplayOptionsChanged}
|
||||
value={fieldOptions}
|
||||
labelWidth={this.labelWidth}
|
||||
/>
|
||||
<Switch
|
||||
label="Labels"
|
||||
labelClass={`width-${this.labelWidth}`}
|
||||
checked={showThresholdLabels}
|
||||
onChange={this.onToggleThresholdLabels}
|
||||
/>
|
||||
<Switch
|
||||
label="Markers"
|
||||
labelClass={`width-${this.labelWidth}`}
|
||||
checked={showThresholdMarkers}
|
||||
onChange={this.onToggleThresholdMarkers}
|
||||
/>
|
||||
</PanelOptionsGroup>
|
||||
<NewPanelEditorContext.Consumer>
|
||||
{useNewEditor => {
|
||||
return (
|
||||
<>
|
||||
<PanelOptionsGrid>
|
||||
<PanelOptionsGroup title="Display">
|
||||
<FieldDisplayEditor
|
||||
onChange={this.onDisplayOptionsChanged}
|
||||
value={fieldOptions}
|
||||
labelWidth={this.labelWidth}
|
||||
/>
|
||||
<Switch
|
||||
label="Labels"
|
||||
labelClass={`width-${this.labelWidth}`}
|
||||
checked={showThresholdLabels}
|
||||
onChange={this.onToggleThresholdLabels}
|
||||
/>
|
||||
<Switch
|
||||
label="Markers"
|
||||
labelClass={`width-${this.labelWidth}`}
|
||||
checked={showThresholdMarkers}
|
||||
onChange={this.onToggleThresholdMarkers}
|
||||
/>
|
||||
</PanelOptionsGroup>
|
||||
|
||||
<PanelOptionsGroup title="Field">
|
||||
<FieldPropertiesEditor
|
||||
showMinMax={true}
|
||||
showTitle={true}
|
||||
onChange={this.onDefaultsChange}
|
||||
value={defaults}
|
||||
/>
|
||||
</PanelOptionsGroup>
|
||||
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={defaults.thresholds} />
|
||||
</PanelOptionsGrid>
|
||||
<LegacyValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} />
|
||||
<>
|
||||
{!useNewEditor && (
|
||||
<>
|
||||
<PanelOptionsGroup title="Field">
|
||||
<FieldPropertiesEditor
|
||||
showMinMax={true}
|
||||
showTitle={true}
|
||||
onChange={this.onDefaultsChange}
|
||||
value={defaults}
|
||||
/>
|
||||
</PanelOptionsGroup>
|
||||
|
||||
<PanelOptionsGroup title="Data links">
|
||||
<DataLinksEditor
|
||||
value={defaults.links}
|
||||
onChange={this.onDataLinksChanged}
|
||||
suggestions={suggestions}
|
||||
maxLinks={10}
|
||||
/>
|
||||
</PanelOptionsGroup>
|
||||
</>
|
||||
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={defaults.thresholds} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
</PanelOptionsGrid>
|
||||
{!useNewEditor && (
|
||||
<>
|
||||
<LegacyValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} />
|
||||
<PanelOptionsGroup title="Data links">
|
||||
<DataLinksEditor
|
||||
value={defaults.links}
|
||||
onChange={this.onDataLinksChanged}
|
||||
suggestions={suggestions}
|
||||
maxLinks={10}
|
||||
/>
|
||||
</PanelOptionsGroup>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</NewPanelEditorContext.Consumer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import {
|
||||
} from '@grafana/ui';
|
||||
import { Options, GraphOptions } from './types';
|
||||
import { GraphLegendEditor } from './GraphLegendEditor';
|
||||
import { NewPanelEditorContext } from 'app/features/dashboard/components/PanelEditor/PanelEditor';
|
||||
|
||||
export class GraphPanelEditor extends PureComponent<PanelEditorProps<Options>> {
|
||||
onGraphOptionsChange = (options: Partial<GraphOptions>) => {
|
||||
@ -61,36 +62,46 @@ export class GraphPanelEditor extends PureComponent<PanelEditorProps<Options>> {
|
||||
} = this.props.options;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="section gf-form-group">
|
||||
<h5 className="section-heading">Draw Modes</h5>
|
||||
<Switch label="Lines" labelClass="width-5" checked={showLines} onChange={this.onToggleLines} />
|
||||
<Switch label="Bars" labelClass="width-5" checked={showBars} onChange={this.onToggleBars} />
|
||||
<Switch label="Points" labelClass="width-5" checked={showPoints} onChange={this.onTogglePoints} />
|
||||
</div>
|
||||
<PanelOptionsGrid>
|
||||
<PanelOptionsGroup title="Field">
|
||||
<FieldPropertiesEditor
|
||||
showMinMax={false}
|
||||
onChange={this.onDefaultsChange}
|
||||
value={this.props.fieldConfig.defaults}
|
||||
/>
|
||||
</PanelOptionsGroup>
|
||||
<PanelOptionsGroup title="Tooltip">
|
||||
<Select
|
||||
value={{ value: mode, label: mode === 'single' ? 'Single' : 'All series' }}
|
||||
onChange={value => {
|
||||
this.onTooltipOptionsChange({ mode: value.value as any });
|
||||
}}
|
||||
options={[
|
||||
{ label: 'All series', value: 'multi' },
|
||||
{ label: 'Single', value: 'single' },
|
||||
]}
|
||||
/>
|
||||
</PanelOptionsGroup>
|
||||
<GraphLegendEditor options={this.props.options.legend} onChange={this.onLegendOptionsChange} />
|
||||
</PanelOptionsGrid>
|
||||
</>
|
||||
<NewPanelEditorContext.Consumer>
|
||||
{useNewEditor => {
|
||||
return (
|
||||
<>
|
||||
<div className="section gf-form-group">
|
||||
<h5 className="section-heading">Draw Modes</h5>
|
||||
<Switch label="Lines" labelClass="width-5" checked={showLines} onChange={this.onToggleLines} />
|
||||
<Switch label="Bars" labelClass="width-5" checked={showBars} onChange={this.onToggleBars} />
|
||||
<Switch label="Points" labelClass="width-5" checked={showPoints} onChange={this.onTogglePoints} />
|
||||
</div>
|
||||
<PanelOptionsGrid>
|
||||
<>
|
||||
{!useNewEditor && (
|
||||
<PanelOptionsGroup title="Field">
|
||||
<FieldPropertiesEditor
|
||||
showMinMax={false}
|
||||
onChange={this.onDefaultsChange}
|
||||
value={this.props.fieldConfig.defaults}
|
||||
/>
|
||||
</PanelOptionsGroup>
|
||||
)}
|
||||
</>
|
||||
<PanelOptionsGroup title="Tooltip">
|
||||
<Select
|
||||
value={{ value: mode, label: mode === 'single' ? 'Single' : 'All series' }}
|
||||
onChange={value => {
|
||||
this.onTooltipOptionsChange({ mode: value.value as any });
|
||||
}}
|
||||
options={[
|
||||
{ label: 'All series', value: 'multi' },
|
||||
{ label: 'Single', value: 'single' },
|
||||
]}
|
||||
/>
|
||||
</PanelOptionsGroup>
|
||||
<GraphLegendEditor options={this.props.options.legend} onChange={this.onLegendOptionsChange} />
|
||||
</PanelOptionsGrid>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</NewPanelEditorContext.Consumer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ import {
|
||||
getCalculationValueDataLinksVariableSuggestions,
|
||||
getDataLinksVariableSuggestions,
|
||||
} from '../../../features/panel/panellinks/link_srv';
|
||||
import { NewPanelEditorContext } from '../../../features/dashboard/components/PanelEditor/PanelEditor';
|
||||
|
||||
export class StatPanelEditor extends PureComponent<PanelEditorProps<StatPanelOptions>> {
|
||||
onThresholdsChanged = (thresholds: ThresholdsConfig) => {
|
||||
@ -91,73 +92,88 @@ export class StatPanelEditor extends PureComponent<PanelEditorProps<StatPanelOpt
|
||||
: getCalculationValueDataLinksVariableSuggestions(this.props.data.series);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PanelOptionsGrid>
|
||||
<PanelOptionsGroup title="Display">
|
||||
<FieldDisplayEditor onChange={this.onDisplayOptionsChanged} value={fieldOptions} labelWidth={8} />
|
||||
<div className="form-field">
|
||||
<FormLabel width={8}>Orientation</FormLabel>
|
||||
<Select
|
||||
width={12}
|
||||
options={orientationOptions}
|
||||
defaultValue={orientationOptions[0]}
|
||||
onChange={this.onOrientationChange}
|
||||
value={orientationOptions.find(item => item.value === options.orientation)}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-field">
|
||||
<FormLabel width={8}>Color</FormLabel>
|
||||
<Select
|
||||
width={12}
|
||||
options={colorModes}
|
||||
defaultValue={colorModes[0]}
|
||||
onChange={this.onColorModeChanged}
|
||||
value={colorModes.find(item => item.value === options.colorMode)}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-field">
|
||||
<FormLabel width={8}>Graph</FormLabel>
|
||||
<Select
|
||||
width={12}
|
||||
options={graphModes}
|
||||
defaultValue={graphModes[0]}
|
||||
onChange={this.onGraphModeChanged}
|
||||
value={graphModes.find(item => item.value === options.graphMode)}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-field">
|
||||
<FormLabel width={8}>Justify</FormLabel>
|
||||
<Select
|
||||
width={12}
|
||||
options={justifyModes}
|
||||
defaultValue={justifyModes[0]}
|
||||
onChange={this.onJustifyModeChanged}
|
||||
value={justifyModes.find(item => item.value === options.justifyMode)}
|
||||
/>
|
||||
</div>
|
||||
</PanelOptionsGroup>
|
||||
<PanelOptionsGroup title="Field">
|
||||
<FieldPropertiesEditor
|
||||
showMinMax={true}
|
||||
onChange={this.onDefaultsChange}
|
||||
value={defaults}
|
||||
showTitle={true}
|
||||
/>
|
||||
</PanelOptionsGroup>
|
||||
<NewPanelEditorContext.Consumer>
|
||||
{useNewEditor => {
|
||||
return (
|
||||
<>
|
||||
<PanelOptionsGrid>
|
||||
<PanelOptionsGroup title="Display">
|
||||
<FieldDisplayEditor onChange={this.onDisplayOptionsChanged} value={fieldOptions} labelWidth={8} />
|
||||
<div className="form-field">
|
||||
<FormLabel width={8}>Orientation</FormLabel>
|
||||
<Select
|
||||
width={12}
|
||||
options={orientationOptions}
|
||||
defaultValue={orientationOptions[0]}
|
||||
onChange={this.onOrientationChange}
|
||||
value={orientationOptions.find(item => item.value === options.orientation)}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-field">
|
||||
<FormLabel width={8}>Color</FormLabel>
|
||||
<Select
|
||||
width={12}
|
||||
options={colorModes}
|
||||
defaultValue={colorModes[0]}
|
||||
onChange={this.onColorModeChanged}
|
||||
value={colorModes.find(item => item.value === options.colorMode)}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-field">
|
||||
<FormLabel width={8}>Graph</FormLabel>
|
||||
<Select
|
||||
width={12}
|
||||
options={graphModes}
|
||||
defaultValue={graphModes[0]}
|
||||
onChange={this.onGraphModeChanged}
|
||||
value={graphModes.find(item => item.value === options.graphMode)}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-field">
|
||||
<FormLabel width={8}>Justify</FormLabel>
|
||||
<Select
|
||||
width={12}
|
||||
options={justifyModes}
|
||||
defaultValue={justifyModes[0]}
|
||||
onChange={this.onJustifyModeChanged}
|
||||
value={justifyModes.find(item => item.value === options.justifyMode)}
|
||||
/>
|
||||
</div>
|
||||
</PanelOptionsGroup>
|
||||
<>
|
||||
{!useNewEditor && (
|
||||
<>
|
||||
<PanelOptionsGroup title="Field">
|
||||
<FieldPropertiesEditor
|
||||
showMinMax={true}
|
||||
onChange={this.onDefaultsChange}
|
||||
value={defaults}
|
||||
showTitle={true}
|
||||
/>
|
||||
</PanelOptionsGroup>
|
||||
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={defaults.thresholds} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
</PanelOptionsGrid>
|
||||
{!useNewEditor && (
|
||||
<>
|
||||
<LegacyValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} />
|
||||
|
||||
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={defaults.thresholds} />
|
||||
</PanelOptionsGrid>
|
||||
<LegacyValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} />
|
||||
|
||||
<PanelOptionsGroup title="Data links">
|
||||
<DataLinksEditor
|
||||
value={defaults.links}
|
||||
onChange={this.onDataLinksChanged}
|
||||
suggestions={suggestions}
|
||||
maxLinks={10}
|
||||
/>
|
||||
</PanelOptionsGroup>
|
||||
</>
|
||||
<PanelOptionsGroup title="Data links">
|
||||
<DataLinksEditor
|
||||
value={defaults.links}
|
||||
onChange={this.onDataLinksChanged}
|
||||
suggestions={suggestions}
|
||||
maxLinks={10}
|
||||
/>
|
||||
</PanelOptionsGroup>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</NewPanelEditorContext.Consumer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
echo -e "Collecting code stats (typescript errors & more)"
|
||||
|
||||
|
||||
ERROR_COUNT_LIMIT=821
|
||||
ERROR_COUNT_LIMIT=820
|
||||
DIRECTIVES_LIMIT=172
|
||||
CONTROLLERS_LIMIT=139
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user