Build plugin with grafana toolkit (#1539)
* Use grafana toolkit template for building plugin * Fix linter and type errors * Update styles building * Fix sass deprecation warning * Remove empty js files produced by webpack building sass * Fix signing script * Replace classnames with cx * Fix data source config page * Use custom webpack config instead of overriding original one * Use gpx_ prefix for plugin executable * Remove unused configs * Roll back react hooks dependencies usage * Move plugin-specific ts config to root config file * Temporary do not use rst2html for function description tooltip * Remove unused code * remove unused dependencies * update react table dependency * Migrate tests to typescript * remove unused dependencies * Remove old webpack configs * Add sign target to makefile * Add magefile * Update CI test job * Update go packages * Update build instructions * Downgrade go version to 1.18 * Fix go version in ci * Fix metric picker * Add comment to webpack config * remove angular mocks * update bra config * Rename datasource-zabbix to datasource (fix mage build) * Add instructions for building backend with mage * Fix webpack targets * Fix ci backend tests * Add initial e2e tests * Fix e2e ci tests * Update docker compose for cypress tests * build grafana docker image * Fix docker stop task * CI: add Grafana compatibility check
This commit is contained in:
102
src/datasource/components/QueryEditor/ITServicesQueryEditor.tsx
Normal file
102
src/datasource/components/QueryEditor/ITServicesQueryEditor.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import _ from 'lodash';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useAsyncFn } from 'react-use';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { InlineField, Select } from '@grafana/ui';
|
||||
import { QueryEditorRow } from './QueryEditorRow';
|
||||
import { MetricPicker } from '../../../components';
|
||||
import { getVariableOptions } from './utils';
|
||||
import { ZabbixDatasource } from '../../datasource';
|
||||
import { ZabbixMetricsQuery } from '../../types';
|
||||
|
||||
const slaPropertyList: Array<SelectableValue<string>> = [
|
||||
{ label: 'Status', value: 'status' },
|
||||
{ label: 'SLA', value: 'sla' },
|
||||
{ label: 'OK time', value: 'okTime' },
|
||||
{ label: 'Problem time', value: 'problemTime' },
|
||||
{ label: 'Down time', value: 'downtimeTime' },
|
||||
];
|
||||
|
||||
const slaIntervals: Array<SelectableValue<string>> = [
|
||||
{ label: 'No interval', value: 'none' },
|
||||
{ label: 'Auto', value: 'auto' },
|
||||
{ label: '1 hour', value: '1h' },
|
||||
{ label: '12 hours', value: '12h' },
|
||||
{ label: '24 hours', value: '1d' },
|
||||
{ label: '1 week', value: '1w' },
|
||||
{ label: '1 month', value: '1M' },
|
||||
];
|
||||
|
||||
export interface Props {
|
||||
query: ZabbixMetricsQuery;
|
||||
datasource: ZabbixDatasource;
|
||||
onChange: (query: ZabbixMetricsQuery) => void;
|
||||
}
|
||||
|
||||
export const ITServicesQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
const loadITServiceOptions = async () => {
|
||||
const services = await datasource.zabbix.getITService();
|
||||
const options = services?.map((s) => ({
|
||||
value: s.name,
|
||||
label: s.name,
|
||||
}));
|
||||
options.unshift(...getVariableOptions());
|
||||
return options;
|
||||
};
|
||||
|
||||
const [{ loading: itServicesLoading, value: itServicesOptions }, fetchITServices] = useAsyncFn(async () => {
|
||||
const options = await loadITServiceOptions();
|
||||
return options;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchITServices();
|
||||
}, []);
|
||||
|
||||
const onPropChange = (prop: string) => {
|
||||
return (option: SelectableValue) => {
|
||||
if (option.value) {
|
||||
onChange({ ...query, [prop]: option.value });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const onITServiceChange = (value: string) => {
|
||||
if (value !== null) {
|
||||
onChange({ ...query, itServiceFilter: value });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<QueryEditorRow>
|
||||
<InlineField label="IT Service" labelWidth={12}>
|
||||
<MetricPicker
|
||||
width={24}
|
||||
value={query.itServiceFilter}
|
||||
options={itServicesOptions}
|
||||
isLoading={itServicesLoading}
|
||||
onChange={onITServiceChange}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Property" labelWidth={12}>
|
||||
<Select
|
||||
isSearchable={false}
|
||||
width={24}
|
||||
value={query.slaProperty}
|
||||
options={slaPropertyList}
|
||||
onChange={onPropChange('slaProperty')}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Interval" labelWidth={12}>
|
||||
<Select
|
||||
isSearchable={false}
|
||||
width={24}
|
||||
value={query.slaInterval}
|
||||
options={slaIntervals}
|
||||
onChange={onPropChange('slaInterval')}
|
||||
/>
|
||||
</InlineField>
|
||||
</QueryEditorRow>
|
||||
);
|
||||
};
|
||||
26
src/datasource/components/QueryEditor/ItemIdQueryEditor.tsx
Normal file
26
src/datasource/components/QueryEditor/ItemIdQueryEditor.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React, { FormEvent } from 'react';
|
||||
import { InlineField, Input } from '@grafana/ui';
|
||||
import { ZabbixMetricsQuery } from '../../types';
|
||||
import { QueryEditorRow } from './QueryEditorRow';
|
||||
|
||||
export interface Props {
|
||||
query: ZabbixMetricsQuery;
|
||||
onChange: (query: ZabbixMetricsQuery) => void;
|
||||
}
|
||||
|
||||
export const ItemIdQueryEditor = ({ query, onChange }: Props) => {
|
||||
const onItemIdsChange = (v: FormEvent<HTMLInputElement>) => {
|
||||
const newValue = v?.currentTarget?.value;
|
||||
if (newValue !== null) {
|
||||
onChange({ ...query, itemids: newValue });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<QueryEditorRow>
|
||||
<InlineField label="Item Ids" labelWidth={12}>
|
||||
<Input width={24} defaultValue={query.itemids} onBlur={onItemIdsChange} />
|
||||
</InlineField>
|
||||
</QueryEditorRow>
|
||||
);
|
||||
};
|
||||
174
src/datasource/components/QueryEditor/MetricsQueryEditor.tsx
Normal file
174
src/datasource/components/QueryEditor/MetricsQueryEditor.tsx
Normal file
@@ -0,0 +1,174 @@
|
||||
import _ from 'lodash';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useAsyncFn } from 'react-use';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { InlineField } from '@grafana/ui';
|
||||
import { QueryEditorRow } from './QueryEditorRow';
|
||||
import { MetricPicker } from '../../../components';
|
||||
import { getVariableOptions } from './utils';
|
||||
import { ZabbixDatasource } from '../../datasource';
|
||||
import { ZabbixMetricsQuery } from '../../types';
|
||||
|
||||
export interface Props {
|
||||
query: ZabbixMetricsQuery;
|
||||
datasource: ZabbixDatasource;
|
||||
onChange: (query: ZabbixMetricsQuery) => void;
|
||||
}
|
||||
|
||||
export const MetricsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
const loadGroupOptions = async () => {
|
||||
const groups = await datasource.zabbix.getAllGroups();
|
||||
const options = groups?.map((group) => ({
|
||||
value: group.name,
|
||||
label: group.name,
|
||||
}));
|
||||
options.unshift(...getVariableOptions());
|
||||
return options;
|
||||
};
|
||||
|
||||
const [{ loading: groupsLoading, value: groupsOptions }, fetchGroups] = useAsyncFn(async () => {
|
||||
const options = await loadGroupOptions();
|
||||
return options;
|
||||
}, []);
|
||||
|
||||
const loadHostOptions = async (group: string) => {
|
||||
const groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hosts = await datasource.zabbix.getAllHosts(groupFilter);
|
||||
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
||||
value: host.name,
|
||||
label: host.name,
|
||||
}));
|
||||
options = _.uniqBy(options, (o) => o.value);
|
||||
options.unshift({ value: '/.*/' });
|
||||
options.unshift(...getVariableOptions());
|
||||
return options;
|
||||
};
|
||||
|
||||
const [{ loading: hostsLoading, value: hostOptions }, fetchHosts] = useAsyncFn(async () => {
|
||||
const options = await loadHostOptions(query.group.filter);
|
||||
return options;
|
||||
}, [query.group.filter]);
|
||||
|
||||
const loadAppOptions = async (group: string, host: string) => {
|
||||
const groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hostFilter = datasource.replaceTemplateVars(host);
|
||||
const apps = await datasource.zabbix.getAllApps(groupFilter, hostFilter);
|
||||
let options: Array<SelectableValue<string>> = apps?.map((app) => ({
|
||||
value: app.name,
|
||||
label: app.name,
|
||||
}));
|
||||
options = _.uniqBy(options, (o) => o.value);
|
||||
options.unshift(...getVariableOptions());
|
||||
return options;
|
||||
};
|
||||
|
||||
const [{ loading: appsLoading, value: appOptions }, fetchApps] = useAsyncFn(async () => {
|
||||
const options = await loadAppOptions(query.group.filter, query.host.filter);
|
||||
return options;
|
||||
}, [query.group.filter, query.host.filter]);
|
||||
|
||||
const loadItemOptions = async (group: string, host: string, app: string, itemTag: string) => {
|
||||
const groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hostFilter = datasource.replaceTemplateVars(host);
|
||||
const appFilter = datasource.replaceTemplateVars(app);
|
||||
const tagFilter = datasource.replaceTemplateVars(itemTag);
|
||||
const options = {
|
||||
itemtype: 'num',
|
||||
showDisabledItems: query.options.showDisabledItems,
|
||||
};
|
||||
const items = await datasource.zabbix.getAllItems(groupFilter, hostFilter, appFilter, tagFilter, options);
|
||||
let itemOptions: Array<SelectableValue<string>> = items?.map((item) => ({
|
||||
value: item.name,
|
||||
label: item.name,
|
||||
}));
|
||||
itemOptions = _.uniqBy(itemOptions, (o) => o.value);
|
||||
itemOptions.unshift(...getVariableOptions());
|
||||
return itemOptions;
|
||||
};
|
||||
|
||||
const [{ loading: itemsLoading, value: itemOptions }, fetchItems] = useAsyncFn(async () => {
|
||||
const options = await loadItemOptions(
|
||||
query.group.filter,
|
||||
query.host.filter,
|
||||
query.application.filter,
|
||||
query.itemTag.filter
|
||||
);
|
||||
return options;
|
||||
}, [query.group.filter, query.host.filter, query.application.filter, query.itemTag.filter]);
|
||||
|
||||
// Update suggestions on every metric change
|
||||
const groupFilter = datasource.replaceTemplateVars(query.group?.filter);
|
||||
const hostFilter = datasource.replaceTemplateVars(query.host?.filter);
|
||||
const appFilter = datasource.replaceTemplateVars(query.application?.filter);
|
||||
const tagFilter = datasource.replaceTemplateVars(query.itemTag?.filter);
|
||||
|
||||
useEffect(() => {
|
||||
fetchGroups();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchHosts();
|
||||
}, [groupFilter]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchApps();
|
||||
}, [groupFilter, hostFilter]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchItems();
|
||||
}, [groupFilter, hostFilter, appFilter, tagFilter]);
|
||||
|
||||
const onFilterChange = (prop: string) => {
|
||||
return (value: string) => {
|
||||
if (value !== null) {
|
||||
onChange({ ...query, [prop]: { filter: value } });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<QueryEditorRow>
|
||||
<InlineField label="Group" labelWidth={12}>
|
||||
<MetricPicker
|
||||
width={24}
|
||||
value={query.group.filter}
|
||||
options={groupsOptions}
|
||||
isLoading={groupsLoading}
|
||||
onChange={onFilterChange('group')}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Host" labelWidth={12}>
|
||||
<MetricPicker
|
||||
width={24}
|
||||
value={query.host.filter}
|
||||
options={hostOptions}
|
||||
isLoading={hostsLoading}
|
||||
onChange={onFilterChange('host')}
|
||||
/>
|
||||
</InlineField>
|
||||
</QueryEditorRow>
|
||||
<QueryEditorRow>
|
||||
<InlineField label="Application" labelWidth={12}>
|
||||
<MetricPicker
|
||||
width={24}
|
||||
value={query.application.filter}
|
||||
options={appOptions}
|
||||
isLoading={appsLoading}
|
||||
onChange={onFilterChange('application')}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Item" labelWidth={12}>
|
||||
<MetricPicker
|
||||
width={24}
|
||||
value={query.item.filter}
|
||||
options={itemOptions}
|
||||
isLoading={itemsLoading}
|
||||
onChange={onFilterChange('item')}
|
||||
/>
|
||||
</InlineField>
|
||||
</QueryEditorRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
232
src/datasource/components/QueryEditor/ProblemsQueryEditor.tsx
Normal file
232
src/datasource/components/QueryEditor/ProblemsQueryEditor.tsx
Normal file
@@ -0,0 +1,232 @@
|
||||
import _ from 'lodash';
|
||||
import React, { useEffect, FormEvent } from 'react';
|
||||
import { useAsyncFn } from 'react-use';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { InlineField, Input, Select } from '@grafana/ui';
|
||||
import { QueryEditorRow } from './QueryEditorRow';
|
||||
import { MetricPicker } from '../../../components';
|
||||
import { getVariableOptions } from './utils';
|
||||
import { ZabbixDatasource } from '../../datasource';
|
||||
import { ZabbixMetricsQuery } from '../../types';
|
||||
|
||||
const showProblemsOptions: Array<SelectableValue<string>> = [
|
||||
{ label: 'Problems', value: 'problems' },
|
||||
{ label: 'Recent problems', value: 'recent' },
|
||||
{ label: 'History', value: 'history' },
|
||||
];
|
||||
|
||||
const severityOptions: Array<SelectableValue<number>> = [
|
||||
{ value: 0, label: 'Not classified' },
|
||||
{ value: 1, label: 'Information' },
|
||||
{ value: 2, label: 'Warning' },
|
||||
{ value: 3, label: 'Average' },
|
||||
{ value: 4, label: 'High' },
|
||||
{ value: 5, label: 'Disaster' },
|
||||
];
|
||||
|
||||
export interface Props {
|
||||
query: ZabbixMetricsQuery;
|
||||
datasource: ZabbixDatasource;
|
||||
onChange: (query: ZabbixMetricsQuery) => void;
|
||||
}
|
||||
|
||||
export const ProblemsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
const loadGroupOptions = async () => {
|
||||
const groups = await datasource.zabbix.getAllGroups();
|
||||
const options = groups?.map((group) => ({
|
||||
value: group.name,
|
||||
label: group.name,
|
||||
}));
|
||||
options.unshift(...getVariableOptions());
|
||||
return options;
|
||||
};
|
||||
|
||||
const [{ loading: groupsLoading, value: groupsOptions }, fetchGroups] = useAsyncFn(async () => {
|
||||
const options = await loadGroupOptions();
|
||||
return options;
|
||||
}, []);
|
||||
|
||||
const loadHostOptions = async (group: string) => {
|
||||
const groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hosts = await datasource.zabbix.getAllHosts(groupFilter);
|
||||
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
||||
value: host.name,
|
||||
label: host.name,
|
||||
}));
|
||||
options = _.uniqBy(options, (o) => o.value);
|
||||
options.unshift({ value: '/.*/' });
|
||||
options.unshift(...getVariableOptions());
|
||||
return options;
|
||||
};
|
||||
|
||||
const [{ loading: hostsLoading, value: hostOptions }, fetchHosts] = useAsyncFn(async () => {
|
||||
const options = await loadHostOptions(query.group.filter);
|
||||
return options;
|
||||
}, [query.group.filter]);
|
||||
|
||||
const loadAppOptions = async (group: string, host: string) => {
|
||||
const groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hostFilter = datasource.replaceTemplateVars(host);
|
||||
const apps = await datasource.zabbix.getAllApps(groupFilter, hostFilter);
|
||||
let options: Array<SelectableValue<string>> = apps?.map((app) => ({
|
||||
value: app.name,
|
||||
label: app.name,
|
||||
}));
|
||||
options = _.uniqBy(options, (o) => o.value);
|
||||
options.unshift(...getVariableOptions());
|
||||
return options;
|
||||
};
|
||||
|
||||
const [{ loading: appsLoading, value: appOptions }, fetchApps] = useAsyncFn(async () => {
|
||||
const options = await loadAppOptions(query.group.filter, query.host.filter);
|
||||
return options;
|
||||
}, [query.group.filter, query.host.filter]);
|
||||
|
||||
const loadProxyOptions = async () => {
|
||||
const proxies = await datasource.zabbix.getProxies();
|
||||
const options = proxies?.map((proxy) => ({
|
||||
value: proxy.host,
|
||||
label: proxy.host,
|
||||
}));
|
||||
options.unshift(...getVariableOptions());
|
||||
return options;
|
||||
};
|
||||
|
||||
const [{ loading: proxiesLoading, value: proxiesOptions }, fetchProxies] = useAsyncFn(async () => {
|
||||
const options = await loadProxyOptions();
|
||||
return options;
|
||||
}, []);
|
||||
|
||||
// Update suggestions on every metric change
|
||||
const groupFilter = datasource.replaceTemplateVars(query.group?.filter);
|
||||
const hostFilter = datasource.replaceTemplateVars(query.host?.filter);
|
||||
|
||||
useEffect(() => {
|
||||
fetchGroups();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchHosts();
|
||||
}, [groupFilter]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchApps();
|
||||
}, [groupFilter, hostFilter]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchProxies();
|
||||
}, []);
|
||||
|
||||
const onTextFilterChange = (prop: string) => {
|
||||
return (v: FormEvent<HTMLInputElement>) => {
|
||||
const newValue = v?.currentTarget?.value;
|
||||
if (newValue !== null) {
|
||||
onChange({ ...query, [prop]: { filter: newValue } });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const onFilterChange = (prop: string) => {
|
||||
return (value: string) => {
|
||||
if (value !== null) {
|
||||
onChange({ ...query, [prop]: { filter: value } });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const onPropChange = (prop: string) => {
|
||||
return (option: SelectableValue) => {
|
||||
if (option.value !== null) {
|
||||
onChange({ ...query, [prop]: option.value });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const onMinSeverityChange = (option: SelectableValue) => {
|
||||
if (option.value !== null) {
|
||||
onChange({ ...query, options: { ...query.options, minSeverity: option.value } });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<QueryEditorRow>
|
||||
<InlineField label="Group" labelWidth={12}>
|
||||
<MetricPicker
|
||||
width={24}
|
||||
value={query.group?.filter}
|
||||
options={groupsOptions}
|
||||
isLoading={groupsLoading}
|
||||
onChange={onFilterChange('group')}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Host" labelWidth={12}>
|
||||
<MetricPicker
|
||||
width={24}
|
||||
value={query.host?.filter}
|
||||
options={hostOptions}
|
||||
isLoading={hostsLoading}
|
||||
onChange={onFilterChange('host')}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Proxy" labelWidth={12}>
|
||||
<MetricPicker
|
||||
width={24}
|
||||
value={query.proxy?.filter}
|
||||
options={proxiesOptions}
|
||||
isLoading={proxiesLoading}
|
||||
onChange={onFilterChange('proxy')}
|
||||
/>
|
||||
</InlineField>
|
||||
</QueryEditorRow>
|
||||
<QueryEditorRow>
|
||||
<InlineField label="Application" labelWidth={12}>
|
||||
<MetricPicker
|
||||
width={24}
|
||||
value={query.application?.filter}
|
||||
options={appOptions}
|
||||
isLoading={appsLoading}
|
||||
onChange={onFilterChange('application')}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Problem" labelWidth={12}>
|
||||
<Input
|
||||
width={24}
|
||||
defaultValue={query.trigger?.filter}
|
||||
placeholder="Problem name"
|
||||
onBlur={onTextFilterChange('trigger')}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Tags" labelWidth={12}>
|
||||
<Input
|
||||
width={24}
|
||||
defaultValue={query.tags?.filter}
|
||||
placeholder="tag1:value1, tag2:value2"
|
||||
onBlur={onTextFilterChange('tags')}
|
||||
/>
|
||||
</InlineField>
|
||||
</QueryEditorRow>
|
||||
<QueryEditorRow>
|
||||
<InlineField label="Show" labelWidth={12}>
|
||||
<Select
|
||||
isSearchable={false}
|
||||
width={24}
|
||||
value={query.showProblems}
|
||||
options={showProblemsOptions}
|
||||
onChange={onPropChange('showProblems')}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Min severity" labelWidth={12}>
|
||||
<Select
|
||||
isSearchable={false}
|
||||
width={24}
|
||||
value={query.options?.minSeverity}
|
||||
options={severityOptions}
|
||||
onChange={onMinSeverityChange}
|
||||
/>
|
||||
</InlineField>
|
||||
</QueryEditorRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
13
src/datasource/components/QueryEditor/QueryEditorRow.tsx
Normal file
13
src/datasource/components/QueryEditor/QueryEditorRow.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
import { InlineFieldRow } from '@grafana/ui';
|
||||
|
||||
export const QueryEditorRow = ({ children }: React.PropsWithChildren<{}>) => {
|
||||
return (
|
||||
<InlineFieldRow>
|
||||
{children}
|
||||
<div className="gf-form gf-form--grow">
|
||||
<div className="gf-form-label gf-form-label--grow" />
|
||||
</div>
|
||||
</InlineFieldRow>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,82 @@
|
||||
import React from 'react';
|
||||
import { swap } from '../../utils';
|
||||
import { createFuncInstance } from '../../metricFunctions';
|
||||
import { FuncDef, MetricFunc, ZabbixMetricsQuery } from '../../types';
|
||||
import { QueryEditorRow } from './QueryEditorRow';
|
||||
import { InlineFormLabel } from '@grafana/ui';
|
||||
import { ZabbixFunctionEditor } from '../FunctionEditor/ZabbixFunctionEditor';
|
||||
import { AddZabbixFunction } from '../FunctionEditor/AddZabbixFunction';
|
||||
|
||||
export interface Props {
|
||||
query: ZabbixMetricsQuery;
|
||||
onChange: (query: ZabbixMetricsQuery) => void;
|
||||
}
|
||||
|
||||
export const QueryFunctionsEditor = ({ query, onChange }: Props) => {
|
||||
const onFuncParamChange = (func: MetricFunc, index: number, value: string) => {
|
||||
func.params[index] = value;
|
||||
const funcIndex = query.functions.findIndex((f) => f === func);
|
||||
const functions = query.functions;
|
||||
functions[funcIndex] = func;
|
||||
onChange({ ...query, functions });
|
||||
};
|
||||
|
||||
const onMoveFuncLeft = (func: MetricFunc) => {
|
||||
const index = query.functions.indexOf(func);
|
||||
const functions = swap(query.functions, index, index - 1);
|
||||
onChange({ ...query, functions });
|
||||
};
|
||||
|
||||
const onMoveFuncRight = (func: MetricFunc) => {
|
||||
const index = query.functions.indexOf(func);
|
||||
const functions = swap(query.functions, index, index + 1);
|
||||
onChange({ ...query, functions });
|
||||
};
|
||||
|
||||
const onRemoveFunc = (func: MetricFunc) => {
|
||||
const functions = query.functions?.filter((f) => f !== func);
|
||||
onChange({ ...query, functions });
|
||||
};
|
||||
|
||||
const onFuncAdd = (def: FuncDef) => {
|
||||
const newFunc = createFuncInstance(def);
|
||||
newFunc.added = true;
|
||||
let functions = query.functions.concat(newFunc);
|
||||
functions = moveAliasFuncLast(functions);
|
||||
|
||||
// if ((newFunc.params.length && newFunc.added) || newFunc.def.params.length === 0) {
|
||||
// }
|
||||
onChange({ ...query, functions });
|
||||
};
|
||||
|
||||
return (
|
||||
<QueryEditorRow>
|
||||
<InlineFormLabel width={6}>Functions</InlineFormLabel>
|
||||
{query.functions?.map((f, i) => {
|
||||
return (
|
||||
<ZabbixFunctionEditor
|
||||
func={f}
|
||||
key={i}
|
||||
onParamChange={onFuncParamChange}
|
||||
onMoveLeft={onMoveFuncLeft}
|
||||
onMoveRight={onMoveFuncRight}
|
||||
onRemove={onRemoveFunc}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<AddZabbixFunction onFuncAdd={onFuncAdd} />
|
||||
</QueryEditorRow>
|
||||
);
|
||||
};
|
||||
|
||||
function moveAliasFuncLast(functions: MetricFunc[]) {
|
||||
const aliasFuncIndex = functions.findIndex((func) => func.def.category === 'Alias');
|
||||
|
||||
console.log(aliasFuncIndex);
|
||||
if (aliasFuncIndex >= 0) {
|
||||
const aliasFunc = functions[aliasFuncIndex];
|
||||
functions.splice(aliasFuncIndex, 1);
|
||||
functions.push(aliasFunc);
|
||||
}
|
||||
return functions;
|
||||
}
|
||||
232
src/datasource/components/QueryEditor/QueryOptionsEditor.tsx
Normal file
232
src/datasource/components/QueryEditor/QueryOptionsEditor.tsx
Normal file
@@ -0,0 +1,232 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React, { useState, FormEvent } from 'react';
|
||||
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
||||
import {
|
||||
HorizontalGroup,
|
||||
Icon,
|
||||
InlineField,
|
||||
InlineFieldRow,
|
||||
InlineSwitch,
|
||||
Input,
|
||||
Select,
|
||||
useStyles2,
|
||||
} from '@grafana/ui';
|
||||
import * as c from '../../constants';
|
||||
import { ZabbixQueryOptions } from '../../types';
|
||||
|
||||
const ackOptions: Array<SelectableValue<number>> = [
|
||||
{ label: 'all triggers', value: 2 },
|
||||
{ label: 'unacknowledged', value: 0 },
|
||||
{ label: 'acknowledged', value: 1 },
|
||||
];
|
||||
|
||||
const sortOptions: Array<SelectableValue<string>> = [
|
||||
{ label: 'Default', value: 'default' },
|
||||
{ label: 'Last change', value: 'lastchange' },
|
||||
{ label: 'Severity', value: 'severity' },
|
||||
];
|
||||
|
||||
interface Props {
|
||||
queryType: string;
|
||||
queryOptions: ZabbixQueryOptions;
|
||||
onChange: (options: ZabbixQueryOptions) => void;
|
||||
}
|
||||
|
||||
export const QueryOptionsEditor = ({ queryType, queryOptions, onChange }: Props) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const onLimitChange = (v: FormEvent<HTMLInputElement>) => {
|
||||
const newValue = Number(v?.currentTarget?.value);
|
||||
if (newValue !== null) {
|
||||
onChange({ ...queryOptions, limit: newValue });
|
||||
}
|
||||
};
|
||||
|
||||
const onPropChange = (prop: string) => {
|
||||
return (option: SelectableValue) => {
|
||||
if (option.value !== null) {
|
||||
onChange({ ...queryOptions, [prop]: option.value });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const renderClosed = () => {
|
||||
return (
|
||||
<>
|
||||
<HorizontalGroup>
|
||||
{!isOpen && <Icon name="angle-right" />}
|
||||
{isOpen && <Icon name="angle-down" />}
|
||||
<span className={styles.label}>Options</span>
|
||||
<div className={styles.options}>{renderOptions()}</div>
|
||||
</HorizontalGroup>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const renderOptions = () => {
|
||||
const elements: JSX.Element[] = [];
|
||||
for (const key in queryOptions) {
|
||||
if (queryOptions.hasOwnProperty(key)) {
|
||||
const value = queryOptions[key];
|
||||
if (value === true && value !== '' && value !== null && value !== undefined) {
|
||||
elements.push(<span className={styles.optionContainer} key={key}>{`${key} = ${value}`}</span>);
|
||||
}
|
||||
}
|
||||
}
|
||||
return elements;
|
||||
};
|
||||
|
||||
const renderEditor = () => {
|
||||
return (
|
||||
<div className={styles.editorContainer}>
|
||||
{queryType === c.MODE_METRICS && renderMetricOptions()}
|
||||
{queryType === c.MODE_ITEMID && renderMetricOptions()}
|
||||
{queryType === c.MODE_ITSERVICE && renderMetricOptions()}
|
||||
{queryType === c.MODE_TEXT && renderTextMetricsOptions()}
|
||||
{queryType === c.MODE_PROBLEMS && renderProblemsOptions()}
|
||||
{queryType === c.MODE_TRIGGERS && renderTriggersOptions()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderMetricOptions = () => {
|
||||
return (
|
||||
<>
|
||||
<InlineField label="Show disabled items" labelWidth={24}>
|
||||
<InlineSwitch
|
||||
value={queryOptions.showDisabledItems}
|
||||
onChange={() => onChange({ ...queryOptions, showDisabledItems: !queryOptions.showDisabledItems })}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Use Zabbix value mapping" labelWidth={24}>
|
||||
<InlineSwitch
|
||||
value={queryOptions.useZabbixValueMapping}
|
||||
onChange={() => onChange({ ...queryOptions, useZabbixValueMapping: !queryOptions.useZabbixValueMapping })}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Disable data alignment" labelWidth={24}>
|
||||
<InlineSwitch
|
||||
value={queryOptions.disableDataAlignment}
|
||||
onChange={() => onChange({ ...queryOptions, disableDataAlignment: !queryOptions.disableDataAlignment })}
|
||||
/>
|
||||
</InlineField>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const renderTextMetricsOptions = () => {
|
||||
return (
|
||||
<>
|
||||
<InlineField label="Show disabled items" labelWidth={24}>
|
||||
<InlineSwitch
|
||||
value={queryOptions.showDisabledItems}
|
||||
onChange={() => onChange({ ...queryOptions, showDisabledItems: !queryOptions.showDisabledItems })}
|
||||
/>
|
||||
</InlineField>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const renderProblemsOptions = () => {
|
||||
return (
|
||||
<>
|
||||
<InlineField label="Acknowledged" labelWidth={24}>
|
||||
<Select
|
||||
isSearchable={false}
|
||||
width={24}
|
||||
value={queryOptions.acknowledged}
|
||||
options={ackOptions}
|
||||
onChange={onPropChange('acknowledged')}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Sort by" labelWidth={24}>
|
||||
<Select
|
||||
isSearchable={false}
|
||||
width={24}
|
||||
value={queryOptions.sortProblems}
|
||||
options={sortOptions}
|
||||
onChange={onPropChange('sortProblems')}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Use time range" labelWidth={24}>
|
||||
<InlineSwitch
|
||||
value={queryOptions.useTimeRange}
|
||||
onChange={() => onChange({ ...queryOptions, useTimeRange: !queryOptions.useTimeRange })}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Hosts in maintenance" labelWidth={24}>
|
||||
<InlineSwitch
|
||||
value={queryOptions.hostsInMaintenance}
|
||||
onChange={() => onChange({ ...queryOptions, hostsInMaintenance: !queryOptions.hostsInMaintenance })}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Host proxy" labelWidth={24}>
|
||||
<InlineSwitch
|
||||
value={queryOptions.hostProxy}
|
||||
onChange={() => onChange({ ...queryOptions, hostProxy: !queryOptions.hostProxy })}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Limit" labelWidth={24}>
|
||||
<Input width={12} type="number" defaultValue={queryOptions.limit} onBlur={onLimitChange} />
|
||||
</InlineField>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const renderTriggersOptions = () => {
|
||||
return (
|
||||
<>
|
||||
<InlineField label="Acknowledged" labelWidth={24}>
|
||||
<Select
|
||||
isSearchable={false}
|
||||
width={24}
|
||||
value={queryOptions.acknowledged}
|
||||
options={ackOptions}
|
||||
onChange={onPropChange('acknowledged')}
|
||||
/>
|
||||
</InlineField>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<InlineFieldRow>
|
||||
<div className={styles.container} onClick={() => setIsOpen(!isOpen)}>
|
||||
{renderClosed()}
|
||||
</div>
|
||||
</InlineFieldRow>
|
||||
<InlineFieldRow>{isOpen && renderEditor()}</InlineFieldRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
container: css({
|
||||
backgroundColor: theme.colors.background.secondary,
|
||||
borderRadius: theme.shape.borderRadius(),
|
||||
marginRight: theme.spacing(0.5),
|
||||
marginBottom: theme.spacing(0.5),
|
||||
padding: `0 ${theme.spacing(1)}`,
|
||||
height: `${theme.v1.spacing.formInputHeight}px`,
|
||||
width: `100%`,
|
||||
}),
|
||||
label: css({
|
||||
color: theme.colors.info.text,
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
cursor: 'pointer',
|
||||
}),
|
||||
options: css({
|
||||
color: theme.colors.text.disabled,
|
||||
fontSize: theme.typography.bodySmall.fontSize,
|
||||
}),
|
||||
optionContainer: css`
|
||||
margin-right: ${theme.spacing(2)};
|
||||
`,
|
||||
editorContainer: css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: ${theme.spacing(4)};
|
||||
`,
|
||||
});
|
||||
192
src/datasource/components/QueryEditor/TextMetricsQueryEditor.tsx
Normal file
192
src/datasource/components/QueryEditor/TextMetricsQueryEditor.tsx
Normal file
@@ -0,0 +1,192 @@
|
||||
import _ from 'lodash';
|
||||
import React, { useEffect, FormEvent } from 'react';
|
||||
import { useAsyncFn } from 'react-use';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { InlineField, InlineSwitch, Input } from '@grafana/ui';
|
||||
import { QueryEditorRow } from './QueryEditorRow';
|
||||
import { MetricPicker } from '../../../components';
|
||||
import { getVariableOptions } from './utils';
|
||||
import { ZabbixDatasource } from '../../datasource';
|
||||
import { ZabbixMetricsQuery } from '../../types';
|
||||
|
||||
export interface Props {
|
||||
query: ZabbixMetricsQuery;
|
||||
datasource: ZabbixDatasource;
|
||||
onChange: (query: ZabbixMetricsQuery) => void;
|
||||
}
|
||||
|
||||
export const TextMetricsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
const loadGroupOptions = async () => {
|
||||
const groups = await datasource.zabbix.getAllGroups();
|
||||
const options = groups?.map((group) => ({
|
||||
value: group.name,
|
||||
label: group.name,
|
||||
}));
|
||||
options.unshift(...getVariableOptions());
|
||||
return options;
|
||||
};
|
||||
|
||||
const [{ loading: groupsLoading, value: groupsOptions }, fetchGroups] = useAsyncFn(async () => {
|
||||
const options = await loadGroupOptions();
|
||||
return options;
|
||||
}, []);
|
||||
|
||||
const loadHostOptions = async (group: string) => {
|
||||
const groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hosts = await datasource.zabbix.getAllHosts(groupFilter);
|
||||
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
||||
value: host.name,
|
||||
label: host.name,
|
||||
}));
|
||||
options = _.uniqBy(options, (o) => o.value);
|
||||
options.unshift({ value: '/.*/' });
|
||||
options.unshift(...getVariableOptions());
|
||||
return options;
|
||||
};
|
||||
|
||||
const [{ loading: hostsLoading, value: hostOptions }, fetchHosts] = useAsyncFn(async () => {
|
||||
const options = await loadHostOptions(query.group.filter);
|
||||
return options;
|
||||
}, [query.group.filter]);
|
||||
|
||||
const loadAppOptions = async (group: string, host: string) => {
|
||||
const groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hostFilter = datasource.replaceTemplateVars(host);
|
||||
const apps = await datasource.zabbix.getAllApps(groupFilter, hostFilter);
|
||||
let options: Array<SelectableValue<string>> = apps?.map((app) => ({
|
||||
value: app.name,
|
||||
label: app.name,
|
||||
}));
|
||||
options = _.uniqBy(options, (o) => o.value);
|
||||
options.unshift(...getVariableOptions());
|
||||
return options;
|
||||
};
|
||||
|
||||
const [{ loading: appsLoading, value: appOptions }, fetchApps] = useAsyncFn(async () => {
|
||||
const options = await loadAppOptions(query.group.filter, query.host.filter);
|
||||
return options;
|
||||
}, [query.group.filter, query.host.filter]);
|
||||
|
||||
const loadItemOptions = async (group: string, host: string, app: string, itemTag: string) => {
|
||||
const groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hostFilter = datasource.replaceTemplateVars(host);
|
||||
const appFilter = datasource.replaceTemplateVars(app);
|
||||
const tagFilter = datasource.replaceTemplateVars(itemTag);
|
||||
const options = {
|
||||
itemtype: 'text',
|
||||
showDisabledItems: query.options.showDisabledItems,
|
||||
};
|
||||
const items = await datasource.zabbix.getAllItems(groupFilter, hostFilter, appFilter, tagFilter, options);
|
||||
let itemOptions: Array<SelectableValue<string>> = items?.map((item) => ({
|
||||
value: item.name,
|
||||
label: item.name,
|
||||
}));
|
||||
itemOptions = _.uniqBy(itemOptions, (o) => o.value);
|
||||
itemOptions.unshift(...getVariableOptions());
|
||||
return itemOptions;
|
||||
};
|
||||
|
||||
const [{ loading: itemsLoading, value: itemOptions }, fetchItems] = useAsyncFn(async () => {
|
||||
const options = await loadItemOptions(
|
||||
query.group.filter,
|
||||
query.host.filter,
|
||||
query.application.filter,
|
||||
query.itemTag.filter
|
||||
);
|
||||
return options;
|
||||
}, [query.group.filter, query.host.filter, query.application.filter, query.itemTag.filter]);
|
||||
|
||||
// Update suggestions on every metric change
|
||||
const groupFilter = datasource.replaceTemplateVars(query.group?.filter);
|
||||
const hostFilter = datasource.replaceTemplateVars(query.host?.filter);
|
||||
const appFilter = datasource.replaceTemplateVars(query.application?.filter);
|
||||
const tagFilter = datasource.replaceTemplateVars(query.itemTag?.filter);
|
||||
|
||||
useEffect(() => {
|
||||
fetchGroups();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchHosts();
|
||||
}, [groupFilter]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchApps();
|
||||
}, [groupFilter, hostFilter]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchItems();
|
||||
}, [groupFilter, hostFilter, appFilter, tagFilter]);
|
||||
|
||||
const onTextFilterChange = (v: FormEvent<HTMLInputElement>) => {
|
||||
const newValue = v?.currentTarget?.value;
|
||||
if (newValue !== null) {
|
||||
onChange({ ...query, textFilter: newValue });
|
||||
}
|
||||
};
|
||||
|
||||
const onFilterChange = (prop: string) => {
|
||||
return (value: string) => {
|
||||
if (value !== null) {
|
||||
onChange({ ...query, [prop]: { filter: value } });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<QueryEditorRow>
|
||||
<InlineField label="Group" labelWidth={12}>
|
||||
<MetricPicker
|
||||
width={24}
|
||||
value={query.group.filter}
|
||||
options={groupsOptions}
|
||||
isLoading={groupsLoading}
|
||||
onChange={onFilterChange('group')}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Host" labelWidth={12}>
|
||||
<MetricPicker
|
||||
width={24}
|
||||
value={query.host.filter}
|
||||
options={hostOptions}
|
||||
isLoading={hostsLoading}
|
||||
onChange={onFilterChange('host')}
|
||||
/>
|
||||
</InlineField>
|
||||
</QueryEditorRow>
|
||||
<QueryEditorRow>
|
||||
<InlineField label="Application" labelWidth={12}>
|
||||
<MetricPicker
|
||||
width={24}
|
||||
value={query.application.filter}
|
||||
options={appOptions}
|
||||
isLoading={appsLoading}
|
||||
onChange={onFilterChange('application')}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Item" labelWidth={12}>
|
||||
<MetricPicker
|
||||
width={24}
|
||||
value={query.item.filter}
|
||||
options={itemOptions}
|
||||
isLoading={itemsLoading}
|
||||
onChange={onFilterChange('item')}
|
||||
/>
|
||||
</InlineField>
|
||||
</QueryEditorRow>
|
||||
<QueryEditorRow>
|
||||
<InlineField label="Text filter" labelWidth={12}>
|
||||
<Input width={24} defaultValue={query.textFilter} onBlur={onTextFilterChange} />
|
||||
</InlineField>
|
||||
<InlineField label="Use capture groups" labelWidth={16}>
|
||||
<InlineSwitch
|
||||
value={query.useCaptureGroups}
|
||||
onChange={() => onChange({ ...query, useCaptureGroups: !query.useCaptureGroups })}
|
||||
/>
|
||||
</InlineField>
|
||||
</QueryEditorRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
160
src/datasource/components/QueryEditor/TriggersQueryEditor.tsx
Normal file
160
src/datasource/components/QueryEditor/TriggersQueryEditor.tsx
Normal file
@@ -0,0 +1,160 @@
|
||||
import _ from 'lodash';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useAsyncFn } from 'react-use';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { InlineField, InlineSwitch, Select } from '@grafana/ui';
|
||||
import { QueryEditorRow } from './QueryEditorRow';
|
||||
import { MetricPicker } from '../../../components';
|
||||
import { getVariableOptions } from './utils';
|
||||
import { ZabbixDatasource } from '../../datasource';
|
||||
import { ZabbixMetricsQuery } from '../../types';
|
||||
|
||||
const severityOptions: Array<SelectableValue<number>> = [
|
||||
{ value: 0, label: 'Not classified' },
|
||||
{ value: 1, label: 'Information' },
|
||||
{ value: 2, label: 'Warning' },
|
||||
{ value: 3, label: 'Average' },
|
||||
{ value: 4, label: 'High' },
|
||||
{ value: 5, label: 'Disaster' },
|
||||
];
|
||||
|
||||
export interface Props {
|
||||
query: ZabbixMetricsQuery;
|
||||
datasource: ZabbixDatasource;
|
||||
onChange: (query: ZabbixMetricsQuery) => void;
|
||||
}
|
||||
|
||||
export const TriggersQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
const loadGroupOptions = async () => {
|
||||
const groups = await datasource.zabbix.getAllGroups();
|
||||
const options = groups?.map((group) => ({
|
||||
value: group.name,
|
||||
label: group.name,
|
||||
}));
|
||||
options.unshift(...getVariableOptions());
|
||||
return options;
|
||||
};
|
||||
|
||||
const [{ loading: groupsLoading, value: groupsOptions }, fetchGroups] = useAsyncFn(async () => {
|
||||
const options = await loadGroupOptions();
|
||||
return options;
|
||||
}, []);
|
||||
|
||||
const loadHostOptions = async (group: string) => {
|
||||
const groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hosts = await datasource.zabbix.getAllHosts(groupFilter);
|
||||
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
||||
value: host.name,
|
||||
label: host.name,
|
||||
}));
|
||||
options = _.uniqBy(options, (o) => o.value);
|
||||
options.unshift({ value: '/.*/' });
|
||||
options.unshift(...getVariableOptions());
|
||||
return options;
|
||||
};
|
||||
|
||||
const [{ loading: hostsLoading, value: hostOptions }, fetchHosts] = useAsyncFn(async () => {
|
||||
const options = await loadHostOptions(query.group.filter);
|
||||
return options;
|
||||
}, [query.group.filter]);
|
||||
|
||||
const loadAppOptions = async (group: string, host: string) => {
|
||||
const groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hostFilter = datasource.replaceTemplateVars(host);
|
||||
const apps = await datasource.zabbix.getAllApps(groupFilter, hostFilter);
|
||||
let options: Array<SelectableValue<string>> = apps?.map((app) => ({
|
||||
value: app.name,
|
||||
label: app.name,
|
||||
}));
|
||||
options = _.uniqBy(options, (o) => o.value);
|
||||
options.unshift(...getVariableOptions());
|
||||
return options;
|
||||
};
|
||||
|
||||
const [{ loading: appsLoading, value: appOptions }, fetchApps] = useAsyncFn(async () => {
|
||||
const options = await loadAppOptions(query.group.filter, query.host.filter);
|
||||
return options;
|
||||
}, [query.group.filter, query.host.filter]);
|
||||
|
||||
// Update suggestions on every metric change
|
||||
const groupFilter = datasource.replaceTemplateVars(query.group?.filter);
|
||||
const hostFilter = datasource.replaceTemplateVars(query.host?.filter);
|
||||
|
||||
useEffect(() => {
|
||||
fetchGroups();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchHosts();
|
||||
}, [groupFilter]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchApps();
|
||||
}, [groupFilter, hostFilter]);
|
||||
|
||||
const onFilterChange = (prop: string) => {
|
||||
return (value: string) => {
|
||||
if (value !== null) {
|
||||
onChange({ ...query, [prop]: { filter: value } });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const onMinSeverityChange = (option: SelectableValue) => {
|
||||
if (option.value !== null) {
|
||||
onChange({ ...query, options: { ...query.options, minSeverity: option.value } });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<QueryEditorRow>
|
||||
<InlineField label="Group" labelWidth={12}>
|
||||
<MetricPicker
|
||||
width={24}
|
||||
value={query.group?.filter}
|
||||
options={groupsOptions}
|
||||
isLoading={groupsLoading}
|
||||
onChange={onFilterChange('group')}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Host" labelWidth={12}>
|
||||
<MetricPicker
|
||||
width={24}
|
||||
value={query.host?.filter}
|
||||
options={hostOptions}
|
||||
isLoading={hostsLoading}
|
||||
onChange={onFilterChange('host')}
|
||||
/>
|
||||
</InlineField>
|
||||
</QueryEditorRow>
|
||||
<QueryEditorRow>
|
||||
<InlineField label="Application" labelWidth={12}>
|
||||
<MetricPicker
|
||||
width={24}
|
||||
value={query.application?.filter}
|
||||
options={appOptions}
|
||||
isLoading={appsLoading}
|
||||
onChange={onFilterChange('application')}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Min severity" labelWidth={12}>
|
||||
<Select
|
||||
isSearchable={false}
|
||||
width={24}
|
||||
value={query.triggers?.minSeverity}
|
||||
options={severityOptions}
|
||||
onChange={onMinSeverityChange}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Count" labelWidth={12}>
|
||||
<InlineSwitch
|
||||
value={query.triggers?.count}
|
||||
onChange={() => onChange({ ...query, triggers: { ...query.triggers, count: !query.triggers?.count } })}
|
||||
/>
|
||||
</InlineField>
|
||||
</QueryEditorRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
13
src/datasource/components/QueryEditor/utils.ts
Normal file
13
src/datasource/components/QueryEditor/utils.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { getTemplateSrv } from '@grafana/runtime';
|
||||
|
||||
export const getVariableOptions = () => {
|
||||
const variables = getTemplateSrv()
|
||||
.getVariables()
|
||||
.filter((v) => {
|
||||
return v.type !== 'datasource' && v.type !== 'interval';
|
||||
});
|
||||
return variables?.map((v) => ({
|
||||
value: `$${v.name}`,
|
||||
label: `$${v.name}`,
|
||||
}));
|
||||
};
|
||||
Reference in New Issue
Block a user