Compare commits
67 Commits
7bb1b38c06
...
3d70908285
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d70908285 | ||
|
|
57bfef6a23 | ||
|
|
2711048e24 | ||
|
|
9b38ec17d5 | ||
|
|
7ee9487be1 | ||
|
|
744124ac6b | ||
|
|
2242654303 | ||
|
|
f91ee858df | ||
|
|
c6a97c65e6 | ||
|
|
4c29637fde | ||
|
|
f8ac3d7025 | ||
|
|
7e279289ec | ||
|
|
b86330a88d | ||
|
|
0806f3625c | ||
|
|
4ebefec030 | ||
|
|
3edec69265 | ||
|
|
88ec24c94c | ||
|
|
984f065296 | ||
|
|
0e5577e088 | ||
|
|
db587e709b | ||
|
|
ea955c5ffa | ||
|
|
97e158c582 | ||
|
|
fdef858e9c | ||
|
|
fb1bb19834 | ||
|
|
d76ffd9cbb | ||
|
|
05ab443cef | ||
|
|
7a2e6185ff | ||
|
|
dc1af6dbfb | ||
|
|
4d97988677 | ||
|
|
d9f20da91d | ||
|
|
6607957d80 | ||
|
|
96061f4c4e | ||
|
|
55c11e8e3d | ||
|
|
4ba5394fe5 | ||
|
|
e388bd7d08 | ||
|
|
ff4ddc6ead | ||
|
|
e88b15b32c | ||
|
|
ef773399c6 | ||
|
|
c96155c4a1 | ||
|
|
98af1074cd | ||
|
|
17883d72b4 | ||
|
|
64c73445d2 | ||
|
|
f511ec060e | ||
|
|
092e1c6014 | ||
|
|
a05b93314d | ||
|
|
0a5129cf2c | ||
|
|
b56f9e7ebb | ||
|
|
97aeabed61 | ||
|
|
eecde20d39 | ||
|
|
56a06171ec | ||
|
|
92e90c027e | ||
|
|
0a1121ed73 | ||
|
|
a26d77f6e3 | ||
|
|
f92bea2580 | ||
|
|
71127842ea | ||
|
|
ffa62a1631 | ||
|
|
b926aa85bc | ||
|
|
fb80f4cc4c | ||
|
|
baed463010 | ||
|
|
3ada0d15e6 | ||
|
|
95554b0b8c | ||
|
|
ec8fe8c1e0 | ||
|
|
c4b5f3dd53 | ||
|
|
5ffb7cdb6c | ||
|
|
5cac1b9b18 | ||
|
|
3a4f3280be | ||
|
|
592380851c |
@@ -3,7 +3,7 @@
|
||||
# see https://github.com/unknwon/bra/blob/master/templates/default.bra.toml for more configuration options.
|
||||
[run]
|
||||
init_cmds = [
|
||||
["mage", "-v", "build:backend"],
|
||||
["mage", "-v", "build:debug"],
|
||||
["mage", "-v" , "reloadPlugin"]
|
||||
]
|
||||
watch_all = true
|
||||
@@ -17,6 +17,6 @@ watch_dirs = [
|
||||
watch_exts = [".go", ".json"]
|
||||
build_delay = 2000
|
||||
cmds = [
|
||||
["mage", "-v", "build:backend"],
|
||||
["mage", "-v", "build:debug"],
|
||||
["mage", "-v" , "reloadPlugin"]
|
||||
]
|
||||
@@ -7,7 +7,8 @@ ARG anonymous_auth_enabled=true
|
||||
ARG development=false
|
||||
ARG TARGETARCH
|
||||
|
||||
|
||||
ARG GO_VERSION=1.25.6
|
||||
ARG GO_ARCH=${TARGETARCH:-amd64}
|
||||
ENV DEV "${development}"
|
||||
|
||||
# Make it as simple as possible to access the grafana instance for development purposes
|
||||
@@ -43,7 +44,27 @@ RUN if [ "${development}" = "true" ]; then \
|
||||
COPY supervisord/supervisord.conf /etc/supervisor.d/supervisord.ini
|
||||
COPY supervisord/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||
|
||||
# Installing Go
|
||||
RUN if [ "${development}" = "true" ]; then \
|
||||
curl -O -L https://golang.org/dl/go${GO_VERSION}.linux-${GO_ARCH}.tar.gz && \
|
||||
rm -rf /usr/local/go && \
|
||||
tar -C /usr/local -xzf go${GO_VERSION}.linux-${GO_ARCH}.tar.gz && \
|
||||
echo "export PATH=$PATH:/usr/local/go/bin:~/go/bin" >> ~/.bashrc && \
|
||||
rm -f go${GO_VERSION}.linux-${GO_ARCH}.tar.gz; \
|
||||
fi
|
||||
|
||||
# Installing delve for debugging
|
||||
RUN if [ "${development}" = "true" ]; then \
|
||||
/usr/local/go/bin/go install github.com/go-delve/delve/cmd/dlv@latest; \
|
||||
fi
|
||||
|
||||
# Installing mage for plugin (re)building
|
||||
RUN if [ "${development}" = "true" ]; then \
|
||||
git clone https://github.com/magefile/mage; \
|
||||
cd mage; \
|
||||
export PATH=$PATH:/usr/local/go/bin; \
|
||||
go run bootstrap.go; \
|
||||
fi
|
||||
|
||||
# Inject livereload script into grafana index.html
|
||||
RUN sed -i 's|</body>|<script src="http://localhost:35729/livereload.js"></script></body>|g' /usr/share/grafana/public/views/index.html
|
||||
|
||||
@@ -7,11 +7,17 @@ services:
|
||||
context: .
|
||||
args:
|
||||
grafana_image: ${GRAFANA_IMAGE:-grafana-enterprise}
|
||||
grafana_version: ${GRAFANA_VERSION:-12.2.0}
|
||||
grafana_version: ${GRAFANA_VERSION:-12.3.1}
|
||||
development: ${DEVELOPMENT:-false}
|
||||
anonymous_auth_enabled: ${ANONYMOUS_AUTH_ENABLED:-true}
|
||||
ports:
|
||||
- 3000:3000/tcp
|
||||
- 2345:2345/tcp # delve
|
||||
security_opt:
|
||||
- 'apparmor:unconfined'
|
||||
- 'seccomp:unconfined'
|
||||
cap_add:
|
||||
- SYS_PTRACE
|
||||
volumes:
|
||||
- ../dist:/var/lib/grafana/plugins/alexanderzobnin-zabbix-app
|
||||
- ../provisioning:/etc/grafana/provisioning
|
||||
|
||||
@@ -5,7 +5,7 @@ user=root
|
||||
[program:grafana]
|
||||
user=root
|
||||
directory=/var/lib/grafana
|
||||
command=/run.sh
|
||||
command=bash -c 'while [ ! -f /root/alexanderzobnin-zabbix-app/dist/datasource/gpx_zabbix-datasource* ]; do sleep 1; done; /run.sh'
|
||||
stdout_logfile=/dev/fd/1
|
||||
stdout_logfile_maxbytes=0
|
||||
redirect_stderr=true
|
||||
@@ -13,3 +13,35 @@ killasgroup=true
|
||||
stopasgroup=true
|
||||
autostart=true
|
||||
|
||||
[program:delve]
|
||||
user=root
|
||||
command=/bin/bash -c 'pid=""; while [ -z "$pid" ]; do pid=$(pgrep -f gpx_zabbix-datasource); done; /root/go/bin/dlv attach --api-version=2 --headless --continue --accept-multiclient --listen=:2345 $pid'
|
||||
stdout_logfile=/dev/fd/1
|
||||
stdout_logfile_maxbytes=0
|
||||
redirect_stderr=true
|
||||
killasgroup=false
|
||||
stopasgroup=false
|
||||
autostart=true
|
||||
autorestart=true
|
||||
|
||||
[program:build-watcher]
|
||||
user=root
|
||||
command=/bin/bash -c 'while inotifywait -e modify,create,delete -r /var/lib/grafana/plugins/alexanderzobnin-zabbix-app; do echo "Change detected, restarting delve...";supervisorctl restart delve; done'
|
||||
stdout_logfile=/dev/fd/1
|
||||
stdout_logfile_maxbytes=0
|
||||
redirect_stderr=true
|
||||
killasgroup=true
|
||||
stopasgroup=true
|
||||
autostart=true
|
||||
|
||||
[program:mage-watcher]
|
||||
user=root
|
||||
environment=PATH="/usr/local/go/bin:/root/go/bin:%(ENV_PATH)s"
|
||||
directory=/root/alexanderzobnin-zabbix-app
|
||||
command=/bin/bash -c 'git config --global --add safe.directory /root/alexanderzobnin-zabbix-app && mage -v watch'
|
||||
stdout_logfile=/dev/fd/1
|
||||
stdout_logfile_maxbytes=0
|
||||
redirect_stderr=true
|
||||
killasgroup=true
|
||||
stopasgroup=true
|
||||
autostart=true
|
||||
|
||||
4
.github/workflows/compatibility-50.yml
vendored
4
.github/workflows/compatibility-50.yml
vendored
@@ -14,11 +14,11 @@ jobs:
|
||||
compatibility-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
|
||||
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||
with:
|
||||
go-version: '1.25'
|
||||
|
||||
|
||||
4
.github/workflows/compatibility-60.yml
vendored
4
.github/workflows/compatibility-60.yml
vendored
@@ -14,11 +14,11 @@ jobs:
|
||||
compatibility-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
|
||||
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||
with:
|
||||
go-version: '1.25'
|
||||
|
||||
|
||||
4
.github/workflows/compatibility-70.yml
vendored
4
.github/workflows/compatibility-70.yml
vendored
@@ -14,11 +14,11 @@ jobs:
|
||||
compatibility-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
|
||||
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||
with:
|
||||
go-version: '1.25'
|
||||
|
||||
|
||||
4
.github/workflows/compatibility-72.yml
vendored
4
.github/workflows/compatibility-72.yml
vendored
@@ -14,11 +14,11 @@ jobs:
|
||||
compatibility-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
|
||||
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||
with:
|
||||
go-version: '1.25'
|
||||
|
||||
|
||||
4
.github/workflows/compatibility-74.yml
vendored
4
.github/workflows/compatibility-74.yml
vendored
@@ -14,11 +14,11 @@ jobs:
|
||||
compatibility-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
|
||||
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||
with:
|
||||
go-version: '1.25'
|
||||
|
||||
|
||||
4
.github/workflows/publish.yaml
vendored
4
.github/workflows/publish.yaml
vendored
@@ -3,6 +3,7 @@ run-name: Deploy ${{ inputs.branch }} to ${{ inputs.environment }} by @${{ githu
|
||||
permissions:
|
||||
attestations: write
|
||||
contents: write
|
||||
pull-requests: read
|
||||
id-token: write
|
||||
|
||||
on:
|
||||
@@ -27,7 +28,7 @@ on:
|
||||
jobs:
|
||||
cd:
|
||||
name: CD
|
||||
uses: grafana/plugin-ci-workflows/.github/workflows/cd.yml@ci-cd-workflows/v5.1.0
|
||||
uses: grafana/plugin-ci-workflows/.github/workflows/cd.yml@ci-cd-workflows/v6.1.0
|
||||
with:
|
||||
golangci-lint-version: '2.7.2'
|
||||
go-version: '1.25.5'
|
||||
@@ -36,3 +37,4 @@ jobs:
|
||||
docs-only: ${{ fromJSON(github.event.inputs.docs-only) }}
|
||||
run-playwright: true
|
||||
github-draft-release: false
|
||||
run-playwright-with-skip-grafana-react-19-preview-image: true
|
||||
|
||||
3
.github/workflows/push.yaml
vendored
3
.github/workflows/push.yaml
vendored
@@ -12,9 +12,10 @@ on:
|
||||
jobs:
|
||||
ci:
|
||||
name: CI
|
||||
uses: grafana/plugin-ci-workflows/.github/workflows/ci.yml@ci-cd-workflows/v5.1.0
|
||||
uses: grafana/plugin-ci-workflows/.github/workflows/ci.yml@ci-cd-workflows/v6.1.0
|
||||
with:
|
||||
go-version: '1.25.5'
|
||||
golangci-lint-version: '2.7.2'
|
||||
plugin-version-suffix: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || '' }}
|
||||
run-playwright: true
|
||||
run-playwright-with-skip-grafana-react-19-preview-image: true
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,7 +2,6 @@
|
||||
*.sublime-project
|
||||
|
||||
.idea/
|
||||
.vscode
|
||||
|
||||
*.bat
|
||||
.DS_Store
|
||||
|
||||
36
.vscode/launch.json
vendored
Normal file
36
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Standalone debug mode",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "debug",
|
||||
"program": "${workspaceFolder}/pkg",
|
||||
"env": {},
|
||||
"args": [
|
||||
"-standalone"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Attach to plugin backend in docker",
|
||||
"type": "go",
|
||||
"request": "attach",
|
||||
"mode": "remote",
|
||||
"port": 2345,
|
||||
"host": "127.0.0.1",
|
||||
"showLog": true,
|
||||
"trace": "log",
|
||||
"logOutput": "rpc",
|
||||
"substitutePath": [
|
||||
{
|
||||
"from": "${workspaceFolder}",
|
||||
"to": "/root/alexanderzobnin-zabbix-app"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
12
CHANGELOG.md
12
CHANGELOG.md
@@ -1,5 +1,17 @@
|
||||
# Change Log
|
||||
|
||||
|
||||
## 6.1.2
|
||||
|
||||
🐛 Fix silent removal of itemTagFilter when no tags match regex
|
||||
🐛 Enhance the problems panel with the ability to convert specific tags to columns. Single or multiple tags supported.
|
||||
🐛 Fix column visibility toggles for problems panel
|
||||
|
||||
## 6.1.1
|
||||
|
||||
🐛 Fix moment value rendering issue
|
||||
🐛 Fix proxies dropdown in ProblemsQueryEditor
|
||||
|
||||
## 6.1.0
|
||||
|
||||
🎉 Migrates use of DatasourceApi to DatasourceWithBackend
|
||||
|
||||
104
CLAUDE.md
Normal file
104
CLAUDE.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
Grafana-Zabbix is a hybrid Grafana plugin connecting Grafana to Zabbix monitoring infrastructure. It consists of three components:
|
||||
- **Zabbix App** (`/src`) - Container app plugin
|
||||
- **Zabbix Data Source** (`/src/datasource`) - TypeScript frontend + Go backend for querying Zabbix
|
||||
- **Problems Panel** (`/src/panel-triggers`) - React panel for displaying Zabbix problems/triggers
|
||||
|
||||
## Build Commands
|
||||
|
||||
```bash
|
||||
# Install all dependencies
|
||||
make install # or: yarn install && go install -v ./pkg/
|
||||
|
||||
# Development
|
||||
yarn dev # Watch mode for frontend
|
||||
make run-backend # Watch mode for backend (uses bra)
|
||||
|
||||
# Production build
|
||||
make build # or: yarn build && mage -v build:backend
|
||||
|
||||
# Distribution (all platforms)
|
||||
make dist
|
||||
|
||||
# After building, restart Grafana to reload the plugin
|
||||
podman container stop grafana && podman container start grafana
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
# Frontend tests (Jest)
|
||||
yarn test # Watch mode
|
||||
yarn test:ci # CI mode, all tests
|
||||
|
||||
# Run specific test file
|
||||
yarn test datasource.test.ts
|
||||
yarn test Problems.test.tsx
|
||||
|
||||
# Backend tests (Go)
|
||||
go test ./pkg/...
|
||||
```
|
||||
|
||||
Test files are located alongside source code with `.test.ts` or `.test.tsx` extensions:
|
||||
- `src/datasource/specs/*.test.ts`
|
||||
- `src/datasource/components/**/*.test.tsx`
|
||||
- `src/datasource/zabbix/**/*.test.ts`
|
||||
- `src/panel-triggers/components/**/*.test.tsx`
|
||||
|
||||
## Linting and Type Checking
|
||||
|
||||
```bash
|
||||
yarn lint # Check for linting errors
|
||||
yarn lint:fix # Auto-fix lint and formatting issues
|
||||
yarn typecheck # TypeScript type checking
|
||||
```
|
||||
|
||||
## Development Environment
|
||||
|
||||
```bash
|
||||
yarn server # Start Grafana + Zabbix via Docker Compose
|
||||
yarn server:down # Stop and remove containers
|
||||
```
|
||||
|
||||
Docker setup in `/devenv/` includes multiple Zabbix versions (5.0, 6.0, 7.0, 7.2, 7.4) for compatibility testing.
|
||||
|
||||
## Architecture
|
||||
|
||||
**Frontend-Backend Communication**: Frontend queries go through Grafana's backend proxy to the plugin backend executable (`gpx_zabbix-datasource`), which handles Zabbix API calls. Uses gRPC via Grafana Plugin SDK.
|
||||
|
||||
**Key Frontend Packages**:
|
||||
- `/src/datasource/datasource.ts` - Main ZabbixDatasource class
|
||||
- `/src/datasource/zabbix/` - Zabbix API client logic
|
||||
- `/src/datasource/components/` - Query editor, config editor components
|
||||
- `/src/panel-triggers/components/` - Problems panel components
|
||||
|
||||
**Key Backend Packages** (`/pkg/`):
|
||||
- `datasource/` - Main backend logic
|
||||
- `zabbix/` - Zabbix API interactions
|
||||
- `zabbixapi/` - Low-level API connector
|
||||
- `cache/` - Caching layer
|
||||
|
||||
## Requirements
|
||||
|
||||
- Node.js: v24 (see `.nvmrc`)
|
||||
- Yarn: v4.12.0
|
||||
- Go: 1.25
|
||||
- Grafana: >=11.6.0
|
||||
|
||||
## PR Workflow
|
||||
|
||||
Run `yarn changeset` to create a changeset file for your PR. This is required for version bumping and changelog generation.
|
||||
|
||||
## Debugging Backend
|
||||
|
||||
```bash
|
||||
make build-debug # Build with debug flags
|
||||
./debug-backend.sh # Attach delve debugger (after starting Grafana)
|
||||
```
|
||||
|
||||
VS Code debug config connects to delve on port 3222.
|
||||
@@ -4,11 +4,11 @@
|
||||
|
||||
```sh
|
||||
# install frontend deps
|
||||
yarn install --pure-lockfile
|
||||
yarn install
|
||||
# build frontend
|
||||
yarn build
|
||||
#build backend for current platform
|
||||
mage -v build:backend
|
||||
#build backend
|
||||
mage -v
|
||||
```
|
||||
|
||||
## Rebuild backend on changes
|
||||
@@ -19,31 +19,31 @@ mage watch
|
||||
|
||||
## Debugging backend plugin
|
||||
|
||||
For debugging backend part written on Go, you should go through a few steps. First, build a plugin with special flags for debugging:
|
||||
The plugin supports two debugging modes for the Go backend:
|
||||
|
||||
### Standalone Debug Mode
|
||||
|
||||
This mode allows you to run and debug the plugin locally:
|
||||
|
||||
1. Use the **"Standalone debug mode"** configuration in VS Code (already configured in `.vscode/launch.json`)
|
||||
2. Set breakpoints in your Go code
|
||||
3. The plugin will start in standalone mode with the `-standalone` flag
|
||||
4. Run your local grafana instance
|
||||
|
||||
### Debugging with Docker (Recommended for Grafana Integration)
|
||||
|
||||
For debugging the backend plugin while it's running inside Grafana in Docker:
|
||||
|
||||
1. Run the docker compose file with the following command in any of the devenv folders:
|
||||
|
||||
```sh
|
||||
make build-debug
|
||||
DEVELOPMENT=true docker compose up --build -d
|
||||
```
|
||||
|
||||
Then, configure your editor to connect to [delve](https://github.com/go-delve/delve) debugger running in headless mode. This is an example for VS Code:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug backend plugin",
|
||||
"type": "go",
|
||||
"request": "attach",
|
||||
"mode": "remote",
|
||||
"port": 3222,
|
||||
"host": "127.0.0.1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Finally, run grafana-server and then execute `./debug-backend.sh` from grafana-zabbix root folder. This script will attach delve to running plugin. Now you can go to the VS Code and run _Debug backend plugin_ debug config.
|
||||
1. Attach VS Code debugger:
|
||||
- Use the **"Attach to plugin backend in docker"** configuration in VS Code (already configured in `.vscode/launch.json`)
|
||||
- Set breakpoints in your Go code
|
||||
- The debugger will connect to delve running in the Docker container
|
||||
|
||||
## Submitting PR
|
||||
|
||||
|
||||
2
Makefile
2
Makefile
@@ -3,7 +3,7 @@ all: install build test lint
|
||||
# Install dependencies
|
||||
install:
|
||||
# Frontend
|
||||
yarn install --pure-lockfile
|
||||
yarn install
|
||||
# Backend
|
||||
go install -v ./pkg/
|
||||
go install golang.org/x/lint/golint@latest
|
||||
|
||||
@@ -29,7 +29,11 @@
|
||||
"src/**",
|
||||
"pkg/**"
|
||||
],
|
||||
"ignoreRegExpList": ["import\\s*\\((.|[\r\n])*?\\)", "import\\s*.*\".*?\"", "\\[@.+\\]"],
|
||||
"ignoreRegExpList": [
|
||||
"import\\s*\\((.|[\r\n])*?\\)",
|
||||
"import\\s*.*\".*?\"",
|
||||
"\\[@.+\\]"
|
||||
],
|
||||
"words": [
|
||||
"alexanderzobnin",
|
||||
"applicationids",
|
||||
@@ -37,6 +41,7 @@
|
||||
"datapoints",
|
||||
"datasource",
|
||||
"datasources",
|
||||
"devenv",
|
||||
"dompurify",
|
||||
"eventid",
|
||||
"eventids",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM python:3@sha256:6d58c1a9444bc2664f0fa20c43a592fcdb2698eb9a9c32257516538a2746c19a
|
||||
FROM python:3@sha256:17bc9f1d032a760546802cc4e406401eb5fe99dbcb4602c91628e73672fa749c
|
||||
|
||||
ENV ZBX_API_URL=http://zabbix-web
|
||||
ENV ZBX_API_USER="Admin"
|
||||
|
||||
@@ -19,7 +19,6 @@ services:
|
||||
file: ../../.config/docker-compose-base.yaml
|
||||
service: grafana
|
||||
volumes:
|
||||
- ../..:/grafana-zabbix
|
||||
- ../dashboards:/devenv/dashboards
|
||||
- '../datasources.yaml:/etc/grafana/provisioning/datasources/datasources.yaml'
|
||||
- '../dashboards.yaml:/etc/grafana/provisioning/dashboards/dashboards.yaml'
|
||||
@@ -65,7 +64,7 @@ services:
|
||||
- ../nginx/.htpasswd:/etc/nginx/.htpasswd:ro
|
||||
|
||||
database:
|
||||
image: postgres:18@sha256:bfe50b2b0ddd9b55eadedd066fe24c7c6fe06626185b73358c480ea37868024d
|
||||
image: postgres:18@sha256:5773fe724c49c42a7a9ca70202e11e1dff21fb7235b335a73f39297d200b73a2
|
||||
ports:
|
||||
- '15432:5432'
|
||||
environment:
|
||||
|
||||
@@ -19,14 +19,13 @@ services:
|
||||
file: ../../.config/docker-compose-base.yaml
|
||||
service: grafana
|
||||
volumes:
|
||||
- ../..:/grafana-zabbix
|
||||
- ../dashboards:/devenv/dashboards
|
||||
- '../datasources.yaml:/etc/grafana/provisioning/datasources/datasources.yaml'
|
||||
- '../dashboards.yaml:/etc/grafana/provisioning/dashboards/dashboards.yaml'
|
||||
|
||||
# Zabbix
|
||||
zabbix-server:
|
||||
image: zabbix/zabbix-server-pgsql:alpine-6.0-latest@sha256:dbc5a2a27b1163f450b86d5633a8a878c67fb895d435d49fb0f2f6461e43138f
|
||||
image: zabbix/zabbix-server-pgsql:alpine-6.0-latest@sha256:b34303498b75e4a13d76211f250d5a831f746fc13d441d89d1dcea04b64adf4c
|
||||
ports:
|
||||
- '10051:10051'
|
||||
depends_on:
|
||||
@@ -43,7 +42,7 @@ services:
|
||||
ZBX_DEBUGLEVEL: 3
|
||||
|
||||
zabbix-web:
|
||||
image: zabbix/zabbix-web-nginx-pgsql:alpine-6.0-latest@sha256:e034dd99e711ebb3c28ae87750b818279b857504cdac9be521743ecfbc9711e0
|
||||
image: zabbix/zabbix-web-nginx-pgsql:alpine-6.0-latest@sha256:b38d7ae7c073a8f4d0488315cd2c2131ab08294076bba4650f7b3ab065d9c07d
|
||||
ports:
|
||||
- '443:443'
|
||||
- '8188:8080'
|
||||
@@ -64,7 +63,7 @@ services:
|
||||
POSTGRES_DB: zabbix
|
||||
|
||||
database:
|
||||
image: postgres:18@sha256:bfe50b2b0ddd9b55eadedd066fe24c7c6fe06626185b73358c480ea37868024d
|
||||
image: postgres:18@sha256:5773fe724c49c42a7a9ca70202e11e1dff21fb7235b335a73f39297d200b73a2
|
||||
ports:
|
||||
- '15432:5432'
|
||||
command: postgres -c 'max_connections=1000'
|
||||
@@ -73,7 +72,7 @@ services:
|
||||
POSTGRES_PASSWORD: zabbix
|
||||
|
||||
zabbix-agent:
|
||||
image: zabbix/zabbix-agent:alpine-6.0-latest@sha256:9eab77e4f23e68746f935ab71f3c828a6bfc73b2e2783c5453d61a24f996bb6b
|
||||
image: zabbix/zabbix-agent:alpine-6.0-latest@sha256:4686ff4dd5c7e3ea43e9653e35019039e567c544495f55fcb949c2cdd0a6cf50
|
||||
environment:
|
||||
ZBX_SERVER_HOST: zabbix-server
|
||||
ZBX_SERVER_PORT: 10051
|
||||
|
||||
@@ -19,14 +19,13 @@ services:
|
||||
file: ../../.config/docker-compose-base.yaml
|
||||
service: grafana
|
||||
volumes:
|
||||
- ../..:/grafana-zabbix
|
||||
- ../dashboards:/devenv/dashboards
|
||||
- '../datasources.yaml:/etc/grafana/provisioning/datasources/datasources.yaml'
|
||||
- '../dashboards.yaml:/etc/grafana/provisioning/dashboards/dashboards.yaml'
|
||||
|
||||
# Zabbix
|
||||
zabbix-server:
|
||||
image: zabbix/zabbix-server-pgsql:alpine-7.0-latest@sha256:b3c1451adf27e28741fe3b2b3b614c52b0ac68f749b2515224536ef2c1474473
|
||||
image: zabbix/zabbix-server-pgsql:alpine-7.0-latest@sha256:2cec4dde78b1ea1ad483ea823f91dff45abd013779909db004f3568a596c3bde
|
||||
ports:
|
||||
- '10051:10051'
|
||||
depends_on:
|
||||
@@ -43,7 +42,7 @@ services:
|
||||
ZBX_DEBUGLEVEL: 3
|
||||
|
||||
zabbix-web:
|
||||
image: zabbix/zabbix-web-nginx-pgsql:alpine-7.0-latest@sha256:08b2b13e1c92f8cdce81238cdbbb89d1fbc5863e5e7ba0310406212d87988d53
|
||||
image: zabbix/zabbix-web-nginx-pgsql:alpine-7.0-latest@sha256:927b9a2836ee0ae6f44c4c749e50042931e87c7d9866b7d3bbd3901318ae63eb
|
||||
ports:
|
||||
- '443:443'
|
||||
- '8188:8080'
|
||||
@@ -64,7 +63,7 @@ services:
|
||||
POSTGRES_DB: zabbix
|
||||
|
||||
database:
|
||||
image: postgres:18@sha256:bfe50b2b0ddd9b55eadedd066fe24c7c6fe06626185b73358c480ea37868024d
|
||||
image: postgres:18@sha256:5773fe724c49c42a7a9ca70202e11e1dff21fb7235b335a73f39297d200b73a2
|
||||
ports:
|
||||
- '15432:5432'
|
||||
command: postgres -c 'max_connections=1000'
|
||||
@@ -73,7 +72,7 @@ services:
|
||||
POSTGRES_PASSWORD: zabbix
|
||||
|
||||
zabbix-agent:
|
||||
image: zabbix/zabbix-agent:alpine-7.0-latest@sha256:71f1f22b3828f47b714a1bbef39b456453e0e552461d138f36e18a35732302ba
|
||||
image: zabbix/zabbix-agent:alpine-7.0-latest@sha256:52865010468af7f6dc3e3dbd45b645c32b8d133f4a230301cea26ffa679d5cc4
|
||||
environment:
|
||||
ZBX_SERVER_HOST: zabbix-server
|
||||
ZBX_SERVER_PORT: 10051
|
||||
|
||||
@@ -5,7 +5,6 @@ services:
|
||||
file: ../../.config/docker-compose-base.yaml
|
||||
service: grafana
|
||||
volumes:
|
||||
- ../..:/grafana-zabbix
|
||||
- ../dashboards:/devenv/dashboards
|
||||
- '../datasources.yaml:/etc/grafana/provisioning/datasources/datasources.yaml'
|
||||
- '../dashboards.yaml:/etc/grafana/provisioning/dashboards/dashboards.yaml'
|
||||
@@ -45,7 +44,7 @@ services:
|
||||
POSTGRES_DB: zabbix
|
||||
|
||||
database:
|
||||
image: postgres:18@sha256:bfe50b2b0ddd9b55eadedd066fe24c7c6fe06626185b73358c480ea37868024d
|
||||
image: postgres:18@sha256:5773fe724c49c42a7a9ca70202e11e1dff21fb7235b335a73f39297d200b73a2
|
||||
ports:
|
||||
- '15432:5432'
|
||||
command: postgres -c 'max_connections=1000'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM python:3.13-slim-bookworm@sha256:0c5171fd1e80d3133604e1006aa5f788c5f020631537dd1e09edcbe874bb8192
|
||||
FROM python:3.13-slim-bookworm@sha256:97e9392d12279f8c180eb80f0c7c0f3dfe5650f0f2573f7ad770aea58f75ed12
|
||||
|
||||
ENV ZBX_API_URL=http://zabbix-web:8080
|
||||
ENV ZBX_API_USER="Admin"
|
||||
|
||||
@@ -5,14 +5,13 @@ services:
|
||||
file: ../../.config/docker-compose-base.yaml
|
||||
service: grafana
|
||||
volumes:
|
||||
- ../..:/grafana-zabbix
|
||||
- ../dashboards:/devenv/dashboards
|
||||
- '../datasources.yaml:/etc/grafana/provisioning/datasources/datasources.yaml'
|
||||
- '../dashboards.yaml:/etc/grafana/provisioning/dashboards/dashboards.yaml'
|
||||
|
||||
# Zabbix
|
||||
zabbix-server:
|
||||
image: zabbix/zabbix-server-pgsql:alpine-7.4-latest@sha256:a6f748d96bfa4d361df5e9284eba041c475e7ff26c1358f8f189969437ebdb9b
|
||||
image: zabbix/zabbix-server-pgsql:alpine-7.4-latest@sha256:033791214ab8146a4663c56dd6b46f742f03363e441d928216202136d094e154
|
||||
ports:
|
||||
- '10051:10051'
|
||||
depends_on:
|
||||
@@ -29,7 +28,7 @@ services:
|
||||
ZBX_DEBUGLEVEL: 3
|
||||
|
||||
zabbix-web:
|
||||
image: zabbix/zabbix-web-apache-pgsql:alpine-7.4-latest@sha256:9b5ad53c684c9f5d7b15c626e22e3d2c97c2b0b2b05ce811d1a8fe42416cd01b
|
||||
image: zabbix/zabbix-web-apache-pgsql:alpine-7.4-latest@sha256:f7357956bb66ac1c18db0b93a2af1c60ecea9f2ce6366f55bfb5cd6000415441
|
||||
ports:
|
||||
- '8188:8080'
|
||||
depends_on:
|
||||
@@ -45,7 +44,7 @@ services:
|
||||
POSTGRES_DB: zabbix
|
||||
|
||||
database:
|
||||
image: postgres:18@sha256:bfe50b2b0ddd9b55eadedd066fe24c7c6fe06626185b73358c480ea37868024d
|
||||
image: postgres:18@sha256:5773fe724c49c42a7a9ca70202e11e1dff21fb7235b335a73f39297d200b73a2
|
||||
ports:
|
||||
- '15432:5432'
|
||||
command: postgres -c 'max_connections=1000'
|
||||
@@ -54,7 +53,7 @@ services:
|
||||
POSTGRES_PASSWORD: zabbix
|
||||
|
||||
zabbix-agent:
|
||||
image: zabbix/zabbix-agent:alpine-7.4-latest@sha256:2a5989f552e70c1a7d48870ff6002ebbcda1a907e42046851aeae9feba614ac4
|
||||
image: zabbix/zabbix-agent:alpine-7.4-latest@sha256:cfc45062ace0e7d0a12e2e043026c1df53b5eceb91ea66dced9fbcad40ae6cc0
|
||||
environment:
|
||||
ZBX_SERVER_HOST: zabbix-server
|
||||
ZBX_SERVER_PORT: 10051
|
||||
|
||||
60
go.mod
60
go.mod
@@ -1,6 +1,6 @@
|
||||
module github.com/alexanderzobnin/grafana-zabbix
|
||||
|
||||
go 1.25
|
||||
go 1.25.5
|
||||
|
||||
// Go 1.24 enabled the post-quantum key exchange mechanism
|
||||
// X25519MLKEM768 by default. It can cause issues with some TLS servers
|
||||
@@ -11,17 +11,17 @@ godebug tlsmlkem=0
|
||||
require (
|
||||
github.com/bitly/go-simplejson v0.5.1
|
||||
github.com/dlclark/regexp2 v1.11.5
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.284.0
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.286.0
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/stretchr/testify v1.11.1
|
||||
golang.org/x/net v0.48.0
|
||||
golang.org/x/net v0.49.0
|
||||
gotest.tools v2.2.0+incompatible
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||
github.com/apache/arrow-go/v18 v18.4.1 // indirect
|
||||
github.com/apache/arrow-go/v18 v18.5.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
@@ -35,20 +35,20 @@ require (
|
||||
github.com/gogo/googleapis v1.4.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/flatbuffers v25.2.10+incompatible // indirect
|
||||
github.com/google/flatbuffers v25.9.23+incompatible // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grafana/otel-profiling-go v0.5.1 // indirect
|
||||
github.com/grafana/pyroscope-go/godeltaprof v0.1.9 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
|
||||
github.com/hashicorp/go-hclog v1.6.3 // indirect
|
||||
github.com/hashicorp/go-plugin v1.7.0 // indirect
|
||||
github.com/hashicorp/yamux v0.1.2 // indirect
|
||||
github.com/jaegertracing/jaeger-idl v0.5.0 // indirect
|
||||
github.com/jaegertracing/jaeger-idl v0.6.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/compress v1.18.2 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/magefile/mage v1.15.0 // indirect
|
||||
github.com/mattetti/filebuffer v1.0.1 // indirect
|
||||
@@ -64,7 +64,7 @@ require (
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.67.2 // indirect
|
||||
github.com/prometheus/common v0.67.4 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
@@ -73,31 +73,31 @@ require (
|
||||
github.com/unknwon/log v0.0.0-20200308114134-929b1006e34a // indirect
|
||||
github.com/urfave/cli v1.22.17 // indirect
|
||||
github.com/zeebo/xxh3 v1.0.2 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 // indirect
|
||||
go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0 // indirect
|
||||
go.opentelemetry.io/otel v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.64.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.39.0 // indirect
|
||||
go.opentelemetry.io/contrib/samplers/jaegerremote v0.33.0 // indirect
|
||||
go.opentelemetry.io/otel v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.39.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 // indirect
|
||||
golang.org/x/mod v0.30.0 // indirect
|
||||
golang.org/x/mod v0.31.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
golang.org/x/tools v0.39.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
golang.org/x/tools v0.40.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797 // indirect
|
||||
google.golang.org/grpc v1.76.0 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||
google.golang.org/grpc v1.78.0 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
128
go.sum
128
go.sum
@@ -3,8 +3,8 @@ github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/apache/arrow-go/v18 v18.4.1 h1:q/jVkBWCJOB9reDgaIZIdruLQUb1kbkvOnOFezVH1C4=
|
||||
github.com/apache/arrow-go/v18 v18.4.1/go.mod h1:tLyFubsAl17bvFdUAy24bsSvA/6ww95Iqi67fTpGu3E=
|
||||
github.com/apache/arrow-go/v18 v18.5.0 h1:rmhKjVA+MKVnQIMi/qnM0OxeY4tmHlN3/Pvu+Itmd6s=
|
||||
github.com/apache/arrow-go/v18 v18.5.0/go.mod h1:F1/wPb3bUy6ZdP4kEPWC7GUZm+yDmxXFERK6uDSkhr8=
|
||||
github.com/apache/thrift v0.22.0 h1:r7mTJdj51TMDe6RtcmNdQxgn9XcyfGDOzegMDRg47uc=
|
||||
github.com/apache/thrift v0.22.0/go.mod h1:1e7J/O1Ae6ZQMTYdy9xa3w9k+XHWPfRvdPyJeynQ+/g=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
@@ -51,8 +51,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
||||
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q=
|
||||
github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/flatbuffers v25.9.23+incompatible h1:rGZKv+wOb6QPzIdkM2KxhBZCDrA0DeN6DNmRDrqIsQU=
|
||||
github.com/google/flatbuffers v25.9.23+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
@@ -61,8 +61,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.284.0 h1:1bK7eWsnPBLUWDcWJWe218Ik5ad0a5JpEL4mH9ry7Ws=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.284.0/go.mod h1:lHPniaSxq3SL5MxDIPy04TYB1jnTp/ivkYO+xn5Rz3E=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.286.0 h1:nrG/XLDpYuUONVpVVGIUxPJdWeXGHM/47qIAnuDkdjg=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.286.0/go.mod h1:cNFa2EpURNF5Hy15kH7HfVdprNu+UEmNZx7TMWVdctY=
|
||||
github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8=
|
||||
github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls=
|
||||
github.com/grafana/pyroscope-go/godeltaprof v0.1.9 h1:c1Us8i6eSmkW+Ez05d3co8kasnuOY813tbMN8i/a3Og=
|
||||
@@ -71,16 +71,16 @@ github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 h1:QGLs
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0/go.mod h1:hM2alZsMUni80N33RBe6J0e423LB+odMj7d3EMP9l20=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3/go.mod h1:NbCUVmiS4foBGBHOYlCT25+YmGpJ32dZPi75pGEUpj4=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
|
||||
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
||||
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA=
|
||||
github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8=
|
||||
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
|
||||
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
|
||||
github.com/jaegertracing/jaeger-idl v0.5.0 h1:zFXR5NL3Utu7MhPg8ZorxtCBjHrL3ReM1VoB65FOFGE=
|
||||
github.com/jaegertracing/jaeger-idl v0.5.0/go.mod h1:ON90zFo9eoyXrt9F/KN8YeF3zxcnujaisMweFY/rg5k=
|
||||
github.com/jaegertracing/jaeger-idl v0.6.0 h1:LOVQfVby9ywdMPI9n3hMwKbyLVV3BL1XH2QqsP5KTMk=
|
||||
github.com/jaegertracing/jaeger-idl v0.6.0/go.mod h1:mpW0lZfG907/+o5w5OlnNnig7nHJGT3SfKmRqC42HGQ=
|
||||
github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94=
|
||||
github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
@@ -91,8 +91,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4=
|
||||
github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
@@ -147,15 +147,15 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.67.2 h1:PcBAckGFTIHt2+L3I33uNRTlKTplNzFctXcWhPyAEN8=
|
||||
github.com/prometheus/common v0.67.2/go.mod h1:63W3KZb1JOKgcjlIr64WW/LvFGAqKPj0atm+knVGEko=
|
||||
github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc=
|
||||
github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
|
||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
@@ -192,38 +192,38 @@ github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
||||
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
|
||||
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0 h1:2pn7OzMewmYRiNtv1doZnLo3gONcnMHlFnmOR8Vgt+8=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0/go.mod h1:rjbQTDEPQymPE0YnRQp9/NuPwwtL0sesz/fnqRW/v84=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 h1:nXGeLvT1QtCAhkASkP/ksjkTKZALIaQBIW+JSIw1KIc=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.38.0/go.mod h1:oMvOXk78ZR3KEuPMBgp/ThAMDy9ku/eyUVztr+3G6Wo=
|
||||
go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0 h1:oPW/SRFyHgIgxrvNhSBzqvZER2N5kRlci3/rGTOuyWo=
|
||||
go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0/go.mod h1:B9Oka5QVD0bnmZNO6gBbBta6nohD/1Z+f9waH2oXyBs=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.64.0 h1:OXSUzgmIFkcC4An+mv+lqqZSndTffXpjAyoR+1f8k/A=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.64.0/go.mod h1:1A4GVLFIm54HFqVdOpWmukap7rgb0frrE3zWXohLPdM=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.39.0 h1:Gz3yKzfMSEFzF0Vy5eIpu9ndpo4DhXMCxsLMF0OOApo=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.39.0/go.mod h1:2D/cxxCqTlrday0rZrPujjg5aoAdqk1NaNyoXn8FJn8=
|
||||
go.opentelemetry.io/contrib/samplers/jaegerremote v0.33.0 h1:RcFp4UxGTE2VQQ0M7s24YRUShEJ5D5JDnd5g2EaTh6E=
|
||||
go.opentelemetry.io/contrib/samplers/jaegerremote v0.33.0/go.mod h1:y6oMwgsv+yWYCLRigU6Pp07/x4KZUEh8LIPTSUnQKbQ=
|
||||
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk=
|
||||
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c=
|
||||
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
|
||||
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
||||
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
||||
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
|
||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
|
||||
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
|
||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
|
||||
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
@@ -235,14 +235,14 @@ golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 h1:TQwNpfvNkxAVlItJf6Cr5JTsV
|
||||
golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
||||
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -260,20 +260,20 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 h1:E2/AqCUMZGgd73TQkxUMcMla25GB9i/5HOdLr+uH7Vo=
|
||||
golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc h1:bH6xUXay0AIFMElXG2rQ4uiE+7ncwtiOdPfYK1NK2XA=
|
||||
golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
||||
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -282,14 +282,14 @@ golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhS
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797 h1:CirRxTOwnRWVLKzDNrs0CXAaVozJoR4G9xvdRecrdpk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ=
|
||||
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
|
||||
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
|
||||
36
package.json
36
package.json
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"name": "grafana-zabbix",
|
||||
"version": "6.1.0",
|
||||
"version": "6.2-rit",
|
||||
|
||||
|
||||
"description": "Zabbix plugin for Grafana",
|
||||
"homepage": "http://grafana-zabbix.org",
|
||||
"bugs": {
|
||||
@@ -42,37 +44,37 @@
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.28.5",
|
||||
"@babel/core": "7.28.6",
|
||||
"@changesets/cli": "2.29.8",
|
||||
"@grafana/e2e-selectors": "12.3.1",
|
||||
"@grafana/eslint-config": "9.0.0",
|
||||
"@grafana/plugin-e2e": "3.1.1",
|
||||
"@grafana/plugin-e2e": "3.1.4",
|
||||
"@grafana/tsconfig": "2.0.1",
|
||||
"@playwright/test": "1.57.0",
|
||||
"@stylistic/eslint-plugin-ts": "4.4.1",
|
||||
"@swc/core": "1.15.8",
|
||||
"@swc/core": "1.15.10",
|
||||
"@swc/helpers": "0.5.18",
|
||||
"@swc/jest": "0.2.39",
|
||||
"@tanstack/react-table": "8.21.3",
|
||||
"@testing-library/dom": "10.4.1",
|
||||
"@testing-library/jest-dom": "6.9.1",
|
||||
"@testing-library/react": "16.3.1",
|
||||
"@testing-library/react": "16.3.2",
|
||||
"@types/grafana": "github:CorpGlory/types-grafana",
|
||||
"@types/jest": "30.0.0",
|
||||
"@types/lodash": "4.17.22",
|
||||
"@types/node": "25.0.5",
|
||||
"@types/lodash": "4.17.23",
|
||||
"@types/node": "25.0.10",
|
||||
"@types/react": "18.3.27",
|
||||
"@types/react-dom": "18.3.7",
|
||||
"@typescript-eslint/eslint-plugin": "8.52.0",
|
||||
"@typescript-eslint/parser": "8.52.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.53.1",
|
||||
"@typescript-eslint/parser": "8.53.1",
|
||||
"autoprefixer": "10.4.23",
|
||||
"copy-webpack-plugin": "13.0.1",
|
||||
"cspell": "9.4.0",
|
||||
"cspell": "9.6.0",
|
||||
"css-loader": "7.1.2",
|
||||
"eslint": "9.39.2",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"eslint-plugin-jsdoc": "62.0.0",
|
||||
"eslint-plugin-prettier": "5.5.4",
|
||||
"eslint-plugin-jsdoc": "62.3.1",
|
||||
"eslint-plugin-prettier": "5.5.5",
|
||||
"eslint-plugin-react": "7.37.5",
|
||||
"eslint-plugin-react-hooks": "7.0.1",
|
||||
"eslint-webpack-plugin": "5.0.2",
|
||||
@@ -82,20 +84,20 @@
|
||||
"imports-loader": "5.0.0",
|
||||
"jest": "30.2.0",
|
||||
"jest-environment-jsdom": "30.2.0",
|
||||
"lodash": "4.17.21",
|
||||
"mini-css-extract-plugin": "2.9.4",
|
||||
"lodash": "4.17.23",
|
||||
"mini-css-extract-plugin": "2.10.0",
|
||||
"moment": "2.30.1",
|
||||
"postcss": "8.5.6",
|
||||
"postcss-loader": "8.2.0",
|
||||
"postcss-reporter": "7.1.0",
|
||||
"postcss-scss": "4.0.9",
|
||||
"prettier": "3.7.4",
|
||||
"prettier": "3.8.1",
|
||||
"replace-in-file-webpack-plugin": "1.0.6",
|
||||
"sass": "1.97.2",
|
||||
"sass": "1.97.3",
|
||||
"sass-loader": "16.0.6",
|
||||
"semver": "7.7.3",
|
||||
"style-loader": "4.0.0",
|
||||
"swc-loader": "0.2.6",
|
||||
"swc-loader": "0.2.7",
|
||||
"terser-webpack-plugin": "5.3.16",
|
||||
"ts-node": "10.9.2",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
|
||||
var (
|
||||
ErrNonMetricQueryNotSupported = errors.New("non-metrics queries are not supported")
|
||||
test = errors.New("This is a test error")
|
||||
)
|
||||
|
||||
type ZabbixDatasource struct {
|
||||
@@ -147,6 +148,10 @@ func (ds *ZabbixDatasource) QueryData(ctx context.Context, req *backend.QueryDat
|
||||
frames, queryErr = zabbixDS.queryNumericItems(queryCtx, &query)
|
||||
case MODE_ITEMID:
|
||||
frames, queryErr = zabbixDS.queryItemIdData(queryCtx, &query)
|
||||
case MODE_PROBLEMS:
|
||||
queryErr = backend.DownstreamError(test) //send a test error
|
||||
case MODE_PROBLEMS_ALERTING:
|
||||
frames, queryErr = zabbixDS.queryProblemsAlerting(queryCtx, &query)
|
||||
default:
|
||||
queryErr = backend.DownstreamError(ErrNonMetricQueryNotSupported)
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ const (
|
||||
MODE_ITEMID = "3"
|
||||
MODE_TRIGGERS = "4"
|
||||
MODE_PROBLEMS = "5"
|
||||
MODE_PROBLEMS_ALERTING = "7"
|
||||
)
|
||||
|
||||
type DBConnectionPostProcessingRequest struct {
|
||||
@@ -127,13 +128,15 @@ func ReadQuery(query backend.DataQuery) (QueryModel, error) {
|
||||
return model, backend.DownstreamError(fmt.Errorf("could not read query JSON: %w", err))
|
||||
}
|
||||
|
||||
queryType, err := queryJSON.Get("queryType").Int64()
|
||||
if err != nil {
|
||||
// Try reading queryType as string first, then as int64
|
||||
if queryTypeStr, err := queryJSON.Get("queryType").String(); err == nil && queryTypeStr != "" {
|
||||
model.QueryType = queryTypeStr
|
||||
} else if queryType, err := queryJSON.Get("queryType").Int64(); err == nil {
|
||||
model.QueryType = strconv.FormatInt(queryType, 10)
|
||||
} else {
|
||||
log.DefaultLogger.Warn("could not read query type", "error", err)
|
||||
log.DefaultLogger.Debug("setting query type to default value")
|
||||
model.QueryType = "0"
|
||||
} else {
|
||||
model.QueryType = strconv.FormatInt(queryType, 10)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
125
pkg/datasource/problems_alerting.go
Normal file
125
pkg/datasource/problems_alerting.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package datasource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alexanderzobnin/grafana-zabbix/pkg/zabbix"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
)
|
||||
|
||||
func (ds *ZabbixDatasourceInstance) queryProblemsAlerting(ctx context.Context, query *QueryModel) ([]*data.Frame, error) {
|
||||
ds.logger.Info("queryProblemsAlerting called", "groupFilter", query.Group.Filter, "hostFilter", query.Host.Filter)
|
||||
|
||||
triggers, err := ds.zabbix.GetTriggers(ctx, query.Group.Filter, query.Host.Filter)
|
||||
if err != nil {
|
||||
ds.logger.Error("GetTriggers failed", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ds.logger.Info("GetTriggers returned", "count", len(triggers))
|
||||
|
||||
if len(triggers) == 0 {
|
||||
frame := data.NewFrame("No triggers found")
|
||||
frame.RefID = query.RefID
|
||||
return []*data.Frame{frame}, nil
|
||||
}
|
||||
|
||||
triggerIDs := make([]string, len(triggers))
|
||||
for i, t := range triggers {
|
||||
triggerIDs[i] = t.ID
|
||||
}
|
||||
|
||||
problems, err := ds.zabbix.GetProblems(ctx, triggerIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Map trigger ID to problem (includes opdata captured at problem creation)
|
||||
problemMap := buildProblemMap(problems)
|
||||
currentTime := time.Now()
|
||||
|
||||
frames := make([]*data.Frame, 0)
|
||||
for _, trigger := range triggers {
|
||||
for _, host := range trigger.Hosts {
|
||||
value := 0.0
|
||||
problemName := ""
|
||||
opdata := ""
|
||||
if problem, hasProblem := problemMap[trigger.ID]; hasProblem {
|
||||
value = 1.0
|
||||
problemName = problem.Name // Expanded trigger description at problem creation
|
||||
opdata = problem.Opdata // Operational data if configured
|
||||
}
|
||||
frame := createAlertingFrame(trigger, host, value, problemName, opdata, currentTime, query.RefID)
|
||||
frames = append(frames, frame)
|
||||
}
|
||||
}
|
||||
|
||||
return frames, nil
|
||||
}
|
||||
|
||||
// buildProblemMap returns a map of trigger ID to Problem object
|
||||
func buildProblemMap(problems []zabbix.Problem) map[string]zabbix.Problem {
|
||||
problemMap := make(map[string]zabbix.Problem)
|
||||
for _, p := range problems {
|
||||
problemMap[p.ObjectID] = p
|
||||
}
|
||||
return problemMap
|
||||
}
|
||||
|
||||
func createAlertingFrame(trigger zabbix.Trigger, host zabbix.ItemHost, value float64, problemName string, opdata string, ts time.Time, refID string) *data.Frame {
|
||||
labels := data.Labels{
|
||||
"host": host.Name,
|
||||
"trigger": trigger.Description,
|
||||
"trigger_id": trigger.ID,
|
||||
"severity": mapSeverity(trigger.Priority),
|
||||
"tags": formatTags(trigger.Tags),
|
||||
"problem_name": problemName, // Expanded trigger description at problem creation
|
||||
"opdata": opdata, // Operational data if configured on trigger
|
||||
}
|
||||
|
||||
timeField := data.NewField("time", nil, []time.Time{ts})
|
||||
valueField := data.NewField("value", labels, []float64{value})
|
||||
|
||||
frameName := fmt.Sprintf("%s: %s", host.Name, trigger.Description)
|
||||
frame := data.NewFrame(frameName, timeField, valueField)
|
||||
frame.RefID = refID
|
||||
|
||||
return frame
|
||||
}
|
||||
|
||||
func mapSeverity(priority string) string {
|
||||
switch priority {
|
||||
case "0":
|
||||
return "Not classified"
|
||||
case "1":
|
||||
return "Information"
|
||||
case "2":
|
||||
return "Warning"
|
||||
case "3":
|
||||
return "Average"
|
||||
case "4":
|
||||
return "High"
|
||||
case "5":
|
||||
return "Disaster"
|
||||
default:
|
||||
return priority
|
||||
}
|
||||
}
|
||||
|
||||
func formatTags(tags []zabbix.Tag) string {
|
||||
if len(tags) == 0 {
|
||||
return ""
|
||||
}
|
||||
parts := make([]string, len(tags))
|
||||
for i, t := range tags {
|
||||
if t.Value != "" {
|
||||
parts[i] = fmt.Sprintf("%s:%s", t.Tag, t.Value)
|
||||
} else {
|
||||
parts[i] = t.Tag
|
||||
}
|
||||
}
|
||||
return strings.Join(parts, ",")
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
)
|
||||
@@ -105,16 +106,24 @@ func (ds *Zabbix) GetItems(
|
||||
}
|
||||
|
||||
if isRegex(itemTagFilter) {
|
||||
ds.logger.Debug("Processing regex item tag filter", "filterPattern", itemTagFilter, "hostCount", len(hostids))
|
||||
tags, err := ds.GetItemTags(ctx, groupFilter, hostFilter, itemTagFilter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ds.logger.Debug("GetItemTags completed", "matchedTagCount", len(tags), "filterPattern", itemTagFilter)
|
||||
// If regex filter doesn't match any tags, return empty items list
|
||||
// to prevent silently removing the filter and returning all items
|
||||
if len(tags) == 0 {
|
||||
return []*Item{}, nil
|
||||
}
|
||||
var tagStrs []string
|
||||
for _, t := range tags {
|
||||
tagStrs = append(tagStrs, itemTagToString(t))
|
||||
}
|
||||
itemTagFilter = strings.Join(tagStrs, ",")
|
||||
}
|
||||
ds.logger.Debug("Fetching items with filters", "hostCount", len(hostids), "itemType", itemType, "showDisabled", showDisabled, "hasItemTagFilter", len(itemTagFilter) > 0)
|
||||
allItems, err = ds.GetAllItems(ctx, hostids, nil, itemType, showDisabled, itemTagFilter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -311,6 +320,10 @@ func (ds *Zabbix) GetHosts(ctx context.Context, groupFilter string, hostFilter s
|
||||
}
|
||||
|
||||
func filterHostsByQuery(items []Host, filter string) ([]Host, error) {
|
||||
if filter == "" {
|
||||
return items, nil
|
||||
}
|
||||
|
||||
re, err := parseFilter(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -346,6 +359,10 @@ func (ds *Zabbix) GetGroups(ctx context.Context, groupFilter string) ([]Group, e
|
||||
}
|
||||
|
||||
func filterGroupsByQuery(items []Group, filter string) ([]Group, error) {
|
||||
if filter == "" {
|
||||
return items, nil
|
||||
}
|
||||
|
||||
re, err := parseFilter(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -549,3 +566,113 @@ func (ds *Zabbix) GetVersion(ctx context.Context) (int, error) {
|
||||
versionNum, err := strconv.Atoi(version)
|
||||
return versionNum, err
|
||||
}
|
||||
|
||||
func (ds *Zabbix) GetTriggers(ctx context.Context, groupFilter string, hostFilter string) ([]Trigger, error) {
|
||||
ds.logger.Info("GetTriggers called", "groupFilter", groupFilter, "hostFilter", hostFilter)
|
||||
|
||||
hosts, err := ds.GetHosts(ctx, groupFilter, hostFilter)
|
||||
if err != nil {
|
||||
ds.logger.Error("GetHosts failed", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
ds.logger.Info("GetHosts returned", "count", len(hosts))
|
||||
|
||||
if len(hosts) == 0 {
|
||||
return []Trigger{}, nil
|
||||
}
|
||||
|
||||
hostids := make([]string, 0)
|
||||
for _, host := range hosts {
|
||||
hostids = append(hostids, host.ID)
|
||||
}
|
||||
|
||||
// Get triggers currently in PROBLEM state (always include these)
|
||||
problemParams := ZabbixAPIParams{
|
||||
"output": []string{"triggerid", "description", "priority", "value"},
|
||||
"hostids": hostids,
|
||||
"selectHosts": []string{"hostid", "name"},
|
||||
"selectTags": "extend",
|
||||
"monitored": true,
|
||||
"expandDescription": true,
|
||||
"skipDependent": true, // Only show root cause triggers, not dependent ones
|
||||
"filter": map[string]interface{}{
|
||||
"value": "1",
|
||||
},
|
||||
}
|
||||
|
||||
problemResult, err := ds.Request(ctx, &ZabbixAPIRequest{Method: "trigger.get", Params: problemParams})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var problemTriggers []Trigger
|
||||
err = convertTo(problemResult, &problemTriggers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get triggers that changed state in the last 24 hours (for alert resolution)
|
||||
oneDayAgo := time.Now().Add(-24 * time.Hour).Unix()
|
||||
recentParams := ZabbixAPIParams{
|
||||
"output": []string{"triggerid", "description", "priority", "value"},
|
||||
"hostids": hostids,
|
||||
"selectHosts": []string{"hostid", "name"},
|
||||
"selectTags": "extend",
|
||||
"monitored": true,
|
||||
"expandDescription": true,
|
||||
"skipDependent": true, // Only show root cause triggers, not dependent ones
|
||||
"lastChangeSince": oneDayAgo,
|
||||
}
|
||||
|
||||
recentResult, err := ds.Request(ctx, &ZabbixAPIRequest{Method: "trigger.get", Params: recentParams})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var recentTriggers []Trigger
|
||||
err = convertTo(recentResult, &recentTriggers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Merge and deduplicate
|
||||
triggerMap := make(map[string]Trigger)
|
||||
for _, t := range problemTriggers {
|
||||
triggerMap[t.ID] = t
|
||||
}
|
||||
for _, t := range recentTriggers {
|
||||
triggerMap[t.ID] = t
|
||||
}
|
||||
|
||||
triggers := make([]Trigger, 0, len(triggerMap))
|
||||
for _, t := range triggerMap {
|
||||
triggers = append(triggers, t)
|
||||
}
|
||||
|
||||
ds.logger.Info("GetTriggers returning", "problemCount", len(problemTriggers), "recentCount", len(recentTriggers), "totalCount", len(triggers))
|
||||
return triggers, nil
|
||||
}
|
||||
|
||||
func (ds *Zabbix) GetProblems(ctx context.Context, triggerIDs []string) ([]Problem, error) {
|
||||
if len(triggerIDs) == 0 {
|
||||
return []Problem{}, nil
|
||||
}
|
||||
|
||||
params := ZabbixAPIParams{
|
||||
"output": []string{"eventid", "objectid", "severity", "name", "opdata"},
|
||||
"objectids": triggerIDs,
|
||||
"source": "0",
|
||||
"object": "0",
|
||||
"recent": true,
|
||||
"selectTags": "extend",
|
||||
}
|
||||
|
||||
result, err := ds.Request(ctx, &ZabbixAPIRequest{Method: "problem.get", Params: params})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var problems []Problem
|
||||
err = convertTo(result, &problems)
|
||||
return problems, err
|
||||
}
|
||||
|
||||
@@ -122,6 +122,76 @@ func TestGetItems(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetItemsWithRegexItemTagFilterNoMatch(t *testing.T) {
|
||||
// Test that when a regex itemTagFilter doesn't match any tags,
|
||||
// GetItems returns an empty list instead of all items.
|
||||
//
|
||||
// This test verifies the fix for the bug where an empty tag filter result
|
||||
// would cause itemTagFilter to become empty string, silently removing the filter.
|
||||
//
|
||||
// Bug scenario (without fix):
|
||||
// 1. Regex itemTagFilter "/^NonExistentTag/" doesn't match any tags
|
||||
// 2. GetItemTags returns empty list
|
||||
// 3. itemTagFilter becomes "" (empty string)
|
||||
// 4. GetAllItems is called with empty itemTagFilter → returns ALL items (no tag filtering)
|
||||
// 5. filterItemsByQuery filters by itemFilter "/.*/" → returns all 2 items
|
||||
//
|
||||
// Fixed scenario (with fix):
|
||||
// 1. Regex itemTagFilter "/^NonExistentTag/" doesn't match any tags
|
||||
// 2. GetItemTags returns empty list
|
||||
// 3. Early return with empty items list → returns 0 items
|
||||
itemGetCalls := []map[string]interface{}{}
|
||||
client := NewZabbixClientWithHandler(t, func(payload ApiRequestPayload) string {
|
||||
if payload.Method == "item.get" {
|
||||
// Track all item.get calls to verify behavior
|
||||
paramsCopy := make(map[string]interface{})
|
||||
for k, v := range payload.Params {
|
||||
paramsCopy[k] = v
|
||||
}
|
||||
itemGetCalls = append(itemGetCalls, paramsCopy)
|
||||
|
||||
// Return items that would be returned if no tag filter is applied
|
||||
return `{
|
||||
"result":[
|
||||
{"itemid":"100","name":"CPU usage","tags":[{"tag":"Env","value":"prod"}]},
|
||||
{"itemid":"200","name":"Memory usage","tags":[{"tag":"Application","value":"api"}]}
|
||||
]
|
||||
}`
|
||||
}
|
||||
if payload.Method == "hostgroup.get" {
|
||||
return `{"result":[{"groupid":"1","name":"Servers"}]}`
|
||||
}
|
||||
if payload.Method == "host.get" {
|
||||
return `{"result":[{"hostid":"10","name":"web01"}]}`
|
||||
}
|
||||
if payload.Method == "apiinfo.version" {
|
||||
return `{"result":"6.4.0"}`
|
||||
}
|
||||
return `{"result":null}`
|
||||
})
|
||||
|
||||
// Use a regex itemTagFilter that won't match any tags
|
||||
// Use itemFilter "/.*/" which matches all items, so we can detect if bug returns all items
|
||||
items, err := client.GetItems(context.Background(), "Servers", "web01", "/^NonExistentTag/", "/.*/", "num", false)
|
||||
require.NoError(t, err)
|
||||
|
||||
// With fix: Should return empty list (0 items) because no tags matched
|
||||
// Without fix: Would return 2 items because itemTagFilter becomes empty and GetAllItems returns all items
|
||||
if len(items) != 0 {
|
||||
t.Errorf("Expected empty list when regex itemTagFilter matches no tags, but got %d items. This indicates the bug: itemTagFilter was silently removed and all items were returned.", len(items))
|
||||
t.Logf("Item.get was called %d times", len(itemGetCalls))
|
||||
for i, call := range itemGetCalls {
|
||||
tags, hasTags := call["tags"]
|
||||
if hasTags {
|
||||
t.Logf("Call %d: has tags filter: %v", i+1, tags)
|
||||
} else {
|
||||
t.Logf("Call %d: NO tags filter (this is the bug - should not call GetAllItems with empty filter)", i+1)
|
||||
}
|
||||
}
|
||||
}
|
||||
assert.Len(t, items, 0, "Expected empty list when regex itemTagFilter matches no tags. Without fix, this would return 2 items.")
|
||||
}
|
||||
|
||||
func TestGetItemsBefore54(t *testing.T) {
|
||||
client := NewZabbixClientWithResponses(t, map[string]string{
|
||||
"hostgroup.get": `{"result":[{"groupid":"1","name":"Servers"}]}`,
|
||||
|
||||
@@ -111,3 +111,21 @@ type ValueMapping struct {
|
||||
Value string `json:"value"`
|
||||
NewValue string `json:"newvalue"`
|
||||
}
|
||||
|
||||
type Trigger struct {
|
||||
ID string `json:"triggerid"`
|
||||
Description string `json:"description"`
|
||||
Priority string `json:"priority"`
|
||||
Value string `json:"value"`
|
||||
Hosts []ItemHost `json:"hosts,omitempty"`
|
||||
Tags []Tag `json:"tags,omitempty"`
|
||||
}
|
||||
|
||||
type Problem struct {
|
||||
EventID string `json:"eventid"`
|
||||
ObjectID string `json:"objectid"`
|
||||
Severity string `json:"severity"`
|
||||
Name string `json:"name"`
|
||||
Opdata string `json:"opdata"`
|
||||
Tags []Tag `json:"tags,omitempty"`
|
||||
}
|
||||
|
||||
@@ -55,6 +55,11 @@ const zabbixQueryTypeOptions: Array<ComboboxOption<QueryType>> = [
|
||||
label: 'User macros',
|
||||
description: 'User Macros',
|
||||
},
|
||||
{
|
||||
value: c.MODE_PROBLEMS_ALERTING,
|
||||
label: 'Problems (Alerting)',
|
||||
description: 'Query problems for alerting (returns 0/1 time series)',
|
||||
},
|
||||
];
|
||||
|
||||
const getDefaultQuery: () => Partial<ZabbixMetricsQuery> = () => ({
|
||||
@@ -226,6 +231,7 @@ export const QueryEditor = ({ query, datasource, onChange, onRunQuery, range }:
|
||||
{queryType === c.MODE_TEXT && renderTextMetricsEditor()}
|
||||
{queryType === c.MODE_ITSERVICE && renderITServicesEditor()}
|
||||
{queryType === c.MODE_PROBLEMS && renderProblemsEditor()}
|
||||
{queryType === c.MODE_PROBLEMS_ALERTING && renderProblemsEditor()}
|
||||
{queryType === c.MODE_TRIGGERS && renderTriggersEditor()}
|
||||
{queryType === c.MODE_MACROS && renderUserMacrosEditor()}
|
||||
<QueryOptionsEditor queryType={queryType} queryOptions={query.options} onChange={onOptionsChange} />
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import React from 'react';
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
import { ProblemsQueryEditor } from './ProblemsQueryEditor';
|
||||
import { ShowProblemTypes, ZabbixTagEvalType } from '../../types/query';
|
||||
|
||||
const metricPickerSpy = jest.fn();
|
||||
|
||||
jest.mock('../../../components', () => ({
|
||||
MetricPicker: (props: any) => {
|
||||
metricPickerSpy(props);
|
||||
return null;
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
getTemplateSrv: jest.fn(() => ({
|
||||
getVariables: jest.fn(() => []),
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('@grafana/ui', () => ({
|
||||
Combobox: (props: any) => <div {...props} />,
|
||||
InlineField: ({ children }: any) => <div>{children}</div>,
|
||||
InlineFieldRow: ({ children }: any) => <div>{children}</div>,
|
||||
InlineFormLabel: ({ children }: any) => <div>{children}</div>,
|
||||
Input: (props: any) => <input {...props} />,
|
||||
MultiSelect: (props: any) => <div {...props} />,
|
||||
}));
|
||||
|
||||
const baseQuery: any = {
|
||||
group: { filter: '' },
|
||||
host: { filter: '' },
|
||||
proxy: { filter: '' },
|
||||
application: { filter: '' },
|
||||
trigger: { filter: '' },
|
||||
tags: { filter: '' },
|
||||
evaltype: ZabbixTagEvalType.AndOr,
|
||||
showProblems: ShowProblemTypes.Problems,
|
||||
options: { severities: [] },
|
||||
};
|
||||
|
||||
const buildDatasource = (overrides: Partial<any> = {}) => {
|
||||
const zabbix = {
|
||||
getAllGroups: jest.fn().mockResolvedValue([]),
|
||||
getAllHosts: jest.fn().mockResolvedValue([]),
|
||||
getAllApps: jest.fn().mockResolvedValue([]),
|
||||
getProxies: jest.fn().mockResolvedValue([]),
|
||||
supportsApplications: jest.fn(() => true),
|
||||
...overrides,
|
||||
};
|
||||
|
||||
return {
|
||||
zabbix,
|
||||
interpolateVariablesInQueries: jest.fn((queries: any[]) => queries),
|
||||
};
|
||||
};
|
||||
|
||||
describe('ProblemsQueryEditor', () => {
|
||||
beforeEach(() => {
|
||||
metricPickerSpy.mockClear();
|
||||
});
|
||||
|
||||
it('uses proxy name when host is missing', async () => {
|
||||
const datasource = buildDatasource({
|
||||
getProxies: jest.fn().mockResolvedValue([{ name: 'proxy-a' }]),
|
||||
});
|
||||
|
||||
render(<ProblemsQueryEditor query={baseQuery} datasource={datasource as any} onChange={jest.fn()} />);
|
||||
|
||||
await waitFor(() => {
|
||||
const proxyCall = metricPickerSpy.mock.calls
|
||||
.map((call) => call[0])
|
||||
.find((props) => props?.placeholder === 'Proxy name' && props?.options);
|
||||
|
||||
expect(proxyCall).toBeTruthy();
|
||||
expect(proxyCall.options).toEqual([{ value: 'proxy-a', label: 'proxy-a' }]);
|
||||
});
|
||||
});
|
||||
|
||||
it('uses proxy host when present', async () => {
|
||||
const datasource = buildDatasource({
|
||||
getProxies: jest.fn().mockResolvedValue([{ host: 'legacy-proxy' }]),
|
||||
});
|
||||
|
||||
render(<ProblemsQueryEditor query={baseQuery} datasource={datasource as any} onChange={jest.fn()} />);
|
||||
|
||||
await waitFor(() => {
|
||||
const proxyCall = metricPickerSpy.mock.calls
|
||||
.map((call) => call[0])
|
||||
.find((props) => props?.placeholder === 'Proxy name' && props?.options);
|
||||
|
||||
expect(proxyCall).toBeTruthy();
|
||||
expect(proxyCall.options).toEqual([{ value: 'legacy-proxy', label: 'legacy-proxy' }]);
|
||||
});
|
||||
});
|
||||
|
||||
it('defaults missing option values to empty strings', async () => {
|
||||
const datasource = buildDatasource({
|
||||
getAllGroups: jest.fn().mockResolvedValue([{ name: 'group-a' }, { name: '' }, {}]),
|
||||
getAllHosts: jest.fn().mockResolvedValue([{ name: 'host-a' }, { name: '' }, {}]),
|
||||
getAllApps: jest.fn().mockResolvedValue([{ name: 'app-a' }, { name: '' }, {}]),
|
||||
getProxies: jest.fn().mockResolvedValue([{ name: '' }, { host: '' }, { name: 'proxy-a' }]),
|
||||
});
|
||||
|
||||
render(<ProblemsQueryEditor query={baseQuery} datasource={datasource as any} onChange={jest.fn()} />);
|
||||
|
||||
await waitFor(() => {
|
||||
const groupCall = metricPickerSpy.mock.calls
|
||||
.map((call) => call[0])
|
||||
.find((props) => props?.placeholder === 'Group name' && props?.options);
|
||||
const hostCall = metricPickerSpy.mock.calls
|
||||
.map((call) => call[0])
|
||||
.find((props) => props?.placeholder === 'Host name' && props?.options);
|
||||
const appCall = metricPickerSpy.mock.calls
|
||||
.map((call) => call[0])
|
||||
.find((props) => props?.placeholder === 'Application name' && props?.options);
|
||||
const proxyCall = metricPickerSpy.mock.calls
|
||||
.map((call) => call[0])
|
||||
.find((props) => props?.placeholder === 'Proxy name' && props?.options);
|
||||
|
||||
const hasValidValues = (options: any[]) =>
|
||||
options.every(
|
||||
(option) => option.value !== undefined && (option.label !== undefined || option.value === '/.*/')
|
||||
);
|
||||
|
||||
expect(hasValidValues(groupCall.options)).toBe(true);
|
||||
expect(hasValidValues(hostCall.options)).toBe(true);
|
||||
expect(hasValidValues(appCall.options)).toBe(true);
|
||||
expect(hasValidValues(proxyCall.options)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -43,8 +43,8 @@ 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,
|
||||
value: group.name ?? '',
|
||||
label: group.name ?? '',
|
||||
}));
|
||||
options.unshift(...getVariableOptions());
|
||||
return options;
|
||||
@@ -58,8 +58,8 @@ export const ProblemsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
const loadHostOptions = async (group: string) => {
|
||||
const hosts = await datasource.zabbix.getAllHosts(group);
|
||||
let options: Array<ComboboxOption<string>> = hosts?.map((host) => ({
|
||||
value: host.name,
|
||||
label: host.name,
|
||||
value: host.name ?? '',
|
||||
label: host.name ?? '',
|
||||
}));
|
||||
options = _.uniqBy(options, (o) => o.value);
|
||||
options.unshift({ value: '/.*/' });
|
||||
@@ -75,8 +75,8 @@ export const ProblemsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
const loadAppOptions = async (group: string, host: string) => {
|
||||
const apps = await datasource.zabbix.getAllApps(group, host);
|
||||
let options: Array<ComboboxOption<string>> = apps?.map((app) => ({
|
||||
value: app.name,
|
||||
label: app.name,
|
||||
value: app.name ?? '',
|
||||
label: app.name ?? '',
|
||||
}));
|
||||
options = _.uniqBy(options, (o) => o.value);
|
||||
options.unshift(...getVariableOptions());
|
||||
@@ -91,8 +91,8 @@ export const ProblemsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
const loadProxyOptions = async () => {
|
||||
const proxies = await datasource.zabbix.getProxies();
|
||||
const options = proxies?.map((proxy) => ({
|
||||
value: proxy.host,
|
||||
label: proxy.host,
|
||||
value: (!!proxy.host ? proxy.host : proxy.name) ?? '',
|
||||
label: (!!proxy.host ? proxy.host : proxy.name) ?? '',
|
||||
}));
|
||||
options.unshift(...getVariableOptions());
|
||||
return options;
|
||||
|
||||
@@ -51,7 +51,7 @@ export class ZabbixVariableQueryEditor extends PureComponent<VariableQueryProps,
|
||||
}
|
||||
|
||||
getSelectedQueryType(queryType: VariableQueryTypes) {
|
||||
return this.queryTypes.find((q) => q.value === queryType);
|
||||
return this.queryTypes.find((q) => q.value === queryType) ?? this.defaults.selectedQueryType;
|
||||
}
|
||||
|
||||
handleQueryUpdate = (evt: React.ChangeEvent<HTMLInputElement>, prop: string) => {
|
||||
|
||||
@@ -14,6 +14,7 @@ export const MODE_ITEMID = '3';
|
||||
export const MODE_TRIGGERS = '4';
|
||||
export const MODE_PROBLEMS = '5';
|
||||
export const MODE_MACROS = '6';
|
||||
export const MODE_PROBLEMS_ALERTING = '7';
|
||||
|
||||
// Triggers severity
|
||||
export const SEV_NOT_CLASSIFIED = 0;
|
||||
|
||||
@@ -900,7 +900,7 @@ export class ZabbixDatasource extends DataSourceWithBackend<ZabbixMetricsQuery,
|
||||
return false;
|
||||
}
|
||||
|
||||
return target.queryType === c.MODE_METRICS || target.queryType === c.MODE_ITEMID;
|
||||
return target.queryType === c.MODE_METRICS || target.queryType === c.MODE_ITEMID || target.queryType === c.MODE_PROBLEMS_ALERTING;
|
||||
};
|
||||
|
||||
isDBConnectionTarget = (target: any): boolean => {
|
||||
|
||||
@@ -9,7 +9,8 @@ export type QueryType =
|
||||
| typeof c.MODE_ITEMID
|
||||
| typeof c.MODE_TRIGGERS
|
||||
| typeof c.MODE_PROBLEMS
|
||||
| typeof c.MODE_MACROS;
|
||||
| typeof c.MODE_MACROS
|
||||
| typeof c.MODE_PROBLEMS_ALERTING;
|
||||
|
||||
type BaseQuery = { queryType: QueryType; datasourceId: number } & DataQuery;
|
||||
|
||||
|
||||
@@ -296,7 +296,6 @@ export class Zabbix implements ZabbixConnector {
|
||||
}
|
||||
|
||||
getAllGroups() {
|
||||
console.log(this.zabbixAPI.getGroups());
|
||||
return this.zabbixAPI.getGroups();
|
||||
}
|
||||
|
||||
|
||||
253
src/panel-triggers/components/Problems/Problems.test.tsx
Normal file
253
src/panel-triggers/components/Problems/Problems.test.tsx
Normal file
@@ -0,0 +1,253 @@
|
||||
import React from 'react';
|
||||
import { render, screen, within } from '@testing-library/react';
|
||||
import { ProblemList, ProblemListProps } from './Problems';
|
||||
import { ProblemDTO, ZBXAlert, ZBXEvent } from '../../../datasource/types';
|
||||
import { ProblemsPanelOptions, DEFAULT_SEVERITY } from '../../types';
|
||||
import { APIExecuteScriptResponse, ZBXScript } from '../../../datasource/zabbix/connectors/zabbix_api/types';
|
||||
|
||||
// Mock @grafana/runtime
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
reportInteraction: jest.fn(),
|
||||
config: {},
|
||||
}));
|
||||
|
||||
describe('ProblemList', () => {
|
||||
const mockGetProblemEvents = jest.fn<Promise<ZBXEvent[]>, [ProblemDTO]>();
|
||||
const mockGetProblemAlerts = jest.fn<Promise<ZBXAlert[]>, [ProblemDTO]>();
|
||||
const mockGetScripts = jest.fn<Promise<ZBXScript[]>, [ProblemDTO]>();
|
||||
const mockOnExecuteScript = jest.fn<Promise<APIExecuteScriptResponse>, [ProblemDTO, string, string]>();
|
||||
const mockOnProblemAck = jest.fn();
|
||||
const mockOnTagClick = jest.fn();
|
||||
const mockOnPageSizeChange = jest.fn();
|
||||
const mockOnColumnResize = jest.fn();
|
||||
|
||||
const defaultPanelOptions: ProblemsPanelOptions = {
|
||||
datasources: [],
|
||||
fontSize: '100%',
|
||||
layout: 'table',
|
||||
schemaVersion: 1,
|
||||
targets: [],
|
||||
hostField: true,
|
||||
hostTechNameField: false,
|
||||
hostGroups: false,
|
||||
hostProxy: false,
|
||||
severityField: true,
|
||||
statusField: true,
|
||||
statusIcon: true,
|
||||
opdataField: false,
|
||||
ackField: true,
|
||||
showTags: true,
|
||||
showDatasourceName: false,
|
||||
ageField: true,
|
||||
customLastChangeFormat: false,
|
||||
lastChangeFormat: '',
|
||||
highlightNewEvents: false,
|
||||
highlightNewerThan: '',
|
||||
markAckEvents: false,
|
||||
ackEventColor: 'rgb(56, 219, 156)',
|
||||
okEventColor: 'rgb(56, 189, 113)',
|
||||
triggerSeverity: DEFAULT_SEVERITY,
|
||||
problemTimeline: false,
|
||||
allowDangerousHTML: false,
|
||||
resizedColumns: [],
|
||||
};
|
||||
|
||||
const createMockProblem = (id: string, timestamp: number): ProblemDTO => ({
|
||||
eventid: id,
|
||||
name: `Test Problem ${id}`,
|
||||
acknowledged: '0',
|
||||
value: '1',
|
||||
severity: '3',
|
||||
priority: '3',
|
||||
host: `Test Host ${id}`,
|
||||
hostTechName: `host-${id}`,
|
||||
hostInMaintenance: false,
|
||||
groups: [],
|
||||
proxy: '',
|
||||
tags: [],
|
||||
url: '',
|
||||
opdata: '',
|
||||
datasource: { type: 'alexanderzobnin-zabbix-datasource', uid: 'test-ds' },
|
||||
timestamp,
|
||||
acknowledges: [],
|
||||
suppressed: '0',
|
||||
suppression_data: [],
|
||||
comments: '',
|
||||
});
|
||||
|
||||
const defaultProps: ProblemListProps = {
|
||||
problems: [],
|
||||
panelOptions: defaultPanelOptions,
|
||||
loading: false,
|
||||
pageSize: 10,
|
||||
fontSize: 100,
|
||||
panelId: 1,
|
||||
getProblemEvents: mockGetProblemEvents,
|
||||
getProblemAlerts: mockGetProblemAlerts,
|
||||
getScripts: mockGetScripts,
|
||||
onExecuteScript: mockOnExecuteScript,
|
||||
onProblemAck: mockOnProblemAck,
|
||||
onTagClick: mockOnTagClick,
|
||||
onPageSizeChange: mockOnPageSizeChange,
|
||||
onColumnResize: mockOnColumnResize,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Age Field', () => {
|
||||
it('should render the age column header when ageField is enabled', () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
panelOptions: { ...defaultPanelOptions, ageField: true },
|
||||
problems: [createMockProblem('1', 1609459200)], // 2021-01-01 00:00:00 UTC
|
||||
};
|
||||
|
||||
render(<ProblemList {...props} />);
|
||||
|
||||
const table = screen.getByRole('table');
|
||||
const headers = within(table).getAllByRole('columnheader');
|
||||
const ageHeader = headers.find((header) => header.textContent === 'Age');
|
||||
|
||||
expect(ageHeader).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render the age column header when ageField is disabled', () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
panelOptions: { ...defaultPanelOptions, ageField: false },
|
||||
problems: [createMockProblem('1', 1609459200)],
|
||||
};
|
||||
|
||||
render(<ProblemList {...props} />);
|
||||
|
||||
const table = screen.getByRole('table');
|
||||
const headers = within(table).getAllByRole('columnheader');
|
||||
const ageHeader = headers.find((header) => header.textContent === 'Age');
|
||||
|
||||
expect(ageHeader).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Status Field', () => {
|
||||
it('should render the status column header when statusField is enabled', () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
panelOptions: { ...defaultPanelOptions, statusField: true },
|
||||
problems: [createMockProblem('1', 1609459200)],
|
||||
};
|
||||
|
||||
render(<ProblemList {...props} />);
|
||||
|
||||
const table = screen.getByRole('table');
|
||||
const headers = within(table).getAllByRole('columnheader');
|
||||
const statusHeader = headers.find((header) => header.textContent === 'Status');
|
||||
|
||||
expect(statusHeader).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render the status column header when statusField is disabled', () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
panelOptions: { ...defaultPanelOptions, statusField: false },
|
||||
problems: [createMockProblem('1', 1609459200)],
|
||||
};
|
||||
|
||||
render(<ProblemList {...props} />);
|
||||
|
||||
const table = screen.getByRole('table');
|
||||
const headers = within(table).getAllByRole('columnheader');
|
||||
const statusHeader = headers.find((header) => header.textContent === 'Status');
|
||||
|
||||
expect(statusHeader).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Severity Field', () => {
|
||||
it('should render the severity column header when severityField is enabled', () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
panelOptions: { ...defaultPanelOptions, severityField: true },
|
||||
problems: [createMockProblem('1', 1609459200)],
|
||||
};
|
||||
|
||||
render(<ProblemList {...props} />);
|
||||
|
||||
const table = screen.getByRole('table');
|
||||
const headers = within(table).getAllByRole('columnheader');
|
||||
const severityHeader = headers.find((header) => header.textContent === 'Severity');
|
||||
|
||||
expect(severityHeader).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render the severity column header when severityField is disabled', () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
panelOptions: { ...defaultPanelOptions, severityField: false },
|
||||
problems: [createMockProblem('1', 1609459200)],
|
||||
};
|
||||
|
||||
render(<ProblemList {...props} />);
|
||||
|
||||
const table = screen.getByRole('table');
|
||||
const headers = within(table).getAllByRole('columnheader');
|
||||
const severityHeader = headers.find((header) => header.textContent === 'Severity');
|
||||
|
||||
expect(severityHeader).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Ack Field', () => {
|
||||
it('should render the ack column header when ackField is enabled', () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
panelOptions: { ...defaultPanelOptions, ackField: true },
|
||||
problems: [createMockProblem('1', 1609459200)],
|
||||
};
|
||||
|
||||
render(<ProblemList {...props} />);
|
||||
|
||||
const table = screen.getByRole('table');
|
||||
const headers = within(table).getAllByRole('columnheader');
|
||||
const ackHeader = headers.find((header) => header.textContent === 'Ack');
|
||||
|
||||
expect(ackHeader).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render the ack column header when ackField is disabled', () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
panelOptions: { ...defaultPanelOptions, ackField: false },
|
||||
problems: [createMockProblem('1', 1609459200)],
|
||||
};
|
||||
|
||||
render(<ProblemList {...props} />);
|
||||
|
||||
const table = screen.getByRole('table');
|
||||
const headers = within(table).getAllByRole('columnheader');
|
||||
const ackHeader = headers.find((header) => header.textContent === 'Ack');
|
||||
|
||||
expect(ackHeader).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Datasource Field', () => {
|
||||
it('should not render the datasource column header when showDatasourceName is disabled', () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
panelOptions: { ...defaultPanelOptions, showDatasourceName: false },
|
||||
problems: [createMockProblem('1', 1609459200)],
|
||||
};
|
||||
|
||||
render(<ProblemList {...props} />);
|
||||
|
||||
const table = screen.getByRole('table');
|
||||
const headers = within(table).getAllByRole('columnheader');
|
||||
const datasourceHeader = headers.find((header) => header.textContent === 'Datasource');
|
||||
|
||||
expect(datasourceHeader).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -23,8 +23,9 @@ import {
|
||||
getPaginationRowModel,
|
||||
useReactTable,
|
||||
} from '@tanstack/react-table';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { getDataSourceSrv, reportInteraction } from '@grafana/runtime';
|
||||
import { ProblemDetails } from './ProblemDetails';
|
||||
import { capitalizeFirstLetter, parseCustomTagColumns } from './utils';
|
||||
|
||||
export interface ProblemListProps {
|
||||
problems: ProblemDTO[];
|
||||
@@ -47,6 +48,33 @@ export interface ProblemListProps {
|
||||
|
||||
const columnHelper = createColumnHelper<ProblemDTO>();
|
||||
|
||||
const buildCustomTagColumns = (customTagColumns?: string) => {
|
||||
const tagNames = parseCustomTagColumns(customTagColumns);
|
||||
|
||||
return tagNames.map((tagName) =>
|
||||
columnHelper.accessor(
|
||||
(row) => {
|
||||
const tags = row.tags ?? [];
|
||||
const values = tags
|
||||
.filter((t) => t.tag === tagName)
|
||||
.map((t) => t.value)
|
||||
.filter(Boolean);
|
||||
|
||||
return values.length ? values.join(', ') : '';
|
||||
},
|
||||
{
|
||||
id: `problem-tag_${tagName}`,
|
||||
header: capitalizeFirstLetter(tagName),
|
||||
size: 150,
|
||||
meta: {
|
||||
className: `problem-tag_${tagName}`,
|
||||
},
|
||||
cell: ({ getValue }) => <span>{getValue() as string}</span>,
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export const ProblemList = (props: ProblemListProps) => {
|
||||
const {
|
||||
pageSize,
|
||||
@@ -72,6 +100,8 @@ export const ProblemList = (props: ProblemListProps) => {
|
||||
const columns = useMemo(() => {
|
||||
const highlightNewerThan = panelOptions.highlightNewEvents && panelOptions.highlightNewerThan;
|
||||
|
||||
const customTagColumns = buildCustomTagColumns(panelOptions.customTagColumns);
|
||||
|
||||
return [
|
||||
columnHelper.accessor('host', {
|
||||
header: 'Host',
|
||||
@@ -146,6 +176,7 @@ export const ProblemList = (props: ProblemListProps) => {
|
||||
size: 70,
|
||||
cell: ({ cell }) => <AckCell acknowledges={cell.row.original.acknowledges} />,
|
||||
}),
|
||||
...customTagColumns,
|
||||
columnHelper.accessor('tags', {
|
||||
header: 'Tags',
|
||||
size: 150,
|
||||
@@ -160,6 +191,19 @@ export const ProblemList = (props: ProblemListProps) => {
|
||||
/>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor('datasource', {
|
||||
header: 'Datasource',
|
||||
size: 120,
|
||||
cell: ({ cell }) => {
|
||||
const datasource = cell.getValue();
|
||||
let dsName: string = datasource as string;
|
||||
if ((datasource as DataSourceRef)?.uid) {
|
||||
const dsInstance = getDataSourceSrv().getInstanceSettings((datasource as DataSourceRef).uid);
|
||||
dsName = dsInstance?.name || dsName;
|
||||
}
|
||||
return <span>{dsName}</span>;
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor('timestamp', {
|
||||
id: 'age',
|
||||
header: 'Age',
|
||||
@@ -167,7 +211,7 @@ export const ProblemList = (props: ProblemListProps) => {
|
||||
meta: {
|
||||
className: 'problem-age',
|
||||
},
|
||||
cell: ({ cell }) => moment.unix(cell.row.original.timestamp),
|
||||
cell: ({ cell }) => <span>{moment.unix(cell.row.original.timestamp).fromNow(true)}</span>,
|
||||
}),
|
||||
columnHelper.accessor('timestamp', {
|
||||
id: 'lastchange',
|
||||
@@ -237,6 +281,27 @@ export const ProblemList = (props: ProblemListProps) => {
|
||||
}));
|
||||
}, [effectivePageSize]);
|
||||
|
||||
// Column visibility state derived from panelOptions
|
||||
const columnVisibility = useMemo(
|
||||
() => ({
|
||||
host: panelOptions.hostField,
|
||||
hostTechName: panelOptions.hostTechNameField,
|
||||
groups: panelOptions.hostGroups,
|
||||
proxy: panelOptions.hostProxy,
|
||||
priority: panelOptions.severityField,
|
||||
statusIcon: panelOptions.statusIcon,
|
||||
value: panelOptions.statusField,
|
||||
opdata: panelOptions.opdataField,
|
||||
acknowledged: panelOptions.ackField,
|
||||
tags: panelOptions.showTags,
|
||||
datasource: panelOptions.showDatasourceName,
|
||||
age: panelOptions.ageField,
|
||||
}),
|
||||
[panelOptions]
|
||||
);
|
||||
|
||||
// https://github.com/TanStack/table/issues/6137
|
||||
// eslint-disable-next-line react-hooks/incompatible-library -- TanStack Table's useReactTable returns functions that cannot be memoized
|
||||
const table = useReactTable({
|
||||
data: problems,
|
||||
columns,
|
||||
@@ -245,25 +310,12 @@ export const ProblemList = (props: ProblemListProps) => {
|
||||
state: {
|
||||
columnSizing,
|
||||
pagination,
|
||||
columnVisibility,
|
||||
},
|
||||
onPaginationChange: setPagination,
|
||||
meta: {
|
||||
panelOptions,
|
||||
},
|
||||
initialState: {
|
||||
columnVisibility: {
|
||||
host: panelOptions.hostField,
|
||||
hostTechName: panelOptions.hostTechNameField,
|
||||
groups: panelOptions.hostGroups,
|
||||
proxy: panelOptions.hostProxy,
|
||||
severity: panelOptions.severityField,
|
||||
statusIcon: panelOptions.statusIcon,
|
||||
opdata: panelOptions.opdataField,
|
||||
ack: panelOptions.ackField,
|
||||
tags: panelOptions.showTags,
|
||||
age: panelOptions.ageField,
|
||||
},
|
||||
},
|
||||
onColumnSizingChange: (updater) => {
|
||||
const newSizing = typeof updater === 'function' ? updater(columnSizing) : updater;
|
||||
setColumnSizing(newSizing);
|
||||
|
||||
38
src/panel-triggers/components/Problems/utils.test.ts
Normal file
38
src/panel-triggers/components/Problems/utils.test.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { capitalizeFirstLetter, parseCustomTagColumns } from './utils';
|
||||
|
||||
describe('capitalizeFirstLetter', () => {
|
||||
it('capitalizes first letter and lowercases the rest', () => {
|
||||
expect(capitalizeFirstLetter('zabbixgrafana')).toBe('Zabbixgrafana');
|
||||
expect(capitalizeFirstLetter('ZABBIXGRAFANA')).toBe('Zabbixgrafana');
|
||||
expect(capitalizeFirstLetter('zAbBiXgRaFaNa')).toBe('Zabbixgrafana');
|
||||
});
|
||||
|
||||
it('returns empty string for empty input', () => {
|
||||
expect(capitalizeFirstLetter('')).toBe('');
|
||||
});
|
||||
|
||||
it('handles single-character strings', () => {
|
||||
expect(capitalizeFirstLetter('a')).toBe('A');
|
||||
expect(capitalizeFirstLetter('A')).toBe('A');
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseCustomTagColumns', () => {
|
||||
it('returns empty array for undefined or empty input', () => {
|
||||
expect(parseCustomTagColumns(undefined)).toEqual([]);
|
||||
expect(parseCustomTagColumns('')).toEqual([]);
|
||||
expect(parseCustomTagColumns(' ')).toEqual([]);
|
||||
});
|
||||
|
||||
it('splits comma-separated values and trims whitespace', () => {
|
||||
expect(parseCustomTagColumns('env, region ,service')).toEqual(['env', 'region', 'service']);
|
||||
});
|
||||
|
||||
it('filters out empty values', () => {
|
||||
expect(parseCustomTagColumns('env,, ,region,')).toEqual(['env', 'region']);
|
||||
});
|
||||
|
||||
it('preserves order', () => {
|
||||
expect(parseCustomTagColumns('a,b,c')).toEqual(['a', 'b', 'c']);
|
||||
});
|
||||
});
|
||||
12
src/panel-triggers/components/Problems/utils.ts
Normal file
12
src/panel-triggers/components/Problems/utils.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export const capitalizeFirstLetter = (str: string): string => str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
||||
|
||||
export const parseCustomTagColumns = (customTagColumns?: string): string[] => {
|
||||
if (!customTagColumns) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return customTagColumns
|
||||
.split(',')
|
||||
.map((tagName) => tagName.trim())
|
||||
.filter(Boolean);
|
||||
};
|
||||
@@ -220,6 +220,17 @@ export const plugin = new PanelPlugin<ProblemsPanelOptions, {}>(ProblemsPanel)
|
||||
name: 'Datasource name',
|
||||
defaultValue: defaultPanelOptions.showDatasourceName,
|
||||
category: ['Fields'],
|
||||
})
|
||||
// Select tag name to display as column
|
||||
.addTextInput({
|
||||
path: 'customTagColumns',
|
||||
name: 'Tags to columns',
|
||||
defaultValue: '',
|
||||
description: 'Comma-separated list of tag names to display as columns (e.g., component, scope, environment)',
|
||||
settings: {
|
||||
placeholder: 'component, scope, target',
|
||||
},
|
||||
category: ['Fields'],
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -42,6 +42,8 @@ export interface ProblemsPanelOptions {
|
||||
okEventColor: TriggerColor;
|
||||
ackEventColor: TriggerColor;
|
||||
markAckEvents?: boolean;
|
||||
// Custom tag names to display as column
|
||||
customTagColumns?: string;
|
||||
}
|
||||
|
||||
export const DEFAULT_SEVERITY: TriggerSeverity[] = [
|
||||
|
||||
Reference in New Issue
Block a user