diff --git a/.gitignore b/.gitignore index c8b2d15..96788fb 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ .vscode *.bat +.DS_Store # Grafana linter config # .jshintrc @@ -38,6 +39,7 @@ yarn-error.log # Built plugin dist/ ci/ +alexanderzobnin-zabbix-app.zip # Grafana toolkit configs # .prettierrc.js diff --git a/Makefile b/Makefile index 8504abe..347b9fb 100644 --- a/Makefile +++ b/Makefile @@ -82,3 +82,8 @@ sign-package: yarn sign package: install dist sign-package + +zip: + cp -r dist/ alexanderzobnin-zabbix-app + zip -r alexanderzobnin-zabbix-app.zip alexanderzobnin-zabbix-app + rm -rf alexanderzobnin-zabbix-app diff --git a/package.json b/package.json index 5f2f370..2207e0b 100644 --- a/package.json +++ b/package.json @@ -31,14 +31,14 @@ "devDependencies": { "@emotion/css": "11.1.3", "@emotion/react": "11.1.5", - "@grafana/data": "9.1.2", + "@grafana/data": "9.3.2", "@grafana/e2e": "9.2.5", "@grafana/e2e-selectors": "9.2.5", "@grafana/eslint-config": "^5.1.0", - "@grafana/runtime": "9.1.2", + "@grafana/runtime": "9.3.2", "@grafana/toolkit": "9.1.2", "@grafana/tsconfig": "^1.2.0-rc1", - "@grafana/ui": "9.1.2", + "@grafana/ui": "9.3.2", "@popperjs/core": "2.4.0", "@swc/core": "^1.2.144", "@swc/helpers": "^0.4.12", diff --git a/src/datasource/components/QueryEditor.tsx b/src/datasource/components/QueryEditor.tsx index 1842e4c..390844d 100644 --- a/src/datasource/components/QueryEditor.tsx +++ b/src/datasource/components/QueryEditor.tsx @@ -2,7 +2,7 @@ import React, { useEffect } from 'react'; import { QueryEditorProps, SelectableValue } from '@grafana/data'; import { InlineField, InlineFieldRow, Select } from '@grafana/ui'; import * as c from '../constants'; -import * as migrations from '../migrations'; +import { migrate, DS_QUERY_SCHEMA } from '../migrations'; import { ZabbixDatasource } from '../datasource'; import { ShowProblemTypes, ZabbixDSOptions, ZabbixMetricsQuery, ZabbixQueryOptions } from '../types'; import { MetricsQueryEditor } from './QueryEditor/MetricsQueryEditor'; @@ -13,6 +13,7 @@ import { ProblemsQueryEditor } from './QueryEditor/ProblemsQueryEditor'; import { ItemIdQueryEditor } from './QueryEditor/ItemIdQueryEditor'; import { ServicesQueryEditor } from './QueryEditor/ServicesQueryEditor'; import { TriggersQueryEditor } from './QueryEditor/TriggersQueryEditor'; +import { UserMacrosQueryEditor } from './QueryEditor/UserMacrosQueryEditor'; const zabbixQueryTypeOptions: Array> = [ { @@ -38,29 +39,32 @@ const zabbixQueryTypeOptions: Array> = [ { value: c.MODE_TRIGGERS, label: 'Triggers', - description: 'Query triggers data', + description: 'Count triggers', }, { value: c.MODE_PROBLEMS, label: 'Problems', description: 'Query problems', }, + { + value: c.MODE_MACROS, + label: 'User macros', + description: 'User Macros', + }, ]; const getDefaultQuery: () => Partial = () => ({ + schema: DS_QUERY_SCHEMA, queryType: c.MODE_METRICS, group: { filter: '' }, host: { filter: '' }, application: { filter: '' }, itemTag: { filter: '' }, item: { filter: '' }, + macro: { filter: '' }, functions: [], - triggers: { - count: true, - minSeverity: 3, - acknowledged: 2, - }, trigger: { filter: '' }, + countTriggersBy: '', tags: { filter: '' }, proxy: { filter: '' }, textFilter: '', @@ -70,6 +74,7 @@ const getDefaultQuery: () => Partial = () => ({ disableDataAlignment: false, useZabbixValueMapping: false, useTrends: 'default', + count: false, }, table: { skipEmptyValues: false, @@ -96,6 +101,7 @@ function getProblemsQueryDefaults(): Partial { hostProxy: false, limit: c.DEFAULT_ZABBIX_PROBLEMS_LIMIT, useTimeRange: false, + count: false, }, }; } @@ -119,7 +125,7 @@ export const QueryEditor = ({ query, datasource, onChange, onRunQuery }: ZabbixQ // Migrate query on load useEffect(() => { - const migratedQuery = migrations.migrate(query); + const migratedQuery = migrate(query); onChange(migratedQuery); }, []); @@ -184,6 +190,10 @@ export const QueryEditor = ({ query, datasource, onChange, onRunQuery }: ZabbixQ return ; }; + const renderUserMacrosEditor = () => { + return ; + }; + return ( <> @@ -206,6 +216,7 @@ export const QueryEditor = ({ query, datasource, onChange, onRunQuery }: ZabbixQ {queryType === c.MODE_ITSERVICE && renderITServicesEditor()} {queryType === c.MODE_PROBLEMS && renderProblemsEditor()} {queryType === c.MODE_TRIGGERS && renderTriggersEditor()} + {queryType === c.MODE_MACROS && renderUserMacrosEditor()} ); diff --git a/src/datasource/components/QueryEditor/QueryOptionsEditor.tsx b/src/datasource/components/QueryEditor/QueryOptionsEditor.tsx index 87b6d11..e8b9ceb 100644 --- a/src/datasource/components/QueryEditor/QueryOptionsEditor.tsx +++ b/src/datasource/components/QueryEditor/QueryOptionsEditor.tsx @@ -201,6 +201,12 @@ export const QueryOptionsEditor = ({ queryType, queryOptions, onChange }: Props) onChange={onPropChange('acknowledged')} /> + + onChange({ ...queryOptions, useTimeRange: !queryOptions.useTimeRange })} + /> + ); }; diff --git a/src/datasource/components/QueryEditor/TriggersQueryEditor.tsx b/src/datasource/components/QueryEditor/TriggersQueryEditor.tsx index 6ce72a9..d3048bd 100644 --- a/src/datasource/components/QueryEditor/TriggersQueryEditor.tsx +++ b/src/datasource/components/QueryEditor/TriggersQueryEditor.tsx @@ -1,14 +1,21 @@ import _ from 'lodash'; -import React, { useEffect } from 'react'; +import React, { useEffect, FormEvent } from 'react'; import { useAsyncFn } from 'react-use'; import { SelectableValue } from '@grafana/data'; -import { InlineField, InlineSwitch, Select } from '@grafana/ui'; +import { InlineField, InlineSwitch, Input, Select } from '@grafana/ui'; import { QueryEditorRow } from './QueryEditorRow'; import { MetricPicker } from '../../../components'; import { getVariableOptions } from './utils'; +import { itemTagToString } from '../../utils'; import { ZabbixDatasource } from '../../datasource'; -import { ZabbixMetricsQuery } from '../../types'; +import { ZabbixMetricsQuery, ZBXItem, ZBXItemTag } from '../../types'; + +const countByOptions: Array> = [ + { value: '', label: 'All triggers' }, + { value: 'problems', label: 'Problems' }, + { value: 'items', label: 'Items' }, +]; const severityOptions: Array> = [ { value: 0, label: 'Not classified' }, @@ -77,9 +84,80 @@ export const TriggersQueryEditor = ({ query, datasource, onChange }: Props) => { return options; }, [query.group.filter, query.host.filter]); + const loadTagOptions = async (group: string, host: string) => { + if (!datasource.zabbix.isZabbix54OrHigher()) { + return []; + } + + const groupFilter = datasource.replaceTemplateVars(group); + const hostFilter = datasource.replaceTemplateVars(host); + const items = await datasource.zabbix.getAllItems(groupFilter, hostFilter, null, null, {}); + const tags: ZBXItemTag[] = _.flatten(items.map((item: ZBXItem) => item.tags || [])); + + const tagList = _.uniqBy(tags, (t) => t.tag + t.value || '').map((t) => itemTagToString(t)); + let options: Array> = tagList?.map((tag) => ({ + value: tag, + label: tag, + })); + options = _.uniqBy(options, (o) => o.value); + options.unshift(...getVariableOptions()); + return options; + }; + + const [{ loading: tagsLoading, value: tagOptions }, fetchItemTags] = useAsyncFn(async () => { + const options = await loadTagOptions(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; + }, []); + + 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> = 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(); @@ -93,6 +171,27 @@ export const TriggersQueryEditor = ({ query, datasource, onChange }: Props) => { fetchApps(); }, [groupFilter, hostFilter]); + useEffect(() => { + fetchItemTags(); + }, [groupFilter, hostFilter]); + + useEffect(() => { + fetchProxies(); + }, []); + + useEffect(() => { + fetchItems(); + }, [groupFilter, hostFilter, appFilter, tagFilter]); + + const onTextFilterChange = (prop: string) => { + return (v: FormEvent) => { + const newValue = v?.currentTarget?.value; + if (newValue !== null) { + onChange({ ...query, [prop]: { filter: newValue } }); + } + }; + }; + const onFilterChange = (prop: string) => { return (value: string) => { if (value !== null) { @@ -107,8 +206,27 @@ export const TriggersQueryEditor = ({ query, datasource, onChange }: Props) => { } }; + const onCountByChange = (option: SelectableValue) => { + if (option.value !== null) { + onChange({ ...query, countTriggersBy: option.value! }); + } + }; + + const supportsApplications = datasource.zabbix.supportsApplications(); + return ( <> + + + + + + + + + )} + {query.countTriggersBy === 'items' && ( + + + + )} - - -