Standardization across Zabbix UI components (#2141)
## Summary Throughout Zabbix we did not have a uniform UI - some drop-down were using `Select` others `Combobox` others a custom one that we created. Some had placeholders and others did not. This PR aims to standardize our Zabbix UI across our query, variable and config editors ## Detailed summary - Migrate from `Select` to `Combobox` -> `Select` component is deprecated - Migrate from `HorizontalGroup` to `Stack` -> `HorizontalGroup` is also deprecated - Remove use of "custom" dropdown `MetricPickerMenu` in favor of `Combobox` ensuring uniformity across our drop-down and removing maintenance overhead for us down the line - Standardize placeholders across all inputs <img width="630" height="243" alt="Screenshot 2025-12-17 at 1 13 45 PM" src="https://github.com/user-attachments/assets/9382057e-b443-4474-a9c8-850086d7f3d4" /> <img width="691" height="256" alt="Screenshot 2025-12-17 at 1 14 05 PM" src="https://github.com/user-attachments/assets/a05ff2af-8603-4752-8d12-337dc381c0fd" /> ## Why To have a clean and standard UI and remove use of UI deprecated packages. ## How to test - Query Editor: - By creating a new query in a dashboard or Explore and interacting with all the different query types and drop-downs - All drop-downs should be searchable and have placeholders - Config Editor: - By going to a datasource and ensuring that the dropdown for Datasource (when DB connection is enabled) and Auth type are responsive and working as expected) Fixes: https://github.com/orgs/grafana/projects/457/views/40?pane=issue&itemId=3740545830&issue=grafana%7Coss-big-tent-squad%7C139
This commit is contained in:
committed by
GitHub
parent
ce4a8d3e19
commit
127367464e
5
.changeset/ninety-eggs-bow.md
Normal file
5
.changeset/ninety-eggs-bow.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'grafana-zabbix': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Standardization across Zabbix UI components
|
||||||
@@ -1,130 +1,35 @@
|
|||||||
import { css, cx } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import React, { FormEvent, useCallback, useEffect, useState, useRef } from 'react';
|
import React, { useRef } from 'react';
|
||||||
import { ClickOutsideWrapper, Input, Spinner, useStyles2 } from '@grafana/ui';
|
import { Combobox, ComboboxOption } from '@grafana/ui';
|
||||||
import { MetricPickerMenu } from './MetricPickerMenu';
|
|
||||||
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
||||||
import { isRegex } from '../../datasource/utils';
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
value: string;
|
value: string;
|
||||||
|
placeholder: string;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
options: Array<SelectableValue<string>>;
|
options: Array<ComboboxOption<string>>;
|
||||||
width?: number;
|
width?: number;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MetricPicker = ({ value, options, isLoading, width, onChange }: Props) => {
|
export const MetricPicker = ({ value, placeholder, options, isLoading, width, onChange }: Props) => {
|
||||||
const [isOpen, setOpen] = useState(false);
|
|
||||||
const [query, setQuery] = useState(value);
|
|
||||||
const [filteredOptions, setFilteredOptions] = useState(options);
|
|
||||||
const [selectedOptionIdx, setSelectedOptionIdx] = useState(-1);
|
|
||||||
const [offset] = useState({ vertical: 0, horizontal: 0 });
|
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const customStyles = useStyles2(getStyles);
|
|
||||||
|
|
||||||
const inputClass = cx({
|
|
||||||
[customStyles.inputRegexp]: isRegex(query),
|
|
||||||
[customStyles.inputVariable]: query.startsWith('$'),
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setFilteredOptions(options);
|
|
||||||
}, [options]);
|
|
||||||
|
|
||||||
const onOpen = () => {
|
|
||||||
setOpen(true);
|
|
||||||
setFilteredOptions(options);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onClose = useCallback(() => {
|
|
||||||
setOpen(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Only call onClose if menu is open. Prevent unnecessary calls for multiple pickers on the page.
|
|
||||||
const onClickOutside = () => isOpen && onClose();
|
|
||||||
|
|
||||||
const onInputChange = (v: FormEvent<HTMLInputElement>) => {
|
|
||||||
if (!isOpen) {
|
|
||||||
setOpen(true);
|
|
||||||
}
|
|
||||||
const newQuery = v?.currentTarget?.value;
|
|
||||||
if (newQuery) {
|
|
||||||
setQuery(newQuery);
|
|
||||||
if (value !== newQuery) {
|
|
||||||
const filtered = options.filter(
|
|
||||||
(option) =>
|
|
||||||
option.value?.toLowerCase().includes(newQuery.toLowerCase()) ||
|
|
||||||
option.label?.toLowerCase().includes(newQuery.toLowerCase())
|
|
||||||
);
|
|
||||||
setFilteredOptions(filtered);
|
|
||||||
} else {
|
|
||||||
setFilteredOptions(options);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setQuery('');
|
|
||||||
setFilteredOptions(options);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onMenuOptionSelect = (option: SelectableValue<string>) => {
|
const onMenuOptionSelect = (option: SelectableValue<string>) => {
|
||||||
const newValue = option?.value || '';
|
const newValue = option?.value || '';
|
||||||
setQuery(newValue);
|
|
||||||
onChange(newValue);
|
onChange(newValue);
|
||||||
onClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
const onBlurInternal = () => {
|
|
||||||
onChange(query);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onKeyDown = (e: React.KeyboardEvent) => {
|
|
||||||
if (e.key === 'ArrowDown') {
|
|
||||||
if (!isOpen) {
|
|
||||||
setOpen(true);
|
|
||||||
}
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
const selected = selectedOptionIdx < filteredOptions.length - 1 ? selectedOptionIdx + 1 : 0;
|
|
||||||
setSelectedOptionIdx(selected);
|
|
||||||
} else if (e.key === 'ArrowUp') {
|
|
||||||
if (!isOpen) {
|
|
||||||
setOpen(true);
|
|
||||||
}
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
const selected = selectedOptionIdx > 0 ? selectedOptionIdx - 1 : filteredOptions.length - 1;
|
|
||||||
setSelectedOptionIdx(selected);
|
|
||||||
} else if (e.key === 'Enter') {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
onMenuOptionSelect(filteredOptions[selectedOptionIdx]);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-testid="role-picker" style={{ position: 'relative' }} ref={ref}>
|
<div data-testid="role-picker" style={{ position: 'relative' }} ref={ref}>
|
||||||
<ClickOutsideWrapper onClick={onClickOutside}>
|
<Combobox<string>
|
||||||
<Input
|
|
||||||
className={inputClass}
|
|
||||||
value={query}
|
|
||||||
type="text"
|
|
||||||
onChange={onInputChange}
|
|
||||||
onBlur={onBlurInternal}
|
|
||||||
onMouseDown={onOpen}
|
|
||||||
suffix={isLoading && <Spinner />}
|
|
||||||
width={width}
|
width={width}
|
||||||
onKeyDown={onKeyDown}
|
value={value}
|
||||||
|
options={options ?? []}
|
||||||
|
onChange={onMenuOptionSelect}
|
||||||
|
loading={isLoading}
|
||||||
|
placeholder={placeholder}
|
||||||
/>
|
/>
|
||||||
{isOpen && (
|
|
||||||
<MetricPickerMenu
|
|
||||||
options={filteredOptions}
|
|
||||||
onSelect={onMenuOptionSelect}
|
|
||||||
offset={offset}
|
|
||||||
minWidth={width}
|
|
||||||
selected={selectedOptionIdx}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</ClickOutsideWrapper>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,138 +0,0 @@
|
|||||||
import { css, cx } from '@emotion/css';
|
|
||||||
import React, { FormEvent } from 'react';
|
|
||||||
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
|
||||||
import { CustomScrollbar, getSelectStyles, Icon, Tooltip, useStyles2, useTheme2 } from '@grafana/ui';
|
|
||||||
import { MENU_MAX_HEIGHT } from './constants';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
options: Array<SelectableValue<string>>;
|
|
||||||
onSelect: (option: SelectableValue<string>) => void;
|
|
||||||
offset: { vertical: number; horizontal: number };
|
|
||||||
minWidth?: number;
|
|
||||||
selected?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const MetricPickerMenu = ({ options, offset, minWidth, selected, onSelect }: Props) => {
|
|
||||||
const theme = useTheme2();
|
|
||||||
const styles = getSelectStyles(theme);
|
|
||||||
const customStyles = useStyles2(getStyles(minWidth));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={cx(
|
|
||||||
styles.menu,
|
|
||||||
customStyles.menuWrapper,
|
|
||||||
{ [customStyles.menuLeft]: offset.horizontal > 0 },
|
|
||||||
css`
|
|
||||||
bottom: ${offset.vertical > 0 ? `${offset.vertical}px` : 'unset'};
|
|
||||||
top: ${offset.vertical < 0 ? `${Math.abs(offset.vertical)}px` : 'unset'};
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className={customStyles.menu} aria-label="Metric picker menu">
|
|
||||||
<CustomScrollbar autoHide={false} autoHeightMax={`${MENU_MAX_HEIGHT}px`} hideHorizontalTrack>
|
|
||||||
<div>
|
|
||||||
<div className={styles.optionBody}>
|
|
||||||
{options?.map((option, i) => (
|
|
||||||
<MenuOption data={option} key={i} onClick={onSelect} isFocused={selected === i} hideDescription />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CustomScrollbar>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface MenuOptionProps<T> {
|
|
||||||
data: SelectableValue<string>;
|
|
||||||
onClick: (value: SelectableValue<string>) => void;
|
|
||||||
isFocused?: boolean;
|
|
||||||
disabled?: boolean;
|
|
||||||
hideDescription?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MenuOption = React.forwardRef<HTMLDivElement, React.PropsWithChildren<MenuOptionProps<any>>>(
|
|
||||||
({ data, isFocused, disabled, onClick, hideDescription }, ref) => {
|
|
||||||
const theme = useTheme2();
|
|
||||||
const styles = getSelectStyles(theme);
|
|
||||||
const customStyles = useStyles2(getStyles());
|
|
||||||
|
|
||||||
const wrapperClassName = cx(
|
|
||||||
styles.option,
|
|
||||||
customStyles.menuOptionWrapper,
|
|
||||||
isFocused && styles.optionFocused,
|
|
||||||
disabled && customStyles.menuOptionDisabled
|
|
||||||
);
|
|
||||||
|
|
||||||
const onClickInternal = (event: FormEvent<HTMLElement>) => {
|
|
||||||
if (disabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
onClick(data);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div ref={ref} className={wrapperClassName} aria-label="Menu option" onClick={onClickInternal}>
|
|
||||||
<div className={cx(styles.optionBody, customStyles.menuOptionBody)}>
|
|
||||||
<span>{data.label || data.value}</span>
|
|
||||||
{!hideDescription && data.description && <div className={styles.optionDescription}>{data.description}</div>}
|
|
||||||
</div>
|
|
||||||
{data.description && (
|
|
||||||
<Tooltip content={data.description}>
|
|
||||||
<Icon name="info-circle" className={customStyles.menuOptionInfoSign} />
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
MenuOption.displayName = 'MenuOption';
|
|
||||||
|
|
||||||
export const getStyles = (menuWidth?: number) => (theme: GrafanaTheme2) => {
|
|
||||||
return {
|
|
||||||
menuWrapper: css`
|
|
||||||
display: flex;
|
|
||||||
max-height: 650px;
|
|
||||||
position: absolute;
|
|
||||||
z-index: ${theme.zIndex.dropdown};
|
|
||||||
overflow: hidden;
|
|
||||||
min-width: auto;
|
|
||||||
`,
|
|
||||||
menu: css`
|
|
||||||
min-width: ${theme.spacing(menuWidth || 0)};
|
|
||||||
|
|
||||||
& > div {
|
|
||||||
padding-top: ${theme.spacing(1)};
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
menuLeft: css`
|
|
||||||
right: 0;
|
|
||||||
flex-direction: row-reverse;
|
|
||||||
`,
|
|
||||||
container: css`
|
|
||||||
padding: ${theme.spacing(1)};
|
|
||||||
border: 1px ${theme.colors.border.weak} solid;
|
|
||||||
border-radius: ${theme.shape.borderRadius(1)};
|
|
||||||
background-color: ${theme.colors.background.primary};
|
|
||||||
z-index: ${theme.zIndex.modal};
|
|
||||||
`,
|
|
||||||
menuOptionWrapper: css`
|
|
||||||
padding: ${theme.spacing(0.5)};
|
|
||||||
`,
|
|
||||||
menuOptionBody: css`
|
|
||||||
font-weight: ${theme.typography.fontWeightRegular};
|
|
||||||
padding: ${theme.spacing(0, 1.5, 0, 0)};
|
|
||||||
`,
|
|
||||||
menuOptionDisabled: css`
|
|
||||||
color: ${theme.colors.text.disabled};
|
|
||||||
cursor: not-allowed;
|
|
||||||
`,
|
|
||||||
menuOptionInfoSign: css`
|
|
||||||
color: ${theme.colors.text.disabled};
|
|
||||||
`,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -2,7 +2,7 @@ import _ from 'lodash';
|
|||||||
import React, { useEffect, FormEvent } from 'react';
|
import React, { useEffect, FormEvent } from 'react';
|
||||||
import { useAsyncFn } from 'react-use';
|
import { useAsyncFn } from 'react-use';
|
||||||
import { AnnotationQuery, SelectableValue } from '@grafana/data';
|
import { AnnotationQuery, SelectableValue } from '@grafana/data';
|
||||||
import { InlineField, InlineSwitch, Input, Select } from '@grafana/ui';
|
import { Combobox, ComboboxOption, InlineField, InlineSwitch, Input } from '@grafana/ui';
|
||||||
import { ZabbixMetricsQuery } from '../types/query';
|
import { ZabbixMetricsQuery } from '../types/query';
|
||||||
import { ZabbixQueryEditorProps } from './QueryEditor';
|
import { ZabbixQueryEditorProps } from './QueryEditor';
|
||||||
import { QueryEditorRow } from './QueryEditor/QueryEditorRow';
|
import { QueryEditorRow } from './QueryEditor/QueryEditorRow';
|
||||||
@@ -11,7 +11,7 @@ import { getVariableOptions } from './QueryEditor/utils';
|
|||||||
import { prepareAnnotation } from '../migrations';
|
import { prepareAnnotation } from '../migrations';
|
||||||
import { useInterpolatedQuery } from '../hooks/useInterpolatedQuery';
|
import { useInterpolatedQuery } from '../hooks/useInterpolatedQuery';
|
||||||
|
|
||||||
const severityOptions: Array<SelectableValue<number>> = [
|
const severityOptions: Array<ComboboxOption<number>> = [
|
||||||
{ value: 0, label: 'Not classified' },
|
{ value: 0, label: 'Not classified' },
|
||||||
{ value: 1, label: 'Information' },
|
{ value: 1, label: 'Information' },
|
||||||
{ value: 2, label: 'Warning' },
|
{ value: 2, label: 'Warning' },
|
||||||
@@ -47,7 +47,7 @@ export const AnnotationQueryEditor = ({ annotation, onAnnotationChange, datasour
|
|||||||
|
|
||||||
const loadHostOptions = async (group: string) => {
|
const loadHostOptions = async (group: string) => {
|
||||||
const hosts = await datasource.zabbix.getAllHosts(group);
|
const hosts = await datasource.zabbix.getAllHosts(group);
|
||||||
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
let options: Array<ComboboxOption<string>> = hosts?.map((host) => ({
|
||||||
value: host.name,
|
value: host.name,
|
||||||
label: host.name,
|
label: host.name,
|
||||||
}));
|
}));
|
||||||
@@ -64,7 +64,7 @@ export const AnnotationQueryEditor = ({ annotation, onAnnotationChange, datasour
|
|||||||
|
|
||||||
const loadAppOptions = async (group: string, host: string) => {
|
const loadAppOptions = async (group: string, host: string) => {
|
||||||
const apps = await datasource.zabbix.getAllApps(group, host);
|
const apps = await datasource.zabbix.getAllApps(group, host);
|
||||||
let options: Array<SelectableValue<string>> = apps?.map((app) => ({
|
let options: Array<ComboboxOption<string>> = apps?.map((app) => ({
|
||||||
value: app.name,
|
value: app.name,
|
||||||
label: app.name,
|
label: app.name,
|
||||||
}));
|
}));
|
||||||
@@ -138,6 +138,7 @@ export const AnnotationQueryEditor = ({ annotation, onAnnotationChange, datasour
|
|||||||
options={groupsOptions}
|
options={groupsOptions}
|
||||||
isLoading={groupsLoading}
|
isLoading={groupsLoading}
|
||||||
onChange={onFilterChange('group')}
|
onChange={onFilterChange('group')}
|
||||||
|
placeholder="Group name"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
<InlineField label="Host" labelWidth={12}>
|
<InlineField label="Host" labelWidth={12}>
|
||||||
@@ -147,6 +148,7 @@ export const AnnotationQueryEditor = ({ annotation, onAnnotationChange, datasour
|
|||||||
options={hostOptions}
|
options={hostOptions}
|
||||||
isLoading={hostsLoading}
|
isLoading={hostsLoading}
|
||||||
onChange={onFilterChange('host')}
|
onChange={onFilterChange('host')}
|
||||||
|
placeholder="Host name"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
</QueryEditorRow>
|
</QueryEditorRow>
|
||||||
@@ -158,6 +160,7 @@ export const AnnotationQueryEditor = ({ annotation, onAnnotationChange, datasour
|
|||||||
options={appOptions}
|
options={appOptions}
|
||||||
isLoading={appsLoading}
|
isLoading={appsLoading}
|
||||||
onChange={onFilterChange('application')}
|
onChange={onFilterChange('application')}
|
||||||
|
placeholder="Application name"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
<InlineField label="Problem" labelWidth={12}>
|
<InlineField label="Problem" labelWidth={12}>
|
||||||
@@ -171,8 +174,7 @@ export const AnnotationQueryEditor = ({ annotation, onAnnotationChange, datasour
|
|||||||
</QueryEditorRow>
|
</QueryEditorRow>
|
||||||
<>
|
<>
|
||||||
<InlineField label="Min severity" labelWidth={12}>
|
<InlineField label="Min severity" labelWidth={12}>
|
||||||
<Select
|
<Combobox
|
||||||
isSearchable={false}
|
|
||||||
width={24}
|
width={24}
|
||||||
value={query.options?.minSeverity}
|
value={query.options?.minSeverity}
|
||||||
options={severityOptions}
|
options={severityOptions}
|
||||||
|
|||||||
@@ -2,13 +2,14 @@ import React, { useEffect, useState } from 'react';
|
|||||||
import { getDataSourceSrv, config } from '@grafana/runtime';
|
import { getDataSourceSrv, config } from '@grafana/runtime';
|
||||||
import { DataSourcePluginOptionsEditorProps, DataSourceSettings, GrafanaTheme2, SelectableValue } from '@grafana/data';
|
import { DataSourcePluginOptionsEditorProps, DataSourceSettings, GrafanaTheme2, SelectableValue } from '@grafana/data';
|
||||||
import {
|
import {
|
||||||
|
Combobox,
|
||||||
|
ComboboxOption,
|
||||||
Field,
|
Field,
|
||||||
Icon,
|
Icon,
|
||||||
Input,
|
Input,
|
||||||
Label,
|
Label,
|
||||||
SecretInput,
|
SecretInput,
|
||||||
SecureSocksProxySettings,
|
SecureSocksProxySettings,
|
||||||
Select,
|
|
||||||
Switch,
|
Switch,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
useStyles2,
|
useStyles2,
|
||||||
@@ -31,7 +32,7 @@ import { css } from '@emotion/css';
|
|||||||
// the postgres-plugin changed it's id, so we list both the old name and the new name
|
// the postgres-plugin changed it's id, so we list both the old name and the new name
|
||||||
const SUPPORTED_SQL_DS = ['mysql', 'grafana-postgresql-datasource', 'postgres', 'influxdb'];
|
const SUPPORTED_SQL_DS = ['mysql', 'grafana-postgresql-datasource', 'postgres', 'influxdb'];
|
||||||
|
|
||||||
const authOptions: Array<SelectableValue<ZabbixAuthType>> = [
|
const authOptions: Array<ComboboxOption<ZabbixAuthType>> = [
|
||||||
{ label: 'User and password', value: ZabbixAuthType.UserLogin },
|
{ label: 'User and password', value: ZabbixAuthType.UserLogin },
|
||||||
{ label: 'API token', value: ZabbixAuthType.Token },
|
{ label: 'API token', value: ZabbixAuthType.Token },
|
||||||
];
|
];
|
||||||
@@ -130,7 +131,7 @@ export const ConfigEditor = (props: Props) => {
|
|||||||
|
|
||||||
<ConfigSection title="Zabbix Connection">
|
<ConfigSection title="Zabbix Connection">
|
||||||
<Field label="Auth type">
|
<Field label="Auth type">
|
||||||
<Select
|
<Combobox
|
||||||
width={40}
|
width={40}
|
||||||
options={authOptions}
|
options={authOptions}
|
||||||
value={options.jsonData.authType}
|
value={options.jsonData.authType}
|
||||||
@@ -313,7 +314,7 @@ export const ConfigEditor = (props: Props) => {
|
|||||||
{options.jsonData.dbConnectionEnable && (
|
{options.jsonData.dbConnectionEnable && (
|
||||||
<>
|
<>
|
||||||
<Field label="Data Source">
|
<Field label="Data Source">
|
||||||
<Select
|
<Combobox
|
||||||
width={40}
|
width={40}
|
||||||
value={selectedDBDatasource}
|
value={selectedDBDatasource}
|
||||||
options={getDirectDBDSOptions()}
|
options={getDirectDBDSOptions()}
|
||||||
@@ -323,6 +324,7 @@ export const ConfigEditor = (props: Props) => {
|
|||||||
setSelectedDBDatasource,
|
setSelectedDBDatasource,
|
||||||
setCurrentDSType
|
setCurrentDSType
|
||||||
)}
|
)}
|
||||||
|
placeholder="Select a DB datasource (MySQL, PostgreSQL, InfluxDB)"
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
@@ -419,7 +421,7 @@ const jsonDataSelectHandler =
|
|||||||
value: DataSourceSettings<ZabbixDSOptions, ZabbixSecureJSONData>,
|
value: DataSourceSettings<ZabbixDSOptions, ZabbixSecureJSONData>,
|
||||||
onChange: Props['onOptionsChange']
|
onChange: Props['onOptionsChange']
|
||||||
) =>
|
) =>
|
||||||
(option: SelectableValue) => {
|
(option: ComboboxOption) => {
|
||||||
onChange({
|
onChange({
|
||||||
...value,
|
...value,
|
||||||
jsonData: {
|
jsonData: {
|
||||||
@@ -505,7 +507,7 @@ const getDirectDBDatasources = () => {
|
|||||||
|
|
||||||
const getDirectDBDSOptions = () => {
|
const getDirectDBDSOptions = () => {
|
||||||
const dsList = getDirectDBDatasources();
|
const dsList = getDirectDBDatasources();
|
||||||
const dsOpts: Array<SelectableValue<number>> = dsList.map((ds) => ({
|
const dsOpts: Array<ComboboxOption<number>> = dsList.map((ds) => ({
|
||||||
label: ds.name,
|
label: ds.name,
|
||||||
value: ds.id,
|
value: ds.id,
|
||||||
description: ds.type,
|
description: ds.type,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { QueryEditorProps, SelectableValue } from '@grafana/data';
|
import { QueryEditorProps } from '@grafana/data';
|
||||||
import { InlineField, Select } from '@grafana/ui';
|
import { Combobox, ComboboxOption, InlineField, Stack } from '@grafana/ui';
|
||||||
import * as c from '../constants';
|
import * as c from '../constants';
|
||||||
import { migrate, DS_QUERY_SCHEMA } from '../migrations';
|
import { migrate, DS_QUERY_SCHEMA } from '../migrations';
|
||||||
import { ZabbixDatasource } from '../datasource';
|
import { ZabbixDatasource } from '../datasource';
|
||||||
@@ -17,7 +17,7 @@ import { TriggersQueryEditor } from './QueryEditor/TriggersQueryEditor';
|
|||||||
import { UserMacrosQueryEditor } from './QueryEditor/UserMacrosQueryEditor';
|
import { UserMacrosQueryEditor } from './QueryEditor/UserMacrosQueryEditor';
|
||||||
import { QueryEditorRow } from './QueryEditor/QueryEditorRow';
|
import { QueryEditorRow } from './QueryEditor/QueryEditorRow';
|
||||||
|
|
||||||
const zabbixQueryTypeOptions: Array<SelectableValue<QueryType>> = [
|
const zabbixQueryTypeOptions: Array<ComboboxOption<QueryType>> = [
|
||||||
{
|
{
|
||||||
value: c.MODE_METRICS,
|
value: c.MODE_METRICS,
|
||||||
label: 'Metrics',
|
label: 'Metrics',
|
||||||
@@ -133,7 +133,7 @@ export const QueryEditor = ({ query, datasource, onChange, onRunQuery }: ZabbixQ
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onPropChange = (prop: string) => {
|
const onPropChange = (prop: string) => {
|
||||||
return (option: SelectableValue) => {
|
return (option: ComboboxOption) => {
|
||||||
if (option.value !== null) {
|
if (option.value !== null) {
|
||||||
onChangeInternal({ ...query, [prop]: option.value });
|
onChangeInternal({ ...query, [prop]: option.value });
|
||||||
}
|
}
|
||||||
@@ -171,7 +171,6 @@ export const QueryEditor = ({ query, datasource, onChange, onRunQuery }: ZabbixQ
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TextMetricsQueryEditor query={query} datasource={datasource} onChange={onChangeInternal} />
|
<TextMetricsQueryEditor query={query} datasource={datasource} onChange={onChangeInternal} />
|
||||||
{/* <QueryFunctionsEditor query={query} onChange={onChangeInternal} /> */}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -198,11 +197,10 @@ export const QueryEditor = ({ query, datasource, onChange, onRunQuery }: ZabbixQ
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Stack direction="column">
|
||||||
<QueryEditorRow>
|
<QueryEditorRow>
|
||||||
<InlineField label="Query type" labelWidth={12}>
|
<InlineField label="Query type" labelWidth={12}>
|
||||||
<Select<QueryType>
|
<Combobox<QueryType>
|
||||||
isSearchable={false}
|
|
||||||
width={24}
|
width={24}
|
||||||
value={queryType}
|
value={queryType}
|
||||||
options={zabbixQueryTypeOptions}
|
options={zabbixQueryTypeOptions}
|
||||||
@@ -218,6 +216,6 @@ export const QueryEditor = ({ query, datasource, onChange, onRunQuery }: ZabbixQ
|
|||||||
{queryType === c.MODE_TRIGGERS && renderTriggersEditor()}
|
{queryType === c.MODE_TRIGGERS && renderTriggersEditor()}
|
||||||
{queryType === c.MODE_MACROS && renderUserMacrosEditor()}
|
{queryType === c.MODE_MACROS && renderUserMacrosEditor()}
|
||||||
<QueryOptionsEditor queryType={queryType} queryOptions={query.options} onChange={onOptionsChange} />
|
<QueryOptionsEditor queryType={queryType} queryOptions={query.options} onChange={onOptionsChange} />
|
||||||
</>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ import _ from 'lodash';
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { useAsyncFn } from 'react-use';
|
import { useAsyncFn } from 'react-use';
|
||||||
|
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { InlineField, ComboboxOption } from '@grafana/ui';
|
||||||
import { InlineField } from '@grafana/ui';
|
|
||||||
import { QueryEditorRow } from './QueryEditorRow';
|
import { QueryEditorRow } from './QueryEditorRow';
|
||||||
import { MetricPicker } from '../../../components';
|
import { MetricPicker } from '../../../components';
|
||||||
import { getVariableOptions } from './utils';
|
import { getVariableOptions } from './utils';
|
||||||
@@ -39,7 +38,7 @@ export const MetricsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
|
|
||||||
const loadHostOptions = async (group: string) => {
|
const loadHostOptions = async (group: string) => {
|
||||||
const hosts = await datasource.zabbix.getAllHosts(group);
|
const hosts = await datasource.zabbix.getAllHosts(group);
|
||||||
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
let options: Array<ComboboxOption<string>> = hosts?.map((host) => ({
|
||||||
value: host.name,
|
value: host.name,
|
||||||
label: host.name,
|
label: host.name,
|
||||||
}));
|
}));
|
||||||
@@ -56,7 +55,7 @@ export const MetricsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
|
|
||||||
const loadAppOptions = async (group: string, host: string) => {
|
const loadAppOptions = async (group: string, host: string) => {
|
||||||
const apps = await datasource.zabbix.getAllApps(group, host);
|
const apps = await datasource.zabbix.getAllApps(group, host);
|
||||||
let options: Array<SelectableValue<string>> = apps?.map((app) => ({
|
let options: Array<ComboboxOption<string>> = apps?.map((app) => ({
|
||||||
value: app.name,
|
value: app.name,
|
||||||
label: app.name,
|
label: app.name,
|
||||||
}));
|
}));
|
||||||
@@ -81,7 +80,7 @@ export const MetricsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
// const tags: ZBXItemTag[] = await datasource.zabbix.getItemTags(groupFilter, hostFilter, null);
|
// const tags: ZBXItemTag[] = await datasource.zabbix.getItemTags(groupFilter, hostFilter, null);
|
||||||
|
|
||||||
const tagList = _.uniqBy(tags, (t) => t.tag + t.value || '').map((t) => itemTagToString(t));
|
const tagList = _.uniqBy(tags, (t) => t.tag + t.value || '').map((t) => itemTagToString(t));
|
||||||
let options: Array<SelectableValue<string>> = tagList?.map((tag) => ({
|
let options: Array<ComboboxOption<string>> = tagList?.map((tag) => ({
|
||||||
value: tag,
|
value: tag,
|
||||||
label: tag,
|
label: tag,
|
||||||
}));
|
}));
|
||||||
@@ -101,7 +100,7 @@ export const MetricsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
showDisabledItems: query.options.showDisabledItems,
|
showDisabledItems: query.options.showDisabledItems,
|
||||||
};
|
};
|
||||||
const items = await datasource.zabbix.getAllItems(group, host, app, itemTag, options);
|
const items = await datasource.zabbix.getAllItems(group, host, app, itemTag, options);
|
||||||
let itemOptions: Array<SelectableValue<string>> = items?.map((item) => ({
|
let itemOptions: Array<ComboboxOption<string>> = items?.map((item) => ({
|
||||||
value: item.name,
|
value: item.name,
|
||||||
label: item.name,
|
label: item.name,
|
||||||
}));
|
}));
|
||||||
@@ -171,6 +170,7 @@ export const MetricsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
options={groupsOptions}
|
options={groupsOptions}
|
||||||
isLoading={groupsLoading}
|
isLoading={groupsLoading}
|
||||||
onChange={onFilterChange('group')}
|
onChange={onFilterChange('group')}
|
||||||
|
placeholder="Group name"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
<InlineField label="Host" labelWidth={12}>
|
<InlineField label="Host" labelWidth={12}>
|
||||||
@@ -180,6 +180,7 @@ export const MetricsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
options={hostOptions}
|
options={hostOptions}
|
||||||
isLoading={hostsLoading}
|
isLoading={hostsLoading}
|
||||||
onChange={onFilterChange('host')}
|
onChange={onFilterChange('host')}
|
||||||
|
placeholder="Host name"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
</QueryEditorRow>
|
</QueryEditorRow>
|
||||||
@@ -192,6 +193,7 @@ export const MetricsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
options={appOptions}
|
options={appOptions}
|
||||||
isLoading={appsLoading}
|
isLoading={appsLoading}
|
||||||
onChange={onFilterChange('application')}
|
onChange={onFilterChange('application')}
|
||||||
|
placeholder="Application name"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
)}
|
)}
|
||||||
@@ -203,6 +205,7 @@ export const MetricsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
options={tagOptions}
|
options={tagOptions}
|
||||||
isLoading={tagsLoading}
|
isLoading={tagsLoading}
|
||||||
onChange={onFilterChange('itemTag')}
|
onChange={onFilterChange('itemTag')}
|
||||||
|
placeholder="Item tag name"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
)}
|
)}
|
||||||
@@ -213,6 +216,7 @@ export const MetricsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
options={itemOptions}
|
options={itemOptions}
|
||||||
isLoading={itemsLoading}
|
isLoading={itemsLoading}
|
||||||
onChange={onFilterChange('item')}
|
onChange={onFilterChange('item')}
|
||||||
|
placeholder="Item name"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
</QueryEditorRow>
|
</QueryEditorRow>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import React, { useEffect, FormEvent } from 'react';
|
|||||||
import { useAsyncFn } from 'react-use';
|
import { useAsyncFn } from 'react-use';
|
||||||
|
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { SelectableValue } from '@grafana/data';
|
||||||
import { InlineField, Input, MultiSelect, Select } from '@grafana/ui';
|
import { Combobox, ComboboxOption, InlineField, Input, MultiSelect } from '@grafana/ui';
|
||||||
import { QueryEditorRow } from './QueryEditorRow';
|
import { QueryEditorRow } from './QueryEditorRow';
|
||||||
import { MetricPicker } from '../../../components';
|
import { MetricPicker } from '../../../components';
|
||||||
import { getVariableOptions } from './utils';
|
import { getVariableOptions } from './utils';
|
||||||
@@ -11,7 +11,7 @@ import { ZabbixDatasource } from '../../datasource';
|
|||||||
import { ZabbixMetricsQuery, ZabbixTagEvalType } from '../../types/query';
|
import { ZabbixMetricsQuery, ZabbixTagEvalType } from '../../types/query';
|
||||||
import { useInterpolatedQuery } from '../../hooks/useInterpolatedQuery';
|
import { useInterpolatedQuery } from '../../hooks/useInterpolatedQuery';
|
||||||
|
|
||||||
const showProblemsOptions: Array<SelectableValue<string>> = [
|
const showProblemsOptions: Array<ComboboxOption<string>> = [
|
||||||
{ label: 'Problems', value: 'problems' },
|
{ label: 'Problems', value: 'problems' },
|
||||||
{ label: 'Recent problems', value: 'recent' },
|
{ label: 'Recent problems', value: 'recent' },
|
||||||
{ label: 'History', value: 'history' },
|
{ label: 'History', value: 'history' },
|
||||||
@@ -26,7 +26,7 @@ const severityOptions: Array<SelectableValue<number>> = [
|
|||||||
{ value: 5, label: 'Disaster' },
|
{ value: 5, label: 'Disaster' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const evaltypeOptions: Array<SelectableValue<ZabbixTagEvalType>> = [
|
const evaltypeOptions: Array<ComboboxOption<ZabbixTagEvalType>> = [
|
||||||
{ label: 'AND/OR', value: ZabbixTagEvalType.AndOr },
|
{ label: 'AND/OR', value: ZabbixTagEvalType.AndOr },
|
||||||
{ label: 'OR', value: ZabbixTagEvalType.Or },
|
{ label: 'OR', value: ZabbixTagEvalType.Or },
|
||||||
];
|
];
|
||||||
@@ -57,7 +57,7 @@ export const ProblemsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
|
|
||||||
const loadHostOptions = async (group: string) => {
|
const loadHostOptions = async (group: string) => {
|
||||||
const hosts = await datasource.zabbix.getAllHosts(group);
|
const hosts = await datasource.zabbix.getAllHosts(group);
|
||||||
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
let options: Array<ComboboxOption<string>> = hosts?.map((host) => ({
|
||||||
value: host.name,
|
value: host.name,
|
||||||
label: host.name,
|
label: host.name,
|
||||||
}));
|
}));
|
||||||
@@ -74,7 +74,7 @@ export const ProblemsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
|
|
||||||
const loadAppOptions = async (group: string, host: string) => {
|
const loadAppOptions = async (group: string, host: string) => {
|
||||||
const apps = await datasource.zabbix.getAllApps(group, host);
|
const apps = await datasource.zabbix.getAllApps(group, host);
|
||||||
let options: Array<SelectableValue<string>> = apps?.map((app) => ({
|
let options: Array<ComboboxOption<string>> = apps?.map((app) => ({
|
||||||
value: app.name,
|
value: app.name,
|
||||||
label: app.name,
|
label: app.name,
|
||||||
}));
|
}));
|
||||||
@@ -166,6 +166,7 @@ export const ProblemsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
options={groupsOptions}
|
options={groupsOptions}
|
||||||
isLoading={groupsLoading}
|
isLoading={groupsLoading}
|
||||||
onChange={onFilterChange('group')}
|
onChange={onFilterChange('group')}
|
||||||
|
placeholder="Group name"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
<InlineField label="Host" labelWidth={12}>
|
<InlineField label="Host" labelWidth={12}>
|
||||||
@@ -175,6 +176,7 @@ export const ProblemsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
options={hostOptions}
|
options={hostOptions}
|
||||||
isLoading={hostsLoading}
|
isLoading={hostsLoading}
|
||||||
onChange={onFilterChange('host')}
|
onChange={onFilterChange('host')}
|
||||||
|
placeholder="Host name"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
<InlineField label="Proxy" labelWidth={12}>
|
<InlineField label="Proxy" labelWidth={12}>
|
||||||
@@ -184,6 +186,7 @@ export const ProblemsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
options={proxiesOptions}
|
options={proxiesOptions}
|
||||||
isLoading={proxiesLoading}
|
isLoading={proxiesLoading}
|
||||||
onChange={onFilterChange('proxy')}
|
onChange={onFilterChange('proxy')}
|
||||||
|
placeholder="Proxy name"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
</QueryEditorRow>
|
</QueryEditorRow>
|
||||||
@@ -196,6 +199,7 @@ export const ProblemsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
options={appOptions}
|
options={appOptions}
|
||||||
isLoading={appsLoading}
|
isLoading={appsLoading}
|
||||||
onChange={onFilterChange('application')}
|
onChange={onFilterChange('application')}
|
||||||
|
placeholder="Application name"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
)}
|
)}
|
||||||
@@ -216,19 +220,12 @@ export const ProblemsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
<InlineField>
|
<InlineField>
|
||||||
<Select
|
<Combobox width={15} value={query.evaltype} options={evaltypeOptions} onChange={onPropChange('evaltype')} />
|
||||||
isSearchable={false}
|
|
||||||
width={15}
|
|
||||||
value={query.evaltype}
|
|
||||||
options={evaltypeOptions}
|
|
||||||
onChange={onPropChange('evaltype')}
|
|
||||||
/>
|
|
||||||
</InlineField>
|
</InlineField>
|
||||||
</QueryEditorRow>
|
</QueryEditorRow>
|
||||||
<QueryEditorRow>
|
<QueryEditorRow>
|
||||||
<InlineField label="Show" labelWidth={12}>
|
<InlineField label="Show" labelWidth={12}>
|
||||||
<Select
|
<Combobox
|
||||||
isSearchable={false}
|
|
||||||
width={24}
|
width={24}
|
||||||
value={query.showProblems}
|
value={query.showProblems}
|
||||||
options={showProblemsOptions}
|
options={showProblemsOptions}
|
||||||
|
|||||||
@@ -2,31 +2,32 @@ import { css } from '@emotion/css';
|
|||||||
import React, { useState, FormEvent, ReactNode } from 'react';
|
import React, { useState, FormEvent, ReactNode } from 'react';
|
||||||
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
||||||
import {
|
import {
|
||||||
HorizontalGroup,
|
Combobox,
|
||||||
|
ComboboxOption,
|
||||||
Icon,
|
Icon,
|
||||||
InlineField,
|
InlineField,
|
||||||
InlineFieldRow,
|
InlineFieldRow,
|
||||||
InlineSwitch,
|
InlineSwitch,
|
||||||
Input,
|
Input,
|
||||||
Select,
|
Stack,
|
||||||
useStyles2,
|
useStyles2,
|
||||||
} from '@grafana/ui';
|
} from '@grafana/ui';
|
||||||
import * as c from '../../constants';
|
import * as c from '../../constants';
|
||||||
import { ZabbixQueryOptions } from '../../types/query';
|
import { ZabbixQueryOptions } from '../../types/query';
|
||||||
|
|
||||||
const ackOptions: Array<SelectableValue<number>> = [
|
const ackOptions: Array<ComboboxOption<number>> = [
|
||||||
{ label: 'all triggers', value: 2 },
|
{ label: 'all triggers', value: 2 },
|
||||||
{ label: 'unacknowledged', value: 0 },
|
{ label: 'unacknowledged', value: 0 },
|
||||||
{ label: 'acknowledged', value: 1 },
|
{ label: 'acknowledged', value: 1 },
|
||||||
];
|
];
|
||||||
|
|
||||||
const sortOptions: Array<SelectableValue<string>> = [
|
const sortOptions: Array<ComboboxOption<string>> = [
|
||||||
{ label: 'Default', value: 'default' },
|
{ label: 'Default', value: 'default' },
|
||||||
{ label: 'Last change', value: 'lastchange' },
|
{ label: 'Last change', value: 'lastchange' },
|
||||||
{ label: 'Severity', value: 'severity' },
|
{ label: 'Severity', value: 'severity' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const trendsOptions: Array<SelectableValue<string>> = [
|
const trendsOptions: Array<ComboboxOption<string>> = [
|
||||||
{ label: 'Default', value: 'default' },
|
{ label: 'Default', value: 'default' },
|
||||||
{ label: 'True', value: 'true' },
|
{ label: 'True', value: 'true' },
|
||||||
{ label: 'False', value: 'false' },
|
{ label: 'False', value: 'false' },
|
||||||
@@ -60,12 +61,12 @@ export const QueryOptionsEditor = ({ queryType, queryOptions, onChange }: Props)
|
|||||||
const renderClosed = () => {
|
const renderClosed = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<HorizontalGroup>
|
<Stack>
|
||||||
{!isOpen && <Icon name="angle-right" />}
|
{!isOpen && <Icon name="angle-right" />}
|
||||||
{isOpen && <Icon name="angle-down" />}
|
{isOpen && <Icon name="angle-down" />}
|
||||||
<span className={styles.label}>Options</span>
|
<span className={styles.label}>Options</span>
|
||||||
<div className={styles.options}>{renderOptions()}</div>
|
<div className={styles.options}>{renderOptions()}</div>
|
||||||
</HorizontalGroup>
|
</Stack>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -100,8 +101,7 @@ export const QueryOptionsEditor = ({ queryType, queryOptions, onChange }: Props)
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<InlineField label="Trends" labelWidth={24}>
|
<InlineField label="Trends" labelWidth={24}>
|
||||||
<Select
|
<Combobox
|
||||||
isSearchable={false}
|
|
||||||
width={16}
|
width={16}
|
||||||
value={queryOptions.useTrends}
|
value={queryOptions.useTrends}
|
||||||
options={trendsOptions}
|
options={trendsOptions}
|
||||||
@@ -147,8 +147,7 @@ export const QueryOptionsEditor = ({ queryType, queryOptions, onChange }: Props)
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<InlineField label="Acknowledged" labelWidth={24}>
|
<InlineField label="Acknowledged" labelWidth={24}>
|
||||||
<Select
|
<Combobox
|
||||||
isSearchable={false}
|
|
||||||
width={24}
|
width={24}
|
||||||
value={queryOptions.acknowledged}
|
value={queryOptions.acknowledged}
|
||||||
options={ackOptions}
|
options={ackOptions}
|
||||||
@@ -156,8 +155,7 @@ export const QueryOptionsEditor = ({ queryType, queryOptions, onChange }: Props)
|
|||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
<InlineField label="Sort by" labelWidth={24}>
|
<InlineField label="Sort by" labelWidth={24}>
|
||||||
<Select
|
<Combobox
|
||||||
isSearchable={false}
|
|
||||||
width={24}
|
width={24}
|
||||||
value={queryOptions.sortProblems}
|
value={queryOptions.sortProblems}
|
||||||
options={sortOptions}
|
options={sortOptions}
|
||||||
@@ -193,8 +191,7 @@ export const QueryOptionsEditor = ({ queryType, queryOptions, onChange }: Props)
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<InlineField label="Acknowledged" labelWidth={24}>
|
<InlineField label="Acknowledged" labelWidth={24}>
|
||||||
<Select
|
<Combobox
|
||||||
isSearchable={false}
|
|
||||||
width={24}
|
width={24}
|
||||||
value={queryOptions.acknowledged}
|
value={queryOptions.acknowledged}
|
||||||
options={ackOptions}
|
options={ackOptions}
|
||||||
@@ -213,7 +210,7 @@ export const QueryOptionsEditor = ({ queryType, queryOptions, onChange }: Props)
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<InlineFieldRow>
|
<InlineFieldRow style={{ alignItems: 'center' }}>
|
||||||
<div className={styles.container} onClick={() => setIsOpen(!isOpen)}>
|
<div className={styles.container} onClick={() => setIsOpen(!isOpen)}>
|
||||||
{renderClosed()}
|
{renderClosed()}
|
||||||
</div>
|
</div>
|
||||||
@@ -226,12 +223,14 @@ export const QueryOptionsEditor = ({ queryType, queryOptions, onChange }: Props)
|
|||||||
const getStyles = (theme: GrafanaTheme2) => ({
|
const getStyles = (theme: GrafanaTheme2) => ({
|
||||||
container: css({
|
container: css({
|
||||||
backgroundColor: theme.colors.background.secondary,
|
backgroundColor: theme.colors.background.secondary,
|
||||||
borderRadius: theme.shape.borderRadius(),
|
borderRadius: theme.shape.radius.default,
|
||||||
marginRight: theme.spacing(0.5),
|
marginRight: theme.spacing(0.5),
|
||||||
marginBottom: theme.spacing(0.5),
|
marginBottom: theme.spacing(0.5),
|
||||||
padding: `0 ${theme.spacing(1)}`,
|
padding: `0 ${theme.spacing(1)}`,
|
||||||
height: `${theme.v1.spacing.formInputHeight}px`,
|
height: theme.spacing(4),
|
||||||
width: `100%`,
|
width: `100%`,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
}),
|
}),
|
||||||
label: css({
|
label: css({
|
||||||
color: theme.colors.info.text,
|
color: theme.colors.info.text,
|
||||||
@@ -241,13 +240,15 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
options: css({
|
options: css({
|
||||||
color: theme.colors.text.disabled,
|
color: theme.colors.text.disabled,
|
||||||
fontSize: theme.typography.bodySmall.fontSize,
|
fontSize: theme.typography.bodySmall.fontSize,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
}),
|
||||||
|
optionContainer: css({
|
||||||
|
marginRight: theme.spacing(2),
|
||||||
|
}),
|
||||||
|
editorContainer: css({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
marginLeft: theme.spacing(4),
|
||||||
}),
|
}),
|
||||||
optionContainer: css`
|
|
||||||
margin-right: ${theme.spacing(2)};
|
|
||||||
`,
|
|
||||||
editorContainer: css`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
margin-left: ${theme.spacing(4)};
|
|
||||||
`,
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,15 +2,14 @@ import _ from 'lodash';
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { useAsyncFn } from 'react-use';
|
import { useAsyncFn } from 'react-use';
|
||||||
|
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { Combobox, ComboboxOption, InlineField } from '@grafana/ui';
|
||||||
import { InlineField, Select } from '@grafana/ui';
|
|
||||||
import { QueryEditorRow } from './QueryEditorRow';
|
import { QueryEditorRow } from './QueryEditorRow';
|
||||||
import { MetricPicker } from '../../../components';
|
import { MetricPicker } from '../../../components';
|
||||||
import { getVariableOptions } from './utils';
|
import { getVariableOptions } from './utils';
|
||||||
import { ZabbixDatasource } from '../../datasource';
|
import { ZabbixDatasource } from '../../datasource';
|
||||||
import { ZabbixMetricsQuery } from '../../types/query';
|
import { ZabbixMetricsQuery } from '../../types/query';
|
||||||
|
|
||||||
const slaPropertyList: Array<SelectableValue<string>> = [
|
const slaPropertyList: Array<ComboboxOption<string>> = [
|
||||||
{ label: 'Status', value: 'status' },
|
{ label: 'Status', value: 'status' },
|
||||||
{ label: 'SLI', value: 'sli' },
|
{ label: 'SLI', value: 'sli' },
|
||||||
{ label: 'Uptime', value: 'uptime' },
|
{ label: 'Uptime', value: 'uptime' },
|
||||||
@@ -18,7 +17,7 @@ const slaPropertyList: Array<SelectableValue<string>> = [
|
|||||||
{ label: 'Error budget', value: 'error_budget' },
|
{ label: 'Error budget', value: 'error_budget' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const slaIntervals: Array<SelectableValue<string>> = [
|
const slaIntervals: Array<ComboboxOption<string>> = [
|
||||||
{ label: 'No interval', value: 'none' },
|
{ label: 'No interval', value: 'none' },
|
||||||
{ label: 'Auto', value: 'auto' },
|
{ label: 'Auto', value: 'auto' },
|
||||||
{ label: '1 hour', value: '1h' },
|
{ label: '1 hour', value: '1h' },
|
||||||
@@ -71,7 +70,7 @@ export const ServicesQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onPropChange = (prop: string) => {
|
const onPropChange = (prop: string) => {
|
||||||
return (option: SelectableValue) => {
|
return (option: ComboboxOption) => {
|
||||||
if (option.value) {
|
if (option.value) {
|
||||||
onChange({ ...query, [prop]: option.value });
|
onChange({ ...query, [prop]: option.value });
|
||||||
}
|
}
|
||||||
@@ -96,6 +95,7 @@ export const ServicesQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
options={itServicesOptions}
|
options={itServicesOptions}
|
||||||
isLoading={itServicesLoading}
|
isLoading={itServicesLoading}
|
||||||
onChange={onStringPropChange('itServiceFilter')}
|
onChange={onStringPropChange('itServiceFilter')}
|
||||||
|
placeholder="Service name"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
<InlineField label="SLA" labelWidth={12}>
|
<InlineField label="SLA" labelWidth={12}>
|
||||||
@@ -105,26 +105,27 @@ export const ServicesQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
options={slaOptions}
|
options={slaOptions}
|
||||||
isLoading={slaLoading}
|
isLoading={slaLoading}
|
||||||
onChange={onStringPropChange('slaFilter')}
|
onChange={onStringPropChange('slaFilter')}
|
||||||
|
placeholder="SLA name"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
</QueryEditorRow>
|
</QueryEditorRow>
|
||||||
<QueryEditorRow>
|
<QueryEditorRow>
|
||||||
<InlineField label="Property" labelWidth={12}>
|
<InlineField label="Property" labelWidth={12}>
|
||||||
<Select
|
<Combobox
|
||||||
isSearchable={false}
|
|
||||||
width={24}
|
width={24}
|
||||||
value={query.slaProperty}
|
value={query.slaProperty}
|
||||||
options={slaPropertyList}
|
options={slaPropertyList}
|
||||||
onChange={onPropChange('slaProperty')}
|
onChange={onPropChange('slaProperty')}
|
||||||
|
placeholder="Property name"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
<InlineField label="Interval" labelWidth={12}>
|
<InlineField label="Interval" labelWidth={12}>
|
||||||
<Select
|
<Combobox
|
||||||
isSearchable={false}
|
|
||||||
width={24}
|
width={24}
|
||||||
value={query.slaInterval}
|
value={query.slaInterval}
|
||||||
options={slaIntervals}
|
options={slaIntervals}
|
||||||
onChange={onPropChange('slaInterval')}
|
onChange={onPropChange('slaInterval')}
|
||||||
|
placeholder="SLA interval"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
</QueryEditorRow>
|
</QueryEditorRow>
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ import _ from 'lodash';
|
|||||||
import React, { useEffect, FormEvent } from 'react';
|
import React, { useEffect, FormEvent } from 'react';
|
||||||
import { useAsyncFn } from 'react-use';
|
import { useAsyncFn } from 'react-use';
|
||||||
|
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { InlineField, InlineSwitch, Input, ComboboxOption } from '@grafana/ui';
|
||||||
import { InlineField, InlineSwitch, Input } from '@grafana/ui';
|
|
||||||
import { QueryEditorRow } from './QueryEditorRow';
|
import { QueryEditorRow } from './QueryEditorRow';
|
||||||
import { MetricPicker } from '../../../components';
|
import { MetricPicker } from '../../../components';
|
||||||
import { getVariableOptions } from './utils';
|
import { getVariableOptions } from './utils';
|
||||||
@@ -37,7 +36,7 @@ export const TextMetricsQueryEditor = ({ query, datasource, onChange }: Props) =
|
|||||||
|
|
||||||
const loadHostOptions = async (group: string) => {
|
const loadHostOptions = async (group: string) => {
|
||||||
const hosts = await datasource.zabbix.getAllHosts(group);
|
const hosts = await datasource.zabbix.getAllHosts(group);
|
||||||
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
let options: Array<ComboboxOption<string>> = hosts?.map((host) => ({
|
||||||
value: host.name,
|
value: host.name,
|
||||||
label: host.name,
|
label: host.name,
|
||||||
}));
|
}));
|
||||||
@@ -54,7 +53,7 @@ export const TextMetricsQueryEditor = ({ query, datasource, onChange }: Props) =
|
|||||||
|
|
||||||
const loadAppOptions = async (group: string, host: string) => {
|
const loadAppOptions = async (group: string, host: string) => {
|
||||||
const apps = await datasource.zabbix.getAllApps(group, host);
|
const apps = await datasource.zabbix.getAllApps(group, host);
|
||||||
let options: Array<SelectableValue<string>> = apps?.map((app) => ({
|
let options: Array<ComboboxOption<string>> = apps?.map((app) => ({
|
||||||
value: app.name,
|
value: app.name,
|
||||||
label: app.name,
|
label: app.name,
|
||||||
}));
|
}));
|
||||||
@@ -74,7 +73,7 @@ export const TextMetricsQueryEditor = ({ query, datasource, onChange }: Props) =
|
|||||||
showDisabledItems: query.options.showDisabledItems,
|
showDisabledItems: query.options.showDisabledItems,
|
||||||
};
|
};
|
||||||
const items = await datasource.zabbix.getAllItems(group, host, app, itemTag, options);
|
const items = await datasource.zabbix.getAllItems(group, host, app, itemTag, options);
|
||||||
let itemOptions: Array<SelectableValue<string>> = items?.map((item) => ({
|
let itemOptions: Array<ComboboxOption<string>> = items?.map((item) => ({
|
||||||
value: item.name,
|
value: item.name,
|
||||||
label: item.name,
|
label: item.name,
|
||||||
}));
|
}));
|
||||||
@@ -145,6 +144,7 @@ export const TextMetricsQueryEditor = ({ query, datasource, onChange }: Props) =
|
|||||||
options={groupsOptions}
|
options={groupsOptions}
|
||||||
isLoading={groupsLoading}
|
isLoading={groupsLoading}
|
||||||
onChange={onFilterChange('group')}
|
onChange={onFilterChange('group')}
|
||||||
|
placeholder="Group name"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
<InlineField label="Host" labelWidth={12}>
|
<InlineField label="Host" labelWidth={12}>
|
||||||
@@ -154,6 +154,7 @@ export const TextMetricsQueryEditor = ({ query, datasource, onChange }: Props) =
|
|||||||
options={hostOptions}
|
options={hostOptions}
|
||||||
isLoading={hostsLoading}
|
isLoading={hostsLoading}
|
||||||
onChange={onFilterChange('host')}
|
onChange={onFilterChange('host')}
|
||||||
|
placeholder="Host name"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
</QueryEditorRow>
|
</QueryEditorRow>
|
||||||
@@ -165,6 +166,7 @@ export const TextMetricsQueryEditor = ({ query, datasource, onChange }: Props) =
|
|||||||
options={appOptions}
|
options={appOptions}
|
||||||
isLoading={appsLoading}
|
isLoading={appsLoading}
|
||||||
onChange={onFilterChange('application')}
|
onChange={onFilterChange('application')}
|
||||||
|
placeholder="Application name"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
<InlineField label="Item" labelWidth={12}>
|
<InlineField label="Item" labelWidth={12}>
|
||||||
@@ -174,12 +176,18 @@ export const TextMetricsQueryEditor = ({ query, datasource, onChange }: Props) =
|
|||||||
options={itemOptions}
|
options={itemOptions}
|
||||||
isLoading={itemsLoading}
|
isLoading={itemsLoading}
|
||||||
onChange={onFilterChange('item')}
|
onChange={onFilterChange('item')}
|
||||||
|
placeholder="Item name"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
</QueryEditorRow>
|
</QueryEditorRow>
|
||||||
<QueryEditorRow>
|
<QueryEditorRow>
|
||||||
<InlineField label="Text filter" labelWidth={12}>
|
<InlineField label="Text filter" labelWidth={12}>
|
||||||
<Input width={24} defaultValue={query.textFilter} onBlur={onTextFilterChange} />
|
<Input
|
||||||
|
width={24}
|
||||||
|
defaultValue={query.textFilter}
|
||||||
|
onBlur={onTextFilterChange}
|
||||||
|
placeholder="Metric text filter"
|
||||||
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
<InlineField label="Use capture groups" labelWidth={18}>
|
<InlineField label="Use capture groups" labelWidth={18}>
|
||||||
<InlineSwitch
|
<InlineSwitch
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import React, { useEffect, FormEvent } from 'react';
|
|||||||
import { useAsyncFn } from 'react-use';
|
import { useAsyncFn } from 'react-use';
|
||||||
|
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { SelectableValue } from '@grafana/data';
|
||||||
import { InlineField, InlineSwitch, Input, Select } from '@grafana/ui';
|
import { Combobox, ComboboxOption, InlineField, InlineSwitch, Input } from '@grafana/ui';
|
||||||
import { QueryEditorRow } from './QueryEditorRow';
|
import { QueryEditorRow } from './QueryEditorRow';
|
||||||
import { MetricPicker } from '../../../components';
|
import { MetricPicker } from '../../../components';
|
||||||
import { getVariableOptions } from './utils';
|
import { getVariableOptions } from './utils';
|
||||||
@@ -13,13 +13,13 @@ import { ZabbixMetricsQuery } from '../../types/query';
|
|||||||
import { ZBXItem, ZBXItemTag } from '../../types';
|
import { ZBXItem, ZBXItemTag } from '../../types';
|
||||||
import { useInterpolatedQuery } from '../../hooks/useInterpolatedQuery';
|
import { useInterpolatedQuery } from '../../hooks/useInterpolatedQuery';
|
||||||
|
|
||||||
const countByOptions: Array<SelectableValue<string>> = [
|
const countByOptions: Array<ComboboxOption<string>> = [
|
||||||
{ value: '', label: 'All triggers' },
|
{ value: '', label: 'All triggers' },
|
||||||
{ value: 'problems', label: 'Problems' },
|
{ value: 'problems', label: 'Problems' },
|
||||||
{ value: 'items', label: 'Items' },
|
{ value: 'items', label: 'Items' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const severityOptions: Array<SelectableValue<number>> = [
|
const severityOptions: Array<ComboboxOption<number>> = [
|
||||||
{ value: 0, label: 'Not classified' },
|
{ value: 0, label: 'Not classified' },
|
||||||
{ value: 1, label: 'Information' },
|
{ value: 1, label: 'Information' },
|
||||||
{ value: 2, label: 'Warning' },
|
{ value: 2, label: 'Warning' },
|
||||||
@@ -54,7 +54,7 @@ export const TriggersQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
|
|
||||||
const loadHostOptions = async (group: string) => {
|
const loadHostOptions = async (group: string) => {
|
||||||
const hosts = await datasource.zabbix.getAllHosts(group);
|
const hosts = await datasource.zabbix.getAllHosts(group);
|
||||||
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
let options: Array<ComboboxOption<string>> = hosts?.map((host) => ({
|
||||||
value: host.name,
|
value: host.name,
|
||||||
label: host.name,
|
label: host.name,
|
||||||
}));
|
}));
|
||||||
@@ -71,7 +71,7 @@ export const TriggersQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
|
|
||||||
const loadAppOptions = async (group: string, host: string) => {
|
const loadAppOptions = async (group: string, host: string) => {
|
||||||
const apps = await datasource.zabbix.getAllApps(group, host);
|
const apps = await datasource.zabbix.getAllApps(group, host);
|
||||||
let options: Array<SelectableValue<string>> = apps?.map((app) => ({
|
let options: Array<ComboboxOption<string>> = apps?.map((app) => ({
|
||||||
value: app.name,
|
value: app.name,
|
||||||
label: app.name,
|
label: app.name,
|
||||||
}));
|
}));
|
||||||
@@ -94,7 +94,7 @@ export const TriggersQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
const tags: ZBXItemTag[] = _.flatten(items.map((item: ZBXItem) => item.tags || []));
|
const tags: ZBXItemTag[] = _.flatten(items.map((item: ZBXItem) => item.tags || []));
|
||||||
|
|
||||||
const tagList = _.uniqBy(tags, (t) => t.tag + t.value || '').map((t) => itemTagToString(t));
|
const tagList = _.uniqBy(tags, (t) => t.tag + t.value || '').map((t) => itemTagToString(t));
|
||||||
let options: Array<SelectableValue<string>> = tagList?.map((tag) => ({
|
let options: Array<ComboboxOption<string>> = tagList?.map((tag) => ({
|
||||||
value: tag,
|
value: tag,
|
||||||
label: tag,
|
label: tag,
|
||||||
}));
|
}));
|
||||||
@@ -129,7 +129,7 @@ export const TriggersQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
showDisabledItems: query.options.showDisabledItems,
|
showDisabledItems: query.options.showDisabledItems,
|
||||||
};
|
};
|
||||||
const items = await datasource.zabbix.getAllItems(group, host, app, itemTag, options);
|
const items = await datasource.zabbix.getAllItems(group, host, app, itemTag, options);
|
||||||
let itemOptions: Array<SelectableValue<string>> = items?.map((item) => ({
|
let itemOptions: Array<ComboboxOption<string>> = items?.map((item) => ({
|
||||||
value: item.name,
|
value: item.name,
|
||||||
label: item.name,
|
label: item.name,
|
||||||
}));
|
}));
|
||||||
@@ -218,13 +218,7 @@ export const TriggersQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
<>
|
<>
|
||||||
<QueryEditorRow>
|
<QueryEditorRow>
|
||||||
<InlineField label="Count by" labelWidth={12}>
|
<InlineField label="Count by" labelWidth={12}>
|
||||||
<Select
|
<Combobox width={24} value={query.countTriggersBy} options={countByOptions} onChange={onCountByChange} />
|
||||||
isSearchable={false}
|
|
||||||
width={24}
|
|
||||||
value={query.countTriggersBy}
|
|
||||||
options={countByOptions}
|
|
||||||
onChange={onCountByChange}
|
|
||||||
/>
|
|
||||||
</InlineField>
|
</InlineField>
|
||||||
</QueryEditorRow>
|
</QueryEditorRow>
|
||||||
<QueryEditorRow>
|
<QueryEditorRow>
|
||||||
@@ -235,6 +229,7 @@ export const TriggersQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
options={groupsOptions}
|
options={groupsOptions}
|
||||||
isLoading={groupsLoading}
|
isLoading={groupsLoading}
|
||||||
onChange={onFilterChange('group')}
|
onChange={onFilterChange('group')}
|
||||||
|
placeholder="Group name"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
<InlineField label="Host" labelWidth={12}>
|
<InlineField label="Host" labelWidth={12}>
|
||||||
@@ -244,6 +239,7 @@ export const TriggersQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
options={hostOptions}
|
options={hostOptions}
|
||||||
isLoading={hostsLoading}
|
isLoading={hostsLoading}
|
||||||
onChange={onFilterChange('host')}
|
onChange={onFilterChange('host')}
|
||||||
|
placeholder="Host name"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
{query.countTriggersBy === 'problems' && (
|
{query.countTriggersBy === 'problems' && (
|
||||||
@@ -254,6 +250,7 @@ export const TriggersQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
options={proxiesOptions}
|
options={proxiesOptions}
|
||||||
isLoading={proxiesLoading}
|
isLoading={proxiesLoading}
|
||||||
onChange={onFilterChange('proxy')}
|
onChange={onFilterChange('proxy')}
|
||||||
|
placeholder="Proxy name"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
)}
|
)}
|
||||||
@@ -267,6 +264,7 @@ export const TriggersQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
options={appOptions}
|
options={appOptions}
|
||||||
isLoading={appsLoading}
|
isLoading={appsLoading}
|
||||||
onChange={onFilterChange('application')}
|
onChange={onFilterChange('application')}
|
||||||
|
placeholder="Application name"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
)}
|
)}
|
||||||
@@ -278,6 +276,7 @@ export const TriggersQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
options={tagOptions}
|
options={tagOptions}
|
||||||
isLoading={tagsLoading}
|
isLoading={tagsLoading}
|
||||||
onChange={onFilterChange('itemTag')}
|
onChange={onFilterChange('itemTag')}
|
||||||
|
placeholder="Item tag name"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
)}
|
)}
|
||||||
@@ -301,6 +300,7 @@ export const TriggersQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
options={itemOptions}
|
options={itemOptions}
|
||||||
isLoading={itemsLoading}
|
isLoading={itemsLoading}
|
||||||
onChange={onFilterChange('item')}
|
onChange={onFilterChange('item')}
|
||||||
|
placeholder="Item name"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
)}
|
)}
|
||||||
@@ -317,8 +317,7 @@ export const TriggersQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
</QueryEditorRow>
|
</QueryEditorRow>
|
||||||
<QueryEditorRow>
|
<QueryEditorRow>
|
||||||
<InlineField label="Min severity" labelWidth={12}>
|
<InlineField label="Min severity" labelWidth={12}>
|
||||||
<Select
|
<Combobox
|
||||||
isSearchable={false}
|
|
||||||
width={24}
|
width={24}
|
||||||
value={query.options?.minSeverity}
|
value={query.options?.minSeverity}
|
||||||
options={severityOptions}
|
options={severityOptions}
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ import _ from 'lodash';
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { useAsyncFn } from 'react-use';
|
import { useAsyncFn } from 'react-use';
|
||||||
|
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { InlineField, ComboboxOption } from '@grafana/ui';
|
||||||
import { InlineField } from '@grafana/ui';
|
|
||||||
import { QueryEditorRow } from './QueryEditorRow';
|
import { QueryEditorRow } from './QueryEditorRow';
|
||||||
import { MetricPicker } from '../../../components';
|
import { MetricPicker } from '../../../components';
|
||||||
import { getVariableOptions } from './utils';
|
import { getVariableOptions } from './utils';
|
||||||
@@ -36,7 +35,7 @@ export const UserMacrosQueryEditor = ({ query, datasource, onChange }: Props) =>
|
|||||||
|
|
||||||
const loadHostOptions = async (group: string) => {
|
const loadHostOptions = async (group: string) => {
|
||||||
const hosts = await datasource.zabbix.getAllHosts(group);
|
const hosts = await datasource.zabbix.getAllHosts(group);
|
||||||
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
let options: Array<ComboboxOption<string>> = hosts?.map((host) => ({
|
||||||
value: host.name,
|
value: host.name,
|
||||||
label: host.name,
|
label: host.name,
|
||||||
}));
|
}));
|
||||||
@@ -53,7 +52,7 @@ export const UserMacrosQueryEditor = ({ query, datasource, onChange }: Props) =>
|
|||||||
|
|
||||||
const loadMacrosOptions = async (group: string, host: string) => {
|
const loadMacrosOptions = async (group: string, host: string) => {
|
||||||
const macros = await datasource.zabbix.getAllMacros(group, host);
|
const macros = await datasource.zabbix.getAllMacros(group, host);
|
||||||
let options: Array<SelectableValue<string>> = macros?.map((m) => ({
|
let options: Array<ComboboxOption<string>> = macros?.map((m) => ({
|
||||||
value: m.macro,
|
value: m.macro,
|
||||||
label: m.macro,
|
label: m.macro,
|
||||||
}));
|
}));
|
||||||
@@ -102,6 +101,7 @@ export const UserMacrosQueryEditor = ({ query, datasource, onChange }: Props) =>
|
|||||||
options={groupsOptions}
|
options={groupsOptions}
|
||||||
isLoading={groupsLoading}
|
isLoading={groupsLoading}
|
||||||
onChange={onFilterChange('group')}
|
onChange={onFilterChange('group')}
|
||||||
|
placeholder="Group name"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
<InlineField label="Host" labelWidth={12}>
|
<InlineField label="Host" labelWidth={12}>
|
||||||
@@ -111,6 +111,7 @@ export const UserMacrosQueryEditor = ({ query, datasource, onChange }: Props) =>
|
|||||||
options={hostOptions}
|
options={hostOptions}
|
||||||
isLoading={hostsLoading}
|
isLoading={hostsLoading}
|
||||||
onChange={onFilterChange('host')}
|
onChange={onFilterChange('host')}
|
||||||
|
placeholder="Host name"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
</QueryEditorRow>
|
</QueryEditorRow>
|
||||||
@@ -122,6 +123,7 @@ export const UserMacrosQueryEditor = ({ query, datasource, onChange }: Props) =>
|
|||||||
options={macrosOptions}
|
options={macrosOptions}
|
||||||
isLoading={macrosLoading}
|
isLoading={macrosLoading}
|
||||||
onChange={onFilterChange('macro')}
|
onChange={onFilterChange('macro')}
|
||||||
|
placeholder="Macro name"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
</QueryEditorRow>
|
</QueryEditorRow>
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
import React, { PureComponent, ReactNode } from 'react';
|
import React, { PureComponent, ReactNode } from 'react';
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { GrafanaTheme, SelectableValue } from '@grafana/data';
|
import { GrafanaTheme } from '@grafana/data';
|
||||||
import { Button, Spinner, Modal, Select, stylesFactory, withTheme, Themeable, ButtonGroup } from '@grafana/ui';
|
import {
|
||||||
|
Button,
|
||||||
|
Spinner,
|
||||||
|
Modal,
|
||||||
|
stylesFactory,
|
||||||
|
withTheme,
|
||||||
|
Themeable,
|
||||||
|
ButtonGroup,
|
||||||
|
Combobox,
|
||||||
|
ComboboxOption,
|
||||||
|
} from '@grafana/ui';
|
||||||
import { ZBXScript, APIExecuteScriptResponse } from '../../datasource/zabbix/connectors/zabbix_api/types';
|
import { ZBXScript, APIExecuteScriptResponse } from '../../datasource/zabbix/connectors/zabbix_api/types';
|
||||||
import { FAIcon } from '../../components';
|
import { FAIcon } from '../../components';
|
||||||
|
|
||||||
@@ -12,8 +22,8 @@ interface Props extends Themeable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
selectedScript: SelectableValue<string>;
|
selectedScript: ComboboxOption<string>;
|
||||||
scriptOptions: Array<SelectableValue<string>>;
|
scriptOptions: Array<ComboboxOption<string>>;
|
||||||
script: ZBXScript;
|
script: ZBXScript;
|
||||||
error: boolean;
|
error: boolean;
|
||||||
errorMessage: string | ReactNode;
|
errorMessage: string | ReactNode;
|
||||||
@@ -47,7 +57,7 @@ export class ExecScriptModalUnthemed extends PureComponent<Props, State> {
|
|||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
const scripts = await this.props.getScripts();
|
const scripts = await this.props.getScripts();
|
||||||
this.scripts = scripts;
|
this.scripts = scripts;
|
||||||
const scriptOptions: Array<SelectableValue<string>> = scripts.map((s) => {
|
const scriptOptions: Array<ComboboxOption<string>> = scripts.map((s) => {
|
||||||
return {
|
return {
|
||||||
value: s.scriptid,
|
value: s.scriptid,
|
||||||
label: s.name,
|
label: s.name,
|
||||||
@@ -61,7 +71,7 @@ export class ExecScriptModalUnthemed extends PureComponent<Props, State> {
|
|||||||
this.setState({ scriptOptions, selectedScript, script });
|
this.setState({ scriptOptions, selectedScript, script });
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangeSelectedScript = (v: SelectableValue<string>) => {
|
onChangeSelectedScript = (v: ComboboxOption<string>) => {
|
||||||
const script = this.scripts.find((s) => v.value === s.scriptid);
|
const script = this.scripts.find((s) => v.value === s.scriptid);
|
||||||
this.setState({ selectedScript: v, script, errorMessage: '', loading: false, result: '' });
|
this.setState({ selectedScript: v, script, errorMessage: '', loading: false, result: '' });
|
||||||
};
|
};
|
||||||
@@ -133,7 +143,7 @@ export class ExecScriptModalUnthemed extends PureComponent<Props, State> {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Select options={scriptOptions} value={selectedScript} onChange={this.onChangeSelectedScript} />
|
<Combobox options={scriptOptions} value={selectedScript} onChange={this.onChangeSelectedScript} />
|
||||||
{selectError && <small className={styles.inputError}>{selectError}</small>}
|
{selectError && <small className={styles.inputError}>{selectError}</small>}
|
||||||
|
|
||||||
<div className={styles.scriptCommandContainer}>
|
<div className={styles.scriptCommandContainer}>
|
||||||
|
|||||||
Reference in New Issue
Block a user