Compare commits

...

12 commits

Author SHA1 Message Date
41e4c4da1f feat: add weather sensor support and fix device status mapping
This commit introduces support for provisioning weather sensors with geo-coordinates
via user macros and fixes a critical mapping bug in device status.

Changes:
- fix: Corrected DeviceStatus enum mapping (0=ENABLED, 1=DISABLED).
- feat: Added 'status' field to CreateTemplateItem input in GraphQL schema.
- feat: Enabled user macro assignment during host and template creation/import.
- feat: Added regression tests for user macro assignment and HTTP agent URL support.
- docs: Updated cookbook and sample queries to use {$LAT} and {$LON} macros.
- test: Added unit tests for macro assignment in HostImporter and TemplateImporter.
- chore: Regenerated GraphQL types.
2026-02-01 16:23:35 +01:00
5da4a17e36 feat: implement weather sensor extension and enhance device interfaces
This change introduces the Weather Sensor device type which retrieves data from public APIs, and enhances the core Host/Device interfaces to provide consistent access to inventory and items across all specialized device types. It also improves search logic and fixes several bugs identified during implementation.

- Weather Sensor Extension: Added schema and recipe for a device retrieving weather data via Zabbix HTTP agent items.

- Interface Enhancements: Added inventory and items fields to Host and Device interfaces to ensure all device specialized types have consistent access to monitoring and inventory data.

- Search Logic Improvements: Enhanced ParsedArgs to support searchByAny and technical name (host) searches when a name pattern is provided.

- Bug Fixes:

  - Fixed getLocations argument order in the Zabbix API datasource.

  - Implemented deduplication for groupids and templateids in HostImporter to prevent Zabbix duplicate value errors.

  - Added missing url field to CreateTemplateItem for HTTP Agent item support.

- Testing:

  - Extended the regression test suite with 4 new automated checks covering the fixed bugs.

  - Updated Jest tests to accommodate the improved search parameters.

- Documentation: Updated cookbook and test specifications to reflect new features and regression testing obligations.
2026-02-01 06:56:23 +01:00
b84e4c0734 feat: implement comprehensive testing framework and regression suite
- Established a centralized test specification in docs/tests.md that defines test categories, cases, and a coverage checklist to ensure consistent quality and maintainability across the project.

- Implemented RegressionTestExecutor for managing automated regression tests on a live Zabbix system.

- Updated GraphQL schema and resolvers with a generic runAllRegressionTests mutation.

- Enhanced MCP integration with new operation files and detailed documentation for AI-driven automation.

- Updated README.md and How-To guides (Cookbook, Maintenance, MCP) to reflect the new testing framework and MCP capabilities.

- Verified all changes with a full Jest suite (74 tests) and live end-to-end smoketests.
2026-02-01 05:05:55 +01:00
ef7afe65ab feat: implement template cloning and extended item data retrieval
- Extend Template and ZabbixItem types in GraphQL schema to support full item hierarchy and cloning.

- Update ZabbixQueryTemplatesRequest in src/datasources/zabbix-templates.ts to fetch comprehensive item configurations (type, status, history, delay, units, preprocessing, tags).

- Implement raw value resolvers for ZabbixItem.type_int and ZabbixItem.status_int in src/api/resolvers.ts.

- Add new MCP operations: mcp/operations/getTemplates.graphql and mcp/operations/importTemplates.graphql for template management via AI agents.

- Add 'Cloning a Template with Items' recipe to docs/howtos/cookbook.md.

- Update src/test/template_query.test.ts to ensure compatibility with extended datasource output.
2026-01-31 12:15:18 +01:00
67357d0bc3 feat: implement smoketest and extend host provisioning with template linking
- Add runSmoketest mutation to automate end-to-end verification.

- Add SmoketestExecutor and HostDeleter to support automated testing and cleanup.

- Extend createHost and importHosts to allow linking templates by name or ID.

- Update docs/howtos/cookbook.md with new recipe steps and AI/MCP guidance.

- Update .junie/guidelines.md with new verification and deployment standards.

- Add src/test/template_link.test.ts and update existing tests to cover new functionality.

- Regenerate GraphQL types to match schema updates.
2026-01-31 11:46:02 +01:00
b56255ffaa feat: add Zabbix 7.4 documentation samples and importHostGroups MCP tool
This commit introduces a comprehensive set of GraphQL query and mutation samples based on the official Zabbix 7.4 API documentation, along with testing and automation improvements.

Changes:

- Documentation:

  - Added 21 GraphQL sample files in docs/queries/from_zabbix_docs/ covering various Zabbix API operations.

  - Updated docs/howtos/cookbook.md with a new recipe for executing these documentation samples.

- AI & MCP:

  - Added mcp/operations/importHostGroups.graphql to enable host group import via MCP tools.

- Testing:

  - Added src/test/zabbix_docs_samples.test.ts to automatically validate all documentation samples against the GraphQL schema.
2026-01-31 10:52:56 +01:00
9a79fc8e4c feat: add MCP tools, refined recipe steps for schema extension verification and update Docker requirements
- Add mcp/operations/importHosts.graphql for flexible, name-based host provisioning.
- Split schema verification operations into createVerificationHost.graphql and verifySchemaExtension.graphql to support Apollo MCP server requirements.
- Improve mcp/operations/createHost.graphql with better type mapping and error message retrieval.
- Fix syntax error in importHosts.graphql by replacing unsupported triple-quote docstrings with standard comments.
- Add src/test/mcp_operations_validation.test.ts to automatically validate MCP operations against the GraphQL schema.
- Update docs/howtos/cookbook.md with 🤖 AI/MCP guidance and refined recipe steps for schema extension verification.
- Update README.md, docs/howtos/mcp.md, and .junie/guidelines.md to:
  - Add Docker (v27+) and Docker Compose (v2.29+) version requirements to the Tech Stack.
  - Enforce the use of docker compose (without hyphen) for all commands in the Environment guidelines.
  - Replace legacy docker-compose references across all documentation with the new command format.
2026-01-31 03:31:40 +01:00
a9940063e9 docs: enhance cookbook recipes with verification steps and templates
This commit refines the project documentation by adding result verification steps to all recipes in the cookbook and formalizing documentation standards in the project guidelines.

Changes:
- docs/howtos/cookbook.md:
  - Added 'Verify' steps to all recipes using GraphQL queries or agent checks.
  - Enhanced 'Extending Schema with a New Device Type' recipe:
    - Updated Step 1 with a realistic schema using 'DistanceTrackerDevice' implementing 'Host' and 'Device' interfaces.
    - Added mandatory advice for new device types to implement 'Host' and 'Device'.
    - Referenced the existing 'location_tracker_devices.graphql' as a prepared real-world sample.
    - Split Step 3 into Method A (Manual Creation) and Method B (Automated Import).
    - Expanded Step 4 with a host creation step (using importHosts) and a filtered allDevices query using tag_deviceType.
  - Standardized icons for preparation (🛠️), configuration (⚙️), execution (🚀), and verification ().
- .junie/guidelines.md:
  - Added 'Documentation Templates' section defining a formal Recipe Template.
  - Standardized icons and step naming to maintain visual consistency across all guides.
  - Added 'Review & Approval' rule to prevent automatic commits without user review.
- readme.improvement.plan.md:
  - Added and completed 'Priority 7: Standardization & Verification' tasks.
  - Updated implementation order to reflect the new standardization phase.
2026-01-31 02:11:15 +01:00
bbf7357e93 docs: enhance documentation and automate MCP setup
This commit introduces several improvements to the project's documentation,
roadmap, and AI agent integration (MCP).

Key changes:
- Created and styled roadmap.md to track project milestones and future plans.
- Updated .junie/guidelines.md with strict documentation style standards.
- Automated GraphQL schema concatenation for the MCP server using a schema-gen init-container.
- Updated MCP setup recipes in cookbook.md and mcp.md to reflect the new automation.
- Added .ai/mcp/mcp.json for connecting to existing MCP services via HTTP.
- Improved development workflow by updating package.json to watch .graphql files.
- Cleaned up the root directory by moving schema.graphql to .gitignore and removing redundant files.
- Standardized visual style and formatting across all markdown files.
2026-01-30 19:04:05 +01:00
91a1523d71 docs: complete documentation refactoring and structure optimization
This commit finalizes the documentation improvement plan by:
- Centralizing reference material in README.md.
- Creating a dedicated Technical Maintenance guide (docs/howtos/maintenance.md).
- Creating a categorized Sample Queries & Mutations overview (docs/queries/README.md).
- Eliminating redundant information across the doc set (DRY principle).
- Optimizing cross-references between reference documentation and the Cookbook.
- Updating the improvement plan to reflect all tasks as completed.
2026-01-30 15:08:19 +01:00
a01bfabfba docs: refactor documentation and upgrade to Node.js 24
This commit upgrades the project to Node.js 24 (LTS) and performs a major refactoring of the documentation to support both advanced users and AI-based automation (MCP).

Changes:
- Environment & CI/CD:
  - Set Node.js version to >=24 in package.json and .nvmrc.
  - Updated Dockerfile to use Node 24 base image.
  - Updated @types/node to ^24.10.9.
- Documentation:
  - Refactored README.md with comprehensive technical reference, configuration details, and Zabbix-to-GraphQL mapping.
  - Created docs/howtos/cookbook.md with practical recipes for common tasks and AI test generation.
  - Updated docs/howtos/mcp.md to emphasize GraphQL's advantages for AI agents and Model Context Protocol.
  - Added readme.improvement.plan.md to track documentation evolution.
  - Enhanced all how-to guides with improved cross-references and up-to-date information.
- Guidelines:
  - Updated .junie/guidelines.md with Node 24 requirements and enhanced commit message standards (Conventional Commits 1.0.0).
- Infrastructure & Code:
  - Updated docker-compose.yml with Apollo MCP server integration.
  - Refined configuration and schema handling in src/api/ and src/datasources/.
  - Synchronized generated TypeScript types with schema updates.
2026-01-30 14:35:31 +01:00
4ec61ffba1 chore: add MCP integration and refactor documentation into modular how-to guides
- Moved GraphQL query samples into a new `docs/queries` directory for better organization.
- Added new queries and mutations, including `createHost.graphql` and `GetApiVersion.graphql`.
- Introduced `mcp-config.yaml` and updated `docker-compose.yml` for MCP integration.
- Updated IntelliJ `.idea/workspace.xml` settings to reflect project changes.
- Added new how-to guides (`docs/howtos`) for permissions, tags, MCP integration, and schema usage.
- Enhanced tests by updating file paths and improving sample data locations.
- Refined permissions and host group structures in `zabbix-hostgroups.ts` and `resolvers.ts`.
2026-01-30 00:47:02 +01:00
108 changed files with 3750 additions and 355 deletions

7
.ai/mcp/mcp.json Normal file
View file

@ -0,0 +1,7 @@
{
"mcpServers": {
"zabbix-graphql": {
"url": "http://localhost:3000/mcp"
}
}
}

3
.gitignore vendored
View file

@ -130,3 +130,6 @@ dist
.pnp.*
.vscode/settings.json
# Generated schema for MCP
schema.graphql

View file

@ -4,7 +4,7 @@
<env name="ADDITIONAL_RESOLVERS" value="SinglePanelDevice,FourPanelDevice,DistanceTrackerDevice" />
<env name="ADDITIONAL_SCHEMAS" value="./schema/extensions/display_devices.graphql,./schema/extensions/location_tracker_devices.graphql,./schema/extensions/location_tracker_commons.graphql" />
<env name="DEBUG" value="device-control-center-api:*" />
<env name="ZABBIX_AUTH_TOKEN" value="$ZABBIX_AUTH_TOKEN_VCR_DEV$" />
<env name="ZABBIX_PRIVILEGE_ESCALATION_TOKEN" value="$ZABBIX_AUTH_TOKEN_VCR_DEV$" />
<env name="ZABBIX_BASE_URL" value="http://cockpit.vcr.develop.hilbigit.com/" />
<env name="ZABBIX_PERMISSION_TEMPLATE_GROUP_NAME_PREFIX" value="Permissions" />
<env name="ZABBIX_ROADWORK_BASE_GROUP" value="Roadwork/Devices" />

99
.idea/workspace.xml generated
View file

@ -4,9 +4,19 @@
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="d7a71994-2699-4ae4-9fd2-ee13b7f33d35" name="Changes" comment="chore: add tests for schema and API config mocking&#10;&#10;- Added unit tests for schema loader, mocking Config variables and resolvers.&#10;- Added unit tests for Zabbix API configuration, verifying constants derived from Config.&#10;- Mocked relevant modules and filesystem behaviors to enable isolated testing.&#10;- Optimized imports on all files and include this within a new .junie/guidelines.md file">
<list default="true" id="d7a71994-2699-4ae4-9fd2-ee13b7f33d35" name="Changes" comment="docs: refactor documentation and upgrade to Node.js 24&#10;&#10;This commit upgrades the project to Node.js 24 (LTS) and performs a major refactoring of the documentation to support both advanced users and AI-based automation (MCP).&#10;&#10;Changes:&#10;- Environment &amp; CI/CD:&#10; - Set Node.js version to &gt;=24 in package.json and .nvmrc.&#10; - Updated Dockerfile to use Node 24 base image.&#10; - Updated @types/node to ^24.10.9.&#10;- Documentation:&#10; - Refactored README.md with comprehensive technical reference, configuration details, and Zabbix-to-GraphQL mapping.&#10; - Created docs/howtos/cookbook.md with practical recipes for common tasks and AI test generation.&#10; - Updated docs/howtos/mcp.md to emphasize GraphQL's advantages for AI agents and Model Context Protocol.&#10; - Added readme.improvement.plan.md to track documentation evolution.&#10; - Enhanced all how-to guides with improved cross-references and up-to-date information.&#10;- Guidelines:&#10; - Updated .junie/guidelines.md with Node 24 requirements and enhanced commit message standards (Conventional Commits 1.0.0).&#10;- Infrastructure &amp; Code:&#10; - Updated docker-compose.yml with Apollo MCP server integration.&#10; - Refined configuration and schema handling in src/api/ and src/datasources/.&#10; - Synchronized generated TypeScript types with schema updates.">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.junie/guidelines.md" beforeDir="false" afterPath="$PROJECT_DIR$/.junie/guidelines.md" afterDir="false" />
<change beforePath="$PROJECT_DIR$/docs/howtos/cookbook.md" beforeDir="false" afterPath="$PROJECT_DIR$/docs/howtos/cookbook.md" afterDir="false" />
<change beforePath="$PROJECT_DIR$/docs/queries/sample_import_weather_sensor_template.graphql" beforeDir="false" afterPath="$PROJECT_DIR$/docs/queries/sample_import_weather_sensor_template.graphql" afterDir="false" />
<change beforePath="$PROJECT_DIR$/schema/mutations.graphql" beforeDir="false" afterPath="$PROJECT_DIR$/schema/mutations.graphql" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/datasources/zabbix-hosts.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/datasources/zabbix-hosts.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/execution/host_importer.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/execution/host_importer.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/execution/regression_test_executor.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/execution/regression_test_executor.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/execution/template_importer.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/execution/template_importer.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/model/model_enum_values.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/model/model_enum_values.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/schema/generated/graphql.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/schema/generated/graphql.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/test/host_importer.test.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/test/host_importer.test.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/test/template_importer.test.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/test/template_importer.test.ts" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -17,7 +27,7 @@
<execution />
</component>
<component name="EmbeddingIndexingInfo">
<option name="cachedIndexableFilesCount" value="70" />
<option name="cachedIndexableFilesCount" value="149" />
<option name="fileBasedEmbeddingIndicesEnabled" value="true" />
</component>
<component name="Git.Settings">
@ -29,6 +39,7 @@
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
<option name="RESET_MODE" value="HARD" />
<option name="SWAP_SIDES_IN_COMPARE_BRANCHES" value="true" />
<option name="UPDATE_TYPE" value="REBASE" />
</component>
<component name="GitRewordedCommitMessages">
<option name="commitMessagesMapping">
@ -42,7 +53,13 @@
</component>
<component name="McpProjectServerCommands">
<commands />
<urls />
<urls>
<McpServerConfigurationProperties>
<option name="allowedToolsNames" />
<option name="enabled" value="true" />
<option name="name" value="zabbix-graphql" />
</McpServerConfigurationProperties>
</urls>
</component>
<component name="ProblemsViewState">
<option name="selectedTabId" value="CurrentFile" />
@ -90,19 +107,19 @@
"npm.copy-schema.executor": "Run",
"npm.prod.executor": "Run",
"npm.test.executor": "Run",
"settings.editor.selected.configurable": "ml.llm.LLMConfigurable",
"settings.editor.splitter.proportion": "0.3839406",
"settings.editor.selected.configurable": "ml.llm.mcp",
"settings.editor.splitter.proportion": "0.28812414",
"to.speed.mode.migration.done": "true",
"ts.external.directory.path": "\\\\wsl.localhost\\Ubuntu\\home\\ahilbig\\git\\vcr\\zabbix-graphql-api\\node_modules\\typescript\\lib",
"vue.rearranger.settings.migration": "true"
}
}]]></component>
<component name="RecapSpentCounter">
<option name="endsOfQuotaMs" value="1768327208764" />
<option name="spentUsd" value="0.04010335" />
<option name="endsOfQuotaMs" value="1772398800000" />
<option name="spentUsd" value="0.01011225" />
</component>
<component name="RecapUselessUpdatesCounter">
<option name="suspendCountdown" value="0" />
<option name="suspendCountdown" value="8" />
</component>
<component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
@ -116,7 +133,7 @@
<recent name="\\wsl.localhost\Ubuntu\home\ahilbig\git\vcr\zabbix-graphql-api\schema" />
</key>
</component>
<component name="RunManager" selected="npm.test">
<component name="RunManager" selected="Node.js.index.ts">
<configuration name="copy-schema" type="js.build_tools.npm" temporary="true" nameIsGenerated="true">
<package-json value="$PROJECT_DIR$/package.json" />
<command value="run" />
@ -138,7 +155,7 @@
<env name="ADDITIONAL_RESOLVERS" value="SinglePanelDevice,FourPanelDevice,DistanceTrackerDevice" />
<env name="ADDITIONAL_SCHEMAS" value="./schema/extensions/display_devices.graphql,./schema/extensions/location_tracker_devices.graphql,./schema/extensions/location_tracker_commons.graphql" />
<env name="DEBUG" value="device-control-center-api:*" />
<env name="ZABBIX_AUTH_TOKEN" value="$ZABBIX_AUTH_TOKEN_VCR_DEV$" />
<env name="ZABBIX_PRIVILEGE_ESCALATION_TOKEN" value="$ZABBIX_AUTH_TOKEN_VCR_DEV$" />
<env name="ZABBIX_BASE_URL" value="http://cockpit.vcr.develop.hilbigit.com/" />
<env name="ZABBIX_PERMISSION_TEMPLATE_GROUP_NAME_PREFIX" value="Permissions" />
<env name="ZABBIX_ROADWORK_BASE_GROUP" value="Roadwork/Devices" />
@ -179,7 +196,7 @@
<component name="SharedIndexes">
<attachedChunks>
<set>
<option value="bundled-js-predefined-d6986cc7102b-9b0f141eb926-JavaScript-WS-253.29346.242" />
<option value="bundled-js-predefined-d6986cc7102b-9b0f141eb926-JavaScript-WS-253.30387.83" />
</set>
</attachedChunks>
</component>
@ -196,6 +213,12 @@
<workItem from="1768913192173" duration="14627000" />
<workItem from="1769095609607" duration="1390000" />
<workItem from="1769256682556" duration="8928000" />
<workItem from="1769699975260" duration="75000" />
<workItem from="1769700092648" duration="5212000" />
<workItem from="1769724930397" duration="16056000" />
<workItem from="1769789496322" duration="14281000" />
<workItem from="1769849767328" duration="18404000" />
<workItem from="1769955114366" duration="3276000" />
</task>
<task id="LOCAL-00001" summary="chore: Update IntelliJ workspace settings and add GitHub Actions workflow for Docker deployment">
<option name="closed" value="true" />
@ -373,7 +396,31 @@
<option name="project" value="LOCAL" />
<updated>1769582068426</updated>
</task>
<option name="localTasksCounter" value="23" />
<task id="LOCAL-00023" summary="chore: update `.idea/workspace.xml` and project guidelines &#10;&#10;- Refined IntelliJ IDEA settings in `.idea/workspace.xml`, including updates to `ProblemsViewState` and `PropertiesComponent` for project consistency. &#10;- Renamed and expanded `.junie/guidelines.md` to reflect updated best practices, project structure, and development workflows.">
<option name="closed" value="true" />
<created>1769597550348</created>
<option name="number" value="00023" />
<option name="presentableId" value="LOCAL-00023" />
<option name="project" value="LOCAL" />
<updated>1769597550348</updated>
</task>
<task id="LOCAL-00024" summary="chore: add MCP integration and refactor documentation into modular how-to guides &#10;&#10;- Moved GraphQL query samples into a new `docs/queries` directory for better organization. &#10;- Added new queries and mutations, including `createHost.graphql` and `GetApiVersion.graphql`. &#10;- Introduced `mcp-config.yaml` and updated `docker-compose.yml` for MCP integration. &#10;- Updated IntelliJ `.idea/workspace.xml` settings to reflect project changes. &#10;- Added new how-to guides (`docs/howtos`) for permissions, tags, MCP integration, and schema usage. &#10;- Enhanced tests by updating file paths and improving sample data locations. &#10;- Refined permissions and host group structures in `zabbix-hostgroups.ts` and `resolvers.ts`.">
<option name="closed" value="true" />
<created>1769730441542</created>
<option name="number" value="00024" />
<option name="presentableId" value="LOCAL-00024" />
<option name="project" value="LOCAL" />
<updated>1769730441542</updated>
</task>
<task id="LOCAL-00025" summary="docs: refactor documentation and upgrade to Node.js 24&#10;&#10;This commit upgrades the project to Node.js 24 (LTS) and performs a major refactoring of the documentation to support both advanced users and AI-based automation (MCP).&#10;&#10;Changes:&#10;- Environment &amp; CI/CD:&#10; - Set Node.js version to &gt;=24 in package.json and .nvmrc.&#10; - Updated Dockerfile to use Node 24 base image.&#10; - Updated @types/node to ^24.10.9.&#10;- Documentation:&#10; - Refactored README.md with comprehensive technical reference, configuration details, and Zabbix-to-GraphQL mapping.&#10; - Created docs/howtos/cookbook.md with practical recipes for common tasks and AI test generation.&#10; - Updated docs/howtos/mcp.md to emphasize GraphQL's advantages for AI agents and Model Context Protocol.&#10; - Added readme.improvement.plan.md to track documentation evolution.&#10; - Enhanced all how-to guides with improved cross-references and up-to-date information.&#10;- Guidelines:&#10; - Updated .junie/guidelines.md with Node 24 requirements and enhanced commit message standards (Conventional Commits 1.0.0).&#10;- Infrastructure &amp; Code:&#10; - Updated docker-compose.yml with Apollo MCP server integration.&#10; - Refined configuration and schema handling in src/api/ and src/datasources/.&#10; - Synchronized generated TypeScript types with schema updates.">
<option name="closed" value="true" />
<created>1769780136862</created>
<option name="number" value="00025" />
<option name="presentableId" value="LOCAL-00025" />
<option name="project" value="LOCAL" />
<updated>1769780136862</updated>
</task>
<option name="localTasksCounter" value="26" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
@ -384,17 +431,25 @@
<map>
<entry key="MAIN">
<value>
<State />
<State>
<option name="FILTERS">
<map>
<entry key="branch">
<value>
<list>
<option value="public/main" />
</list>
</value>
</entry>
</map>
</option>
</State>
</value>
</entry>
</map>
</option>
</component>
<component name="VcsManagerConfiguration">
<MESSAGE value="refactor!: Update Node.js version to 24.12.0, enhance GraphQL schema structure, and improve dynamic schema loading logic" />
<MESSAGE value="chore: Update IntelliJ workspace settings and add GitHub Actions workflow for Docker deployment" />
<MESSAGE value="chore: Add missing &quot;.js&quot; extensions to imports and improve Node.js compatibility for dynamic schema loading" />
<MESSAGE value="chore: Update IntelliJ workspace and fix Docker image tag in workflow" />
<MESSAGE value="chore: Add dry-run mode and configure logger for operation mode logging" />
<MESSAGE value="chore: Add test for Zabbix API arguments parsing" />
<MESSAGE value="chore: Replace copying of `schema.graphql` with `extensions` in Dockerfile" />
@ -416,7 +471,11 @@
<MESSAGE value="chore: add default filters for host and host group queries&#10;&#10;- Introduced `HOST_TYPE_FILTER_DEFAULT` and `HOST_GROUP_FILTER_DEFAULT` constants in the `Config` class.&#10;- Updated resolvers to use these defaults when `tag_hostType` or `search_name` arguments are not provided.&#10;- Added corresponding tests to verify default behavior in host and host group queries.&#10;- Added documentation on overriding 'HOST_GROUP_FILTER_DEFAULT' by explicitly setting the 'search_name' argument in the 'allHostGroups' query.&#10;- Explained the usage of the '*' wildcard in 'search_name' with a concrete example for subgroup matching." />
<MESSAGE value="chore: add tests for schema and API config mocking&#10;&#10;- Added unit tests for schema loader, mocking Config variables and resolvers.&#10;- Added unit tests for Zabbix API configuration, verifying constants derived from Config.&#10;- Mocked relevant modules and filesystem behaviors to enable isolated testing.&#10;- Optimized imports on all files" />
<MESSAGE value="chore: add tests for schema and API config mocking&#10;&#10;- Added unit tests for schema loader, mocking Config variables and resolvers.&#10;- Added unit tests for Zabbix API configuration, verifying constants derived from Config.&#10;- Mocked relevant modules and filesystem behaviors to enable isolated testing.&#10;- Optimized imports on all files and include this within a new .junie/guidelines.md file" />
<option name="LAST_COMMIT_MESSAGE" value="chore: add tests for schema and API config mocking&#10;&#10;- Added unit tests for schema loader, mocking Config variables and resolvers.&#10;- Added unit tests for Zabbix API configuration, verifying constants derived from Config.&#10;- Mocked relevant modules and filesystem behaviors to enable isolated testing.&#10;- Optimized imports on all files and include this within a new .junie/guidelines.md file" />
<MESSAGE value="chore: update `.idea/workspace.xml` and project guidelines &#10;&#10;- Refined IntelliJ IDEA settings in `.idea/workspace.xml`, including updates to `ProblemsViewState` and `PropertiesComponent` for project consistency. &#10;- Renamed and expanded `.junie/guidelines.md` to reflect updated best practices, project structure, and development workflows." />
<MESSAGE value="feat: add MCP integration and refactor documentation into modular how-to guides&quot; &#10;&#10;- Moved query files to a centralized `docs/queries/` directory for better organization. &#10;- Added example files under `mcp/operations` to support MCP integration. &#10;- Introduced a `docker-compose.yml` file for easier local development and deployment of services. &#10;- Updated tests to reflect the relocation of query files. &#10;- Enhanced IntelliJ `.idea/workspace.xml` settings for project consistency. " />
<MESSAGE value="chore: add MCP integration and refactor documentation into modular how-to guides &#10;&#10;- Moved GraphQL query samples into a new `docs/queries` directory for better organization. &#10;- Added new queries and mutations, including `createHost.graphql` and `GetApiVersion.graphql`. &#10;- Introduced `mcp-config.yaml` and updated `docker-compose.yml` for MCP integration. &#10;- Updated IntelliJ `.idea/workspace.xml` settings to reflect project changes. &#10;- Added new how-to guides (`docs/howtos`) for permissions, tags, MCP integration, and schema usage. &#10;- Enhanced tests by updating file paths and improving sample data locations. &#10;- Refined permissions and host group structures in `zabbix-hostgroups.ts` and `resolvers.ts`." />
<MESSAGE value="docs: refactor documentation and upgrade to Node.js 24&#10;&#10;This commit upgrades the project to Node.js 24 (LTS) and performs a major refactoring of the documentation to support both advanced users and AI-based automation (MCP).&#10;&#10;Changes:&#10;- Environment &amp; CI/CD:&#10; - Set Node.js version to &gt;=24 in package.json and .nvmrc.&#10; - Updated Dockerfile to use Node 24 base image.&#10; - Updated @types/node to ^24.10.9.&#10;- Documentation:&#10; - Refactored README.md with comprehensive technical reference, configuration details, and Zabbix-to-GraphQL mapping.&#10; - Created docs/howtos/cookbook.md with practical recipes for common tasks and AI test generation.&#10; - Updated docs/howtos/mcp.md to emphasize GraphQL's advantages for AI agents and Model Context Protocol.&#10; - Added readme.improvement.plan.md to track documentation evolution.&#10; - Enhanced all how-to guides with improved cross-references and up-to-date information.&#10;- Guidelines:&#10; - Updated .junie/guidelines.md with Node 24 requirements and enhanced commit message standards (Conventional Commits 1.0.0).&#10;- Infrastructure &amp; Code:&#10; - Updated docker-compose.yml with Apollo MCP server integration.&#10; - Refined configuration and schema handling in src/api/ and src/datasources/.&#10; - Synchronized generated TypeScript types with schema updates." />
<option name="LAST_COMMIT_MESSAGE" value="docs: refactor documentation and upgrade to Node.js 24&#10;&#10;This commit upgrades the project to Node.js 24 (LTS) and performs a major refactoring of the documentation to support both advanced users and AI-based automation (MCP).&#10;&#10;Changes:&#10;- Environment &amp; CI/CD:&#10; - Set Node.js version to &gt;=24 in package.json and .nvmrc.&#10; - Updated Dockerfile to use Node 24 base image.&#10; - Updated @types/node to ^24.10.9.&#10;- Documentation:&#10; - Refactored README.md with comprehensive technical reference, configuration details, and Zabbix-to-GraphQL mapping.&#10; - Created docs/howtos/cookbook.md with practical recipes for common tasks and AI test generation.&#10; - Updated docs/howtos/mcp.md to emphasize GraphQL's advantages for AI agents and Model Context Protocol.&#10; - Added readme.improvement.plan.md to track documentation evolution.&#10; - Enhanced all how-to guides with improved cross-references and up-to-date information.&#10;- Guidelines:&#10; - Updated .junie/guidelines.md with Node 24 requirements and enhanced commit message standards (Conventional Commits 1.0.0).&#10;- Infrastructure &amp; Code:&#10; - Updated docker-compose.yml with Apollo MCP server integration.&#10; - Refined configuration and schema handling in src/api/ and src/datasources/.&#10; - Synchronized generated TypeScript types with schema updates." />
<option name="OPTIMIZE_IMPORTS_BEFORE_PROJECT_COMMIT" value="true" />
</component>
<component name="XDebuggerManager">
@ -424,7 +483,7 @@
<breakpoints>
<line-breakpoint enabled="true" type="javascript">
<url>file://$PROJECT_DIR$/src/datasources/zabbix-request.ts</url>
<line>133</line>
<line>134</line>
<option name="timeStamp" value="5" />
</line-breakpoint>
<line-breakpoint enabled="true" type="javascript">

View file

@ -2,12 +2,20 @@
This document provides concise information and best practices for developers working on the Zabbix GraphQL API project.
## Roadmap
The [Roadmap](../roadmap.md) is to be considered as outlook giving constraints on architectural and design decisions for current work on the project.
### Environment
- **Operating System**: Windows with WSL + Ubuntu installed.
- **Commands**: Always execute Linux commands (e.g. use `ls` instead of `dir`) and use `docker compose` (without hyphen) instead of `docker-compose`.
## Tech Stack
- **Runtime**: Node.js (v18+)
- **Runtime**: Node.js (v24+)
- **Language**: TypeScript (ESM)
- **API**: GraphQL (Apollo Server 4)
- **Testing**: Jest
- **Deployment**: Docker
- **Deployment**: Docker (v27+) and Docker Compose (v2.29+)
## Project Structure
- `src/api/`: GraphQL server configuration, schema loading, and root resolvers (see `createResolvers` in `resolvers.ts`).
@ -20,18 +28,79 @@ This document provides concise information and best practices for developers wor
## Common Scripts
- `npm run start`: Launches the development server with `tsx` and `nodemon` for hot-reloading.
- `npm run test`: Executes the Jest test suite.
- `npm run codegen`: Generates TypeScript types based on the GraphQL schema definitions.
- `npm run codegen`: Starts GraphQL Codegen in watch mode (for continuous development).
- `npx graphql-codegen --config codegen.ts`: Generates TypeScript types once (use this for one-off updates).
- `npm run compile`: Compiles TypeScript source files into the `dist/` directory.
- `npm run prod`: Prepares the schema and runs the compiled production build.
## Best Practices & Standards
- **ESM & Imports**: The project uses ECMAScript Modules (ESM). Always use the `.js` extension when importing local files (e.g., `import { Config } from "../common_utils.js";`), even though the source files are `.ts`.
- **ESM & Imports**: The project uses ECMAScript Modules (ESM). Always use the `.js` extension when importing local files (e.g. `import { Config } from "../common_utils.js";`), even though the source files are `.ts`.
- **Configuration**: Always use the `Config` class to access environment variables. Avoid direct `process.env` calls.
- **Type Safety**: Leverage types generated via `npm run codegen` for resolvers and data handling to ensure consistency with the schema.
- **Type Safety**: Leverage types generated via `npx graphql-codegen --config codegen.ts` (or `npm run codegen` for watch mode) for resolvers and data handling to ensure consistency with the schema.
- **Import Optimization**:
- Always optimize imports before committing.
- Project setting `OPTIMIZE_IMPORTS_BEFORE_PROJECT_COMMIT` is enabled.
- Junie should include "Optimize imports" as a step in every plan.
- **Modular Datasources**: When adding support for new Zabbix features, create a new datasource class in `src/datasources/` extending `ZabbixRESTDataSource`.
- **Schema Organization**: Place GraphQL SDL files in the `schema/` directory. Use descriptive comments in SDL as they are used for API documentation.
- **Testing**: Write reproduction tests for bugs and cover new features with both unit and integration tests in `src/test/`.
- **Grammar & Style**: Avoid using a comma after "e.g." or "i.e." (e.g. use "e.g. example" instead of "e.g., example").
## Verification & Deployment
- **Pre-commit Verification**: Always add a verification stage to your plan before committing.
- *Action*: Run the `Smoketest` tool using MCP to ensure basic functionality is intact.
- *Action*: Monitor the API logs for errors after each service restart.
- **Environment Restart**: Always include a step to rebuild and restart the API and MCP server as a final check.
- *Command*: `docker compose up -d --build`
- *Requirement*: Ask the user if everything looks okay before executing the restart, and offer the option to skip this step.
### Documentation Style
- **Bullet Points**: Use bullet points instead of enumerations for lists to maintain consistency across all documentation.
- **Visual Style**: Use icons in headers and bold subjects for primary list items (e.g. `- **Feature**: Description`) to match the `README.md` style.
- *Note*: Standardize colon placement (outside bold tags) for primary list items.
- **Sub-points**: Use indented bullet points for detailed descriptions or additional context.
- *Format*: Use the `- *Subject*: Description` format for specific references or categorized sub-items (e.g. `- *Reference*: ...`).
- *Format*: Use plain text for general descriptions that follow a primary list item.
- **Formatting**: Avoid extra blank lines between headers and list items.
## Documentation Templates
### Cookbook & Recipes
Follow this template for all cookbook entries to ensure consistency and AI-parsability.
#### Recipe Template
```markdown
## 🍳 Recipe: [Action-Oriented Title]
[Brief description of what this recipe achieves.]
### 📋 Prerequisites
- [Constraint 1]
- [Constraint 2]
### 🛠️ Step 1: [Preparation/Definition]
[Instructions for preparing data or environment.]
### ⚙️ Step 2: [Configuration/Settings]
[Instructions for configuring settings or environment variables.]
### 🚀 Step 3: [Execution/Action]
[The main GraphQL operation or command to execute.]
### ✅ Step 4: [Verification]
[How to verify that the operation was successful using a query or agent check.]
```
#### Standard Icons for Steps
- 🍳 **Recipe**: Main header icon.
- 📋 **Prerequisites**: Necessary conditions before starting.
- 🛠️ **Preparation**: Initial setup, data definition, or file creation.
- ⚙️ **Configuration**: Changes to settings, `.env`, or Zabbix UI.
- 🚀 **Execution**: The primary action or mutation.
- ✅ **Verification**: Result checking and outcome validation.
- 💡 **Tip/Alternative**: Helpful hints or alternative methods.
- 🤖 **AI/MCP**: Agent-specific instructions.
### Git Standards
- **Commit Messages:** Use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) (e.g. `feat:`, `fix:`, `chore:`, `docs:`, `test:`, `refactor:`, `style:`).
- If a commit is complex and covers different aspects, the message **must** always contain a detailed list of what was changed within the optional "body" section, in addition to the short "description" in the header.
- **Review & Approval**: Never commit changes automatically. Always present the proposed changes and the updated plan to the user for review. Allow the user to provide feedback or add tasks to the plan before proceeding with a commit.

1
.nvmrc Normal file
View file

@ -0,0 +1 @@
24

View file

@ -1,7 +1,7 @@
# Hint: With node_version>=21.6.0 there are problems with debugging,
# therefore the development node version is set to 21.5.0 + in order to keep dev + prod versions aligned
# therefore the development node version is set to 24 + in order to keep dev + prod versions aligned
# this was also reflected in the Dockerfile
ARG node_version=21.5.0
ARG node_version=24
#stage1
FROM node:${node_version} as builder

339
README.md
View file

@ -6,61 +6,67 @@ A modern GraphQL interface for Zabbix, providing enhanced features and easier in
The Zabbix GraphQL API acts as a wrapper and enhancer for the native Zabbix JSON-RPC API. It simplifies complex operations, provides a strongly-typed schema, and adds advanced logic for importing, querying, and managing Zabbix entities like hosts, templates, and user rights.
## Key Features & Enhancements
## 🚀 Features
Compared to the original Zabbix API, this GraphQL API provides several key enhancements:
- **GraphQL Interface**: Modern GraphQL API wrapping Zabbix functionality
- *Reference*: `schema/queries.graphql`, `schema/mutations.graphql`, `src/api/start.ts`
* **Mass Import/Export**: Robust support for importing and exporting templates, template groups, hosts, and host groups in bulk.
* **Hierarchical Host Groups**: Automatically handles the creation and resolution of nested host group hierarchies (e.g., `Parent/Child/Leaf`).
* **Template Management**:
* Full support for template items, including complex preprocessing steps and tags.
* **Dependent Item Support**: Intelligent deferred creation logic to handle item dependencies within a template.
* Linked template resolution by name.
* **Advanced Deletion**: Ability to delete templates and template groups not only by ID but also by **name patterns** (supporting Zabbix wildcards like `%`).
* **User Rights & Permissions**:
* Integrated management of user roles and user groups.
* Support for importing/exporting user rights with UUID-based matching for cross-instance consistency.
* On-the-fly permission checks (`hasPermissions`, `userPermissions`).
* **Improved Error Reporting**: Detailed error data from Zabbix is appended to GraphQL error messages, making debugging significantly easier.
* **Strongly Typed Schema**: Leverages GraphQL's type system for clear API documentation and client-side code generation.
* **Dynamic Schema Extensibility**: Easily extend the API with custom schema snippets and dynamic resolvers for specialized device types without modifying the core code.
* **CI/CD Integration**: Includes a ready-to-use Forgejo/Gitea/GitHub Actions workflow for automated building, testing, and deployment.
* **Sample Application (VCR)**: Designed to power the **Virtual Control Room**, a professional cockpit for managing thousands of IoT/Edge devices.
- **Hierarchical Data Mapping**: Automatic mapping of Zabbix items/tags to nested GraphQL objects
- *Reference*: `src/api/resolver_helpers.ts`, `schema/device_value_commons.graphql`, `docs/sample_all_devices_query.graphql`
- **Mass Operations**: Import/export capabilities for hosts, templates, and user rights
- *Reference*: `schema/mutations.graphql` (importHosts, importTemplates, importUserRights, etc.), `docs/sample_import_*.graphql`
- **Dynamic Schema Extension**: Extend the schema without code changes using environment variables
- *Reference*: `src/api/schema.ts`, `schema/extensions/`, `src/common_utils.ts` (ADDITIONAL_SCHEMAS, ADDITIONAL_RESOLVERS)
- **Permission System**: Role-based access control using Zabbix template groups
- *Reference*: `schema/api_commons.graphql` (Permission enum, PermissionRequest), `src/api/resolvers.ts` (hasPermissions, userPermissions), `docs/sample_import_permissions_template_groups_mutation.graphql`
- **Type Safety**: Full TypeScript support with generated types
- *Reference*: `codegen.ts`, `src/schema/generated/graphql.ts`, `tsconfig.json`, `package.json` (devDependencies for GraphQL Codegen)
- **AI Agent & MCP Enablement**: Native support for Model Context Protocol (MCP) and AI-driven automation. GraphQL's strongly-typed, introspectable nature provides a superior interface for AI agents compared to traditional REST APIs.
- *Reference*: `docs/howtos/mcp.md`, `.ai/mcp/mcp.json` (Sample Config), `mcp-config.yaml`, `docker compose` (MCP service)
> **Planned features**: For an overview of achieved milestones and planned enhancements have a look at the [**Roadmap**](./roadmap.md).
## How-To Guides
For detailed information on specific topics and practical step-by-step instructions, please refer to our guides:
- [**Cookbook**](./docs/howtos/cookbook.md): Practical, task-oriented recipes for quick start and AI test generation.
- [**Schema & Extension Overview**](./docs/howtos/schema.md): Detailed explanation of the schema structure and extension mechanism.
- [**Hierarchical Data Mapping**](./docs/howtos/hierarchical_data_mapping.md): How Zabbix items are mapped to nested GraphQL fields.
- [**Roles & Permissions**](./docs/howtos/permissions.md): Managing user rights through Zabbix template groups.
- [**Technical Maintenance Guide**](./docs/howtos/maintenance.md): Guide on code generation, testing, and Docker maintenance.
- [**Test Specification**](./docs/tests.md): Detailed list of test cases and coverage checklist.
- [**MCP & Agent Integration**](./docs/howtos/mcp.md): Connecting LLMs and autonomous agents via Model Context Protocol.
See the [How-To Overview](./docs/howtos/README.md) for a complete list of documentation.
## How to Install and Start
### Prerequisites
### 📋 Prerequisites
Before you begin, ensure you have met the following requirements:
* **Node.js**: Version 18 or higher recommended.
* **Zabbix**: A running Zabbix instance (compatible with Zabbix 6.0+).
- **Node.js**: Version 24 (LTS) or higher recommended.
- **Docker**: Version 27 or higher and **Docker Compose** v2.29 or higher (use `docker compose` instead of `docker-compose`).
- **Zabbix**: A running Zabbix instance (compatible with Zabbix 6.0+) with API access.
- **Zabbix Super Admin Token** (for full functionality / privilege escalation).
- **Zabbix User Access** (groups and roles depending on your use case).
### Installation
1. Clone the repository:
```bash
git clone <repository-url>
cd zabbix-graphql-api
```
2. Install dependencies:
```bash
npm install
```
### Configuration
The API is configured via environment variables. Create a `.env` file or set them in your environment:
| Variable | Description | Default |
| :--- | :--- | :--- |
| `ZABBIX_BASE_URL` | URL to your Zabbix API (e.g., `http://zabbix.example.com/zabbix`) | |
| `ZABBIX_AUTH_TOKEN` | Zabbix Super Admin API token for administrative tasks | |
| `ZABBIX_EDGE_DEVICE_BASE_GROUP` | Base host group for devices | `Roadwork` |
| `ZABBIX_PERMISSION_TEMPLATE_GROUP_NAME_PREFIX` | Prefix for template groups used as permissions | `Permissions` |
| `SCHEMA_PATH` | Path to the directory containing `.graphql` schema files | `./schema/` |
| `HOST_GROUP_FILTER_DEFAULT` | Default search pattern for `allHostGroups` query | |
| `HOST_TYPE_FILTER_DEFAULT` | Default value for `tag_hostType` filter in `allHosts` and `allDevices` queries | |
### 🛠️ Installation
- Clone the repository:
```bash
git clone <repository-url>
cd zabbix-graphql-api
```
- Install dependencies:
```bash
npm install
```
### Starting the API
#### Development Mode
@ -77,9 +83,68 @@ npm run prod
The API will be available at `http://localhost:4000/`.
## ⚙️ Configuration
### Environment Variables
The API is configured via environment variables. Create a `.env` file or set them in your environment:
| Variable | Description | Default | Required |
|----------|-------------|---------|----------|
| `ZABBIX_BASE_URL` | URL to your Zabbix server (include `/zabbix` path) | - | Yes |
| `ZABBIX_PRIVILEGE_ESCALATION_TOKEN` | Zabbix Super Admin API token used for privilege escalation (e.g. during import operations) | - | Yes |
| `ZABBIX_DEVELOPMENT_TOKEN` | Token used ONLY for local development and isolated testing to simulate a "real end user" | - | No |
| `ZABBIX_EDGE_DEVICE_BASE_GROUP` | Base group for edge devices | `Roadwork` | No |
| `ZABBIX_PERMISSION_TEMPLATE_GROUP_NAME_PREFIX` | Prefix for permission template groups (used to identify permission-related template groups in Zabbix) | `Permissions` | No |
| `SCHEMA_PATH` | Path to schema files | `./schema/` | No |
| `ADDITIONAL_SCHEMAS` | Comma-separated list of additional schema files | - | No |
| `ADDITIONAL_RESOLVERS` | Comma-separated list of resolver types to generate | - | No |
| `LOG_LEVEL` | Log level configuration (e.g. `debug`, `info`, `warn`, `error`) | `info` | No |
| `HOST_TYPE_FILTER_DEFAULT` | Default filter for host types | - | No |
| `HOST_GROUP_FILTER_DEFAULT` | Default filter for host groups | - | No |
## 🔐 Authorization
The application operates with different authentication and authorization mechanisms depending on the scenario:
### 🛡️ Production Use
In production environments, the `ZABBIX_DEVELOPMENT_TOKEN` should **always be unset** to ensure proper security.
- **Zabbix Frontend Widgets**: When accessing the API from widgets embedded in the Zabbix frontend, no static token is needed. Authentication is automatically derived from the `zbx_session` cookie provided by the Zabbix web login, which is forwarded to the Zabbix API.
- **Other Production Tools**: For other purposes (e.g. accessing the API from external tools or scripts), a Zabbix session or auth token must be passed via the `zabbix-auth-token` HTTP header.
> **Recipe**: See [Managing User Permissions](./docs/howtos/cookbook.md#recipe-managing-user-permissions) for setup instructions.
### 🔑 Privilege Escalation
- **`ZABBIX_PRIVILEGE_ESCALATION_TOKEN`**: Certain operations, such as importing hosts, templates, or user rights, require Super Admin access to Zabbix for specific parts of the process. This token allows the API to perform these administrative tasks even when the initiating user does not have Super Admin rights themselves.
### 🧪 Local Development and Testing
- **`ZABBIX_DEVELOPMENT_TOKEN`**: This environment variable is intended **only** for local development and isolated testing. It allows developers to "simulate" a Zabbix access token representing a "real end user" without needing to provide the HTTP header in every request.
- **Warning**: This should be avoided in production as it undermines security by bypassing per-request authentication.
### 🗺️ Zabbix to GraphQL Mapping
The API maps Zabbix entities to GraphQL types as follows:
| Zabbix Entity | GraphQL Type | Description |
|---------------|--------------|-------------|
| Host | `Host` / `Device` | Represents a Zabbix host; `Device` is a specialized `Host` with a `deviceType` tag |
| Host Group | `HostGroup` | Represents a Zabbix host group |
| Template | `Template` | Represents a Zabbix template |
| Template Group | `HostGroup` | Represents a Zabbix template group |
| Item | Nested fields | Zabbix items become nested fields based on their key names (hierarchical mapping) |
| Tag | `Tag` | Represents a Zabbix tag associated with a host or template |
| Inventory | `Location` | Host inventory information maps to location data |
> **Detailed Guide**: For a deeper dive into how Zabbix items are transformed into GraphQL fields, see [Hierarchical Data Mapping](./docs/howtos/hierarchical_data_mapping.md).
## Running with Docker
### Using the Pre-built Image
### 📦 Using the Pre-built Image
You can run the API without building it locally by pulling the latest image from the Hilbig IT Forgejo infrastructure:
@ -94,159 +159,39 @@ docker run -d \
--name zabbix-graphql-api \
-p 4000:4000 \
-e ZABBIX_BASE_URL=http://your-zabbix-instance/zabbix \
-e ZABBIX_AUTH_TOKEN=your-super-admin-token \
-e ZABBIX_PRIVILEGE_ESCALATION_TOKEN=your-super-admin-token \
forgejo.tooling.hilbigit.com/zabbix/zabbix-graphql-api:latest
```
### Building Locally
### 🏗️ Building Locally
If you prefer to build the image yourself using the provided `Dockerfile`:
1. Build the image (ensure you provide an `API_VERSION`):
* Build the image (ensure you provide an `API_VERSION`):
```bash
docker build -t zabbix-graphql-api --build-arg API_VERSION=1.0.0 .
```
2. Run the container:
* Run the container:
```bash
docker run -d \
--name zabbix-graphql-api \
-p 4000:4000 \
-e ZABBIX_BASE_URL=http://your-zabbix-instance/zabbix \
-e ZABBIX_AUTH_TOKEN=your-super-admin-token \
-e ZABBIX_PRIVILEGE_ESCALATION_TOKEN=your-super-admin-token \
zabbix-graphql-api
```
## Extending the Schema
The Zabbix GraphQL API is designed to be highly extensible. You can add your own GraphQL schema snippets and have resolvers dynamically created for them.
### Dynamic Resolvers with `createHierarchicalValueFieldResolver`
The function `createHierarchicalValueFieldResolver` (found in `src/api/resolver_helpers.ts`) allows for the automatic creation of resolvers that map Zabbix items or tags to a hierarchical GraphQL structure. It uses field names and Zabbix item keys (dot-separated) to automatically resolve nested objects.
### Zabbix Preconditions for Hierarchical Mapping
In order for the dynamic resolvers to correctly map Zabbix data to your GraphQL schema, the following preconditions must be met in your Zabbix templates:
* **Key Naming**: Zabbix item keys (or tags) must match the GraphQL field names.
* **Dot Separation**: Use a dot (`.`) as a separator to represent nested object structures. For example, a Zabbix item with the key `state.current.values.temperature` will be automatically mapped to the `temperature` field within the nested structure: `state` -> `current` -> `values` -> `temperature`.
* **Type Hinting**: You can guide the type conversion by prepending a type hint and an underscore to the last token of the key:
* `json_`: Parses the value as a JSON object (useful for complex types).
* `str_`: Forces the value to be treated as a string.
* `bool_`: Forces the value to be treated as a boolean.
* `float_`: Forces the value to be treated as a number.
For a complete example of a Zabbix template designed for schema extension, see the [Distance Tracker Import Sample](docs/sample_import_distance_tracker_template.graphql).
### No-Code Extension via Environment Variables
You can extend the schema and add resolvers without writing any TypeScript code by using the following environment variables:
* **`ADDITIONAL_SCHEMAS`**: A comma-separated list of paths to additional `.graphql` files.
* **`ADDITIONAL_RESOLVERS`**: A comma-separated list of GraphQL Type names for which dynamic hierarchical resolvers should be created.
#### Example
Suppose you have custom device definitions in `schema/extensions/`. You can load them and enable dynamic resolution by setting:
```bash
ADDITIONAL_SCHEMAS=./schema/extensions/display_devices.graphql,./schema/extensions/location_tracker_devices.graphql,./schema/extensions/location_tracker_commons.graphql
ADDITIONAL_RESOLVERS=SinglePanelDevice,FourPanelDevice,DistanceTrackerDevice
```
The API will:
1. Load all provided schema files.
2. For each type listed in `ADDITIONAL_RESOLVERS`, it will automatically create a resolver that maps Zabbix items (e.g., an item with key `state.current.values.temperature`) to the corresponding GraphQL fields.
## User Permissions & `hasPermission`
The Zabbix GraphQL API provides a sophisticated way to manage and check application-level permissions using Zabbix's built-in user group and template group mechanisms.
### Modeling Permissions with Template Groups
Permissions can be modeled as **empty template groups** (groups with no templates or hosts attached) organized in a hierarchical structure. By convention, these groups start with a configurable prefix (default: `Permissions/`).
#### Example Hierarchy:
* `Permissions/ConstructionSite`: General access to construction site data.
* `Permissions/Automatism`: Access to automation features.
* `Permissions/Automatism/Status`: Permission to view automation status.
### Zabbix Preconditions
1. **Template Groups**: Create template groups for each permission you want to manage (e.g., `Permissions/App1/FeatureA`).
2. **User Groups**: In Zabbix, assign these template groups to Zabbix User Groups with specific permission levels (`READ`, `READ_WRITE`, or `DENY`).
3. **Authentication**: The GraphQL API will check the permissions of the authenticated user (via token or session cookie) against these Zabbix assignments.
### Using `hasPermission` and `userPermissions`
The API provides two main queries for permission checking:
* **`userPermissions`**: Returns a list of all permissions assigned to the current user.
* **`hasPermissions`**: Checks if the user has a specific set of required permissions (e.g., "Does the user have `READ_WRITE` access to `Automatism/Status`?").
This allows for fine-grained access control in your frontend or external applications, using Zabbix as the central authorization authority.
For a complete example of how to import these permission groups, see the [Permissions Template Groups Import Sample](docs/sample_import_permissions_template_groups_mutation.graphql).
## Host Classification & Filtering
The API leverages Zabbix tags to classify hosts and devices, enabling efficient filtering and multi-tenancy support.
### The `hostType` Tag
The `hostType` tag is used to categorize hosts and templates. This allows the API to provide default filters for specific application domains or device categories.
#### How to set the Host Type in Zabbix:
To classify a host or a template, simply add a tag in the Zabbix UI or via the API:
* **Tag Name**: `hostType`
* **Tag Value**: A string representing the category (e.g., `Roadwork/Devices`, `SmartCity/Sensors`).
This tag can be defined:
1. **Directly on the Host**: Specific to that individual device.
2. **On a Template**: All hosts linked to this template will inherit the classification.
### Default Filtering with `HOST_TYPE_FILTER_DEFAULT`
By configuring the `HOST_TYPE_FILTER_DEFAULT` environment variable, you can set a global default for the `allHosts` and `allDevices` queries.
* If `HOST_TYPE_FILTER_DEFAULT=Roadwork/Devices` is set, a query like `allHosts { host }` will only return hosts that have the `hostType` tag set to `Roadwork/Devices`.
* This default can always be overridden in the GraphQL query by explicitly passing the `tag_hostType` argument.
### Search Filtering with `HOST_GROUP_FILTER_DEFAULT`
The `HOST_GROUP_FILTER_DEFAULT` variable provides a default search pattern for the `allHostGroups` query. This is particularly useful for restricting the visible host group hierarchy to a specific subtree by default.
#### Overriding the Default Filter
The default filter can be overridden by explicitly providing the `search_name` argument in the `allHostGroups` query. When `search_name` is present, the environment variable is ignored.
#### Using Wildcards
The `search_name` parameter supports the `*` wildcard (enabled via the Zabbix API's `searchWildcardsEnabled` feature). This allows you to search for all subgroups within a specific path.
**Example**: To find all subgroups of `Roadwork/Devices/`, use the following query:
```graphql
query {
allHostGroups(search_name: "Roadwork/Devices/*") {
groupid
name
}
}
```
## Sample Application: Virtual Control Room (VCR)
The **Virtual Control Room (VCR)** is a professional cockpit and control center application designed for monitoring and managing large-scale deployments of IoT and Edge devices, such as traffic management systems, roadwork safety equipment, and environmental sensors.
### How VCR uses the GraphQL API:
### 🧩 How VCR uses the GraphQL API
* **Unified Cockpit**: VCR utilizes the API's **hierarchical mapping** to provide a unified view of diverse device types. It maps Zabbix items and tags directly to structured GraphQL objects (e.g., `operational` telemetry and `current` business state).
* **Dynamic Authorization**: The `hasPermissions` query is used to implement a **Dynamic UI**. Buttons, controls, and status indicators are shown or enabled only if the user has the required `READ` or `READ_WRITE` permissions for that specific object.
* **Mass Provisioning**: VCR leverages the **mass import** capabilities to provision thousands of devices and templates in a single operation, significantly reducing manual configuration effort in Zabbix.
* **Data Visualization**: It uses the `exportHostValueHistory` endpoint to power dashboards showing historical trends, such as traffic density, battery levels, or sensor readings over time.
- **Unified Cockpit**: VCR utilizes the API's **hierarchical mapping** to provide a unified view of diverse device types. It maps Zabbix items and tags directly to structured GraphQL objects (e.g. `operational` telemetry and `current` business state).
- **Dynamic Authorization**: The `hasPermissions` query is used to implement a **Dynamic UI**. Buttons, controls, and status indicators are shown or enabled only if the user has the required `READ` or `READ_WRITE` permissions for that specific object.
- **Mass Provisioning**: VCR leverages the **mass import** capabilities to provision thousands of devices and templates in a single operation, significantly reducing manual configuration effort in Zabbix.
- **Data Visualization**: It uses the `exportHostValueHistory` endpoint to power dashboards showing historical trends, such as traffic density, battery levels, or sensor readings over time.
For more detailed information about the VCR product, please refer to the technical presentation:
[VCR - Technical product information](docs/VCR%20-%20Technical%20product%20information.pdf)
@ -258,7 +203,7 @@ Below is a complete example of a `.env` file showing all available configuration
```env
# Zabbix Connection
ZABBIX_BASE_URL=http://your-zabbix-instance/zabbix
ZABBIX_AUTH_TOKEN=your-super-admin-token-here
ZABBIX_PRIVILEGE_ESCALATION_TOKEN=your-super-admin-token-here
# General Configuration
ZABBIX_EDGE_DEVICE_BASE_GROUP=Roadwork
@ -277,23 +222,27 @@ ADDITIONAL_RESOLVERS=SinglePanelDevice,FourPanelDevice,DistanceTrackerDevice
## Usage Samples
The `docs` directory contains several sample GraphQL queries and mutations to help you get started:
The `docs/queries` directory contains several sample GraphQL queries and mutations to help you get started.
* **Hosts**:
* [Query All Hosts](docs/sample_all_hosts_query.graphql)
* [Import Hosts](docs/sample_import_hosts_mutation.graphql)
* **Templates**:
* [Query Templates](docs/sample_templates_query.graphql)
* [Import Templates](docs/sample_import_templates_mutation.graphql)
* [Import Distance Tracker Template](docs/sample_import_distance_tracker_template.graphql) (Schema Extension Example)
* [Delete Templates](docs/sample_delete_templates_mutation.graphql)
* **Template Groups**:
* [Import Host Template Groups](docs/sample_import_host_template_groups_mutation.graphql)
* [Import Permissions Template Groups](docs/sample_import_permissions_template_groups_mutation.graphql)
* [Delete Template Groups](docs/sample_delete_template_groups_mutation.graphql)
* **User Rights**:
* [Export User Rights](docs/sample_export_user_rights_query.graphql)
* [Import User Rights](docs/sample_import_user_rights_mutation.graphql)
> **Samples Reference**: See the [Sample Queries & Mutations Overview](./docs/queries/README.md) for a categorized list of examples.
## 🔄 API Version
The API version is automatically set during the Docker build process based on the Git tag or commit hash. This version information is embedded into the Docker image and becomes accessible through the `API_VERSION` environment variable at runtime.
### 🔧 Zabbix Version Compatibility
This API is designed to work with Zabbix 7.4, which is the version it runs productively with. While it may work with earlier versions (like 6.0+), 7.4 is the officially supported and tested version.
## 🛠️ Technical Maintenance
For information on code generation, running tests, and managing the project's development lifecycle, please refer to the [Technical Maintenance Guide](./docs/howtos/maintenance.md).
## 🔗 External Resources
- **Zabbix API Documentation**: [https://www.zabbix.com/documentation/7.0/en/manual/api](https://www.zabbix.com/documentation/7.0/en/manual/api)
- **Apollo Server Documentation**: [https://www.apollographql.com/docs/apollo-server/](https://www.apollographql.com/docs/apollo-server/)
- **Model Context Protocol (MCP)**: [https://modelcontextprotocol.io/](https://modelcontextprotocol.io/)
## License

View file

@ -16,8 +16,7 @@ const config: CodegenConfig = {
declarationKind: 'interface'
}
}
},
watch: true
}
};
export default config;

40
docker-compose.yml Normal file
View file

@ -0,0 +1,40 @@
services:
zabbix-graphql-api:
build:
context: .
args:
- API_VERSION=1.0.0
ports:
- "4001:4000"
env_file:
- .env
environment:
- SCHEMA_PATH=/usr/app/dist/schema/
- ZABBIX_DEVELOPMENT_TOKEN=${ZABBIX_DEVELOPMENT_TOKEN}
apollo-mcp-server:
image: ghcr.io/apollographql/apollo-mcp-server:latest
ports:
- "3000:8000"
volumes:
- ./mcp-config.yaml:/mcp-config.yaml
- mcp-shared:/mcp-data:ro
- ./mcp/operations:/mcp/operations
command: /mcp-config.yaml
environment:
- APOLLO_GRAPH_REF=local@main
depends_on:
schema-gen:
condition: service_completed_successfully
zabbix-graphql-api:
condition: service_started
schema-gen:
image: alpine
volumes:
- ./schema:/schema:ro
- mcp-shared:/mcp-data
command: sh -c "cat /schema/*.graphql > /mcp-data/schema.graphql"
volumes:
mcp-shared:

32
docs/howtos/README.md Normal file
View file

@ -0,0 +1,32 @@
# How-To Guides
This directory contains detailed guides on how to use and extend the Zabbix GraphQL API.
## Available Guides
### 🍳 [Cookbook](./cookbook.md)
Practical, step-by-step recipes for common tasks, designed for both humans and AI-based test generation.
### 📊 [Schema and Schema Extension](./schema.md)
Learn about the GraphQL schema structure, how Zabbix entities map to GraphQL types, and how to use the dynamic schema extension system.
### 🗂️ [Hierarchical Data Mapping](./hierarchical_data_mapping.md)
Understand how the API automatically maps flat Zabbix item keys into nested GraphQL objects using hierarchical resolvers and type hinting.
### 🔐 [Roles and Permissions Extension](./permissions.md)
Discover how the permission system works, how to define permission levels using Zabbix template groups, and how to query user permissions.
### 🛠️ [Technical Maintenance](./maintenance.md)
Guide on code generation (GraphQL Codegen), running Jest tests, and local Docker builds.
### 🧪 [Test Specification](../tests.md)
Detailed list of test cases, categories (Unit, Integration, E2E), and coverage checklist.
### 🤖 [MCP & Agent Integration](./mcp.md)
Discover how to integrate with the Model Context Protocol (MCP) to enable LLMs and autonomous agents to interact with Zabbix efficiently.
---
## 🔍 Additional Resources
- **[Sample Queries](../queries/README.md)**: Categorized list of practical GraphQL operation examples.
- **[Main README](../../README.md)**: Technical reference, configuration, and environment setup.

580
docs/howtos/cookbook.md Normal file
View file

@ -0,0 +1,580 @@
# Zabbix GraphQL API Cookbook
This cookbook provides step-by-step "recipes" for common tasks. These instructions are designed to be easy for humans to follow and structured enough for AI agents (using the MCP server) to generate test cases.
## 🤖 AI-Based Test Generation
To generate a test case from a recipe:
- Start the `zabbix-graphql` MCP server.
- Provide the recipe to your AI agent.
- Ask the agent to "Implement a test case for this recipe using the Zabbix GraphQL API".
- The agent will use the MCP server to explore the schema and generate appropriate GraphQL operations.
---
## 🍳 Recipe: Executing Zabbix 7.4 Documentation Samples
This recipe shows how to execute standard Zabbix 7.4 API examples using their GraphQL equivalents. These samples are directly derived from the official Zabbix documentation and adapted for use with this GraphQL API.
### 📋 Prerequisites
- Zabbix GraphQL API is running.
- You have a valid Zabbix user account and are logged in (or have an auth token).
### 🛠️ Step 1: Browse Available Samples
All samples derived from the Zabbix 7.4 documentation are stored in the following directory:
- `docs/queries/from_zabbix_docs/`
Each `.graphql` file in this directory contains a reference link to the original Zabbix documentation source.
### ⚙️ Step 2: Extract Query and Variables
Each sample file in `docs/queries/from_zabbix_docs/` is structured to include both the GraphQL operation and a corresponding set of sample variables.
1. **Open the Sample**: Open the desired `.graphql` file (e.g. `createHost.graphql`).
2. **Copy the Query**: Copy the GraphQL code block found under the `### Query` header.
3. **Copy the Variables**: Copy the JSON code block found under the `### Variables` header.
### 🚀 Step 3: Execution/Action
Choose a sample and execute it against the GraphQL endpoint using your preferred client (like Apollo Studio, GraphiQL, or Postman).
- **Configure the Request**:
- Paste the **Query** into the operation window.
- Paste the **Variables** JSON into the variables window.
- **Run the Operation**: Click "Execute" or "Play".
#### Example: Creating a Host
Using the content from `docs/queries/from_zabbix_docs/createHost.graphql`:
**Query**:
```graphql
mutation CreateHost($host: String!, $hostgroupids: [Int!]!, $templateids: [Int!]!) {
createHost(host: $host, hostgroupids: $hostgroupids, templateids: $templateids) {
hostids
}
}
```
**Variables**:
```json
{
"host": "Linux server",
"hostgroupids": [50],
"templateids": [20045]
}
```
### ✅ Step 4: Verification
Compare the GraphQL response with the expected output described in the Zabbix documentation. Note that while the field names in GraphQL match the Zabbix API field names (e.g. `hostid`, `host`), the structure is simplified and nested objects (like `hostgroups`) can be queried directly without separate `selectXXX` parameters.
- *Reference*: [Zabbix 7.4 API Documentation](https://www.zabbix.com/documentation/7.4/en/manual/api)
---
## 🍳 Recipe: Extending Schema with a New Device Type
This recipe shows how to add support for a new specialized device type without modifying the core API code. We will use the `DistanceTrackerDevice` as an example.
### 📋 Prerequisites
- Zabbix Template Group `Templates/Roadwork/Devices` exists.
- Zabbix GraphQL API is running.
### 🛠️ Step 1: Define the Schema Extension
Create a new `.graphql` file in `schema/extensions/` (e.g. `distance_tracker.graphql`).
> **Advice**: A new device type must always implement both the `Host` and `Device` interfaces to ensure compatibility with the API's core logic and resolvers.
```graphql
type DistanceTrackerDevice implements Host & Device {
# Mandatory Host & Device fields
hostid: ID!
host: String!
deviceType: String
hostgroups: [HostGroup!]
name: String
tags: DeviceConfig
# Specialized state for this device
state: DistanceTrackerState
}
type DistanceTrackerState implements DeviceState {
operational: OperationalDeviceData
current: DistanceTrackerValues
}
type DistanceTrackerValues {
timeFrom: Time
timeUntil: Time
count: Int
# The distances are modelled using a type which is already defined in location_tracker_commons.graphql
distances: [SensorDistanceValue!]
}
```
> **Reference**: This example is based on the already prepared sample: [location_tracker_devices.graphql](../../schema/extensions/location_tracker_devices.graphql).
### ⚙️ Step 2: Configure Environment Variables
Add the new schema and resolver to your `.env` file:
```env
ADDITIONAL_SCHEMAS=./schema/extensions/distance_tracker.graphql,./schema/extensions/location_tracker_commons.graphql
ADDITIONAL_RESOLVERS=DistanceTrackerDevice
```
Restart the API server.
### 🚀 Step 3: Execution/Action (Choose Method)
#### Method A: Manual Creation in Zabbix
If you prefer to configure Zabbix manually:
1. **Create Template**: Create a template named `DISTANCE_TRACKER`.
2. **Create Items**: Add items with keys that match the GraphQL fields (using hierarchical mapping):
* `state.current.timeFrom`
* `state.current.timeUntil`
* `state.current.count`
* `state.current.json_distances` (maps to `distances` array via `json_` prefix)
3. **Add Tag**: Add a host tag to the template with name `deviceType` and value `DistanceTrackerDevice`.
#### Method B: Automated Import
Execute the `importTemplates` mutation to create the template and items automatically.
> **Reference**: Use the [Sample: Distance Tracker Import](../../docs/queries/sample_import_distance_tracker_template.graphql) for a complete mutation and variables example.
### ✅ Step 4: Verify the Extension
Verify that the new type is available and correctly mapped by creating a test host and querying it.
#### 1. Create a Test Host
Use the `importHosts` mutation (or `createHost` if IDs are already known) to create a host. Set its `deviceType` to `DistanceTrackerDevice` and link it to the `DISTANCE_TRACKER` template (created in Step 3) using the `templateNames` parameter.
```graphql
mutation CreateTestDistanceTracker($host: String!, $groupNames: [String!]!, $templateNames: [String]) {
importHosts(hosts: [{
deviceKey: $host,
deviceType: "DistanceTrackerDevice",
groupNames: $groupNames,
templateNames: $templateNames
}]) {
hostid
message
}
}
```
#### 2. Query the Device
Query the newly created device. Use the `tag_deviceType: ["DistanceTrackerDevice"]` argument to ensure you only retrieve devices of this specific type and prevent showing all devices of any type.
```graphql
query VerifyNewDeviceType {
# 1. Check if the type exists in the schema
__type(name: "DistanceTrackerDevice") {
name
fields {
name
}
}
# 2. Query the specific device
allDevices(tag_deviceType: ["DistanceTrackerDevice"]) {
... on DistanceTrackerDevice {
name
state {
current {
count
distances {
distance
}
}
}
}
}
}
```
> **Reference**: See how items map to fields in the [Zabbix to GraphQL Mapping](../../README.md#zabbix-to-graphql-mapping).
### 🤖 AI/MCP
AI agents can use the generalized `verifySchemaExtension.graphql` operations to automate this step:
- **CreateVerificationHost**: Automatically imports a test host with the correct `deviceType`.
- **VerifySchemaExtension**: Queries the newly created device to verify it is correctly mapped to the new type.
---
## 🍳 Recipe: Extending Schema with a Weather Sensor Device (Public API)
This recipe demonstrates how to extend the schema with a new device type that retrieves real-time weather data from a public API (Open-Meteo) using Zabbix HTTP agent items. This approach allows you to integrate external data sources into your Zabbix monitoring and expose them through the GraphQL API.
### 📋 Prerequisites
- Zabbix GraphQL API is running.
- The device has geo-coordinates set via user macros (`{$LAT}` and `{$LON}`).
### 🛠️ Step 1: Define the Schema Extension
Create a new `.graphql` file in `schema/extensions/` named `weather_sensor.graphql`.
```graphql
type WeatherSensorDevice implements Host & Device {
hostid: ID!
host: String!
deviceType: String
hostgroups: [HostGroup!]
name: String
tags: DeviceConfig
state: WeatherSensorState
}
type WeatherSensorState implements DeviceState {
operational: OperationalDeviceData
current: WeatherSensorValues
}
type WeatherSensorValues {
temperature: Float
streetConditionWarnings: String
}
```
### ⚙️ Step 2: Register the Resolver
Add the new type and schema to your `.env` file to enable the dynamic resolver:
```env
ADDITIONAL_SCHEMAS=./schema/extensions/weather_sensor.graphql
ADDITIONAL_RESOLVERS=WeatherSensorDevice
```
Restart the API server to apply the changes.
### 🚀 Step 3: Import the Weather Sensor Template
Use the `importTemplates` mutation to create the `WEATHER_SENSOR` template. This template uses an **HTTP agent** item to fetch data from Open-Meteo and **dependent items** to parse the results.
> **Reference**: See the [Sample: Weather Sensor Template Import](../../docs/queries/sample_import_weather_sensor_template.graphql) for the complete mutation and variables.
**Key Item Configuration**:
- **Master Item**: `weather.get` (HTTP Agent)
- URL: `https://api.open-meteo.com/v1/forecast?latitude={$LAT}&longitude={$LON}&current=temperature_2m,weather_code`
- **Dependent Item**: `state.current.temperature` (JSONPath: `$.current.temperature_2m`)
- **Dependent Item**: `state.current.streetConditionWarnings` (JavaScript mapping from `$.current.weather_code`)
### ✅ Step 4: Verification
Create a host, assign it macros for coordinates, and query its weather state.
1. **Create Host**:
```graphql
mutation CreateWeatherHost {
importHosts(hosts: [{
deviceKey: "Berlin-Weather-Sensor",
deviceType: "WeatherSensorDevice",
groupNames: ["External Sensors"],
templateNames: ["WEATHER_SENSOR"],
macros: [
{ macro: "{$LAT}", value: "52.52" },
{ macro: "{$LON}", value: "13.41" }
],
location: {
name: "Berlin"
}
}]) {
hostid
}
}
```
2. **Query Data**:
```graphql
query GetWeather {
allDevices(tag_deviceType: ["WeatherSensorDevice"]) {
... on WeatherSensorDevice {
name
state {
current {
temperature
streetConditionWarnings
}
}
}
}
}
```
---
## 🍳 Recipe: Provisioning a New Host
### 📋 Prerequisites
- A target Host Group exists in Zabbix.
- At least one Template exists in Zabbix.
### 🛠️ Step 1: Prepare the Host Object
Define the host name, groups, and templates to link.
### 🚀 Step 2: Execute `createHost` Mutation
For more details on the input fields, see the [Reference: createHost](../../schema/mutations.graphql).
### 🤖 AI/MCP
AI agents should prefer using the `importHosts` MCP tool for provisioning as it allows using names for host groups instead of IDs.
```graphql
mutation CreateNewHost($host: String!, $groups: [Int!]!, $templates: [Int], $templateNames: [String]) {
createHost(host: $host, hostgroupids: $groups, templateids: $templates, templateNames: $templateNames) {
hostids
error {
message
}
}
}
```
### ✅ Step 3: Verify Host Creation
Check if the host is correctly provisioned and linked to groups:
```graphql
query VerifyHost($host: String!) {
allHosts(filter_host: $host) {
hostid
host
hostgroups {
name
}
}
}
```
---
## 🍳 Recipe: Managing User Permissions
### 🛠️ Step 1: Create Permission Template Group
Create a template group with the prefix `Permissions/` in Zabbix (e.g. `Permissions/Read-Only-Access`).
### ⚙️ Step 2: Assign to User Group
In Zabbix, give a User Group `Read` access to this template group.
### ✅ Step 3: Verify via API
Verify that the current user has the expected permissions:
```graphql
query CheckMyPermissions {
hasPermissions(permissions: [
{ objectName: "Read-Only-Access", permission: READ }
])
}
```
---
## 🍳 Recipe: Bulk Import of Templates and Hosts
This recipe guides you through performing a mass import of multiple templates and hosts in a single operation.
### 🛠️ Step 1: Prepare Template Import
Use the `importTemplates` mutation. You can provide multiple template definitions in the `templates` array.
### ⚙️ Step 2: Prepare Host Import
Use the `importHosts` mutation. Link them to the newly imported templates using their names or IDs.
### 🚀 Step 3: Combined Operation (Optional)
You can execute both mutations in a single GraphQL request to ensure atomic-like provisioning of your infrastructure.
```graphql
mutation BulkProvisioning($templates: [CreateTemplate!]!, $hosts: [CreateHost!]!) {
importTemplates(templates: $templates) {
templateid
host
message
}
importHosts(hosts: $hosts) {
hostid
deviceKey
message
}
}
```
### ✅ Step 4: Verify Bulk Import
Verify that all entities were created and linked correctly:
```graphql
query VerifyBulkImport($pattern: String!) {
allHosts(name_pattern: $pattern) {
hostid
host
... on ZabbixHost {
parentTemplates {
name
}
}
}
}
```
For detailed examples of the input structures, refer to [Sample Import Templates](../../docs/queries/sample_import_templates_mutation.graphql) and [Sample Import Hosts](../../docs/queries/sample_import_hosts_mutation.graphql).
---
## 🍳 Recipe: Running the Smoketest via MCP
This recipe explains how to execute the end-to-end smoketest using the integrated MCP server. This is the fastest way to verify that your Zabbix GraphQL API is correctly connected to a Zabbix instance and all core features (Groups, Templates, Hosts) are working.
### 📋 Prerequisites
- Zabbix GraphQL API is running (`npm run start` or via Docker).
- Integrated MCP server is configured in your environment (e.g. registered in **Junie**).
### 🛠️ Step 1: Prompt the AI Agent
If you are using **Junie**, you can trigger the smoketest with a single natural language prompt.
**Prompt**:
> "Run the Zabbix smoketest using the MCP tool. Use 'SMOKE_HOST', 'SMOKE_TEMPLATE', and 'SMOKE_GROUP' for the entity names."
### 🚀 Step 2: Agent Execution
The agent will:
1. Identify the `RunSmoketest` tool from the MCP server.
2. Call the tool with the provided arguments.
3. Monitor the progress of each step (Create Template Group -> Create Template -> Create Host Group -> Create and Link Host -> Verify -> Cleanup).
### ✅ Step 3: Verification
The agent will report the success or failure of each step. You should see a final message indicating "Smoketest passed successfully".
---
## 🍳 Recipe: Cloning a Template with Items
This recipe guides you through cloning an existing Zabbix template, including all its items and their configurations, into a new template using the GraphQL API and MCP.
### 📋 Prerequisites
- Zabbix GraphQL API is running.
- You have the technical name of the source template.
### 🛠️ Step 1: Query the Source Template
Retrieve the source template's details and all its items.
**GraphQL Query**:
```graphql
query GetSourceTemplate($name: String!) {
templates(name_pattern: $name) {
host
name
items {
itemid
name
key_
type_int
value_type
status_int
history
delay
units
description
preprocessing
tags
master_itemid
}
}
}
```
### ⚙️ Step 2: Prepare the Clone Configuration
1. **Technical Names**: Choose a new technical name (`host`) and visible name (`name`) for the clone.
2. **Item Mapping**: Map the source items to the `items` array in the `importTemplates` mutation.
3. **Resolve Master Items**: For dependent items (where `master_itemid` > 0), find the source item with the matching `itemid` and use its `key_` as the `master_item.key` in the new item definition.
### 🚀 Step 3: Execute `importTemplates` Mutation
Execute the mutation to create the clone.
```graphql
mutation CloneTemplate($templates: [CreateTemplate!]!) {
importTemplates(templates: $templates) {
host
templateid
message
}
}
```
### ✅ Step 4: Verification
Verify that the cloned template exists and has the expected items.
```graphql
query VerifyClone($host: String!) {
templates(name_pattern: $host) {
templateid
host
items {
name
key_
}
}
}
```
### 🤖 AI/MCP
AI agents can use the following MCP tools to automate this:
- **GetTemplates**: To fetch the source template and its hierarchical item structure.
- **ImportTemplates**: To provision the new cloned template.
#### 🤖 Prompting Junie
You can ask **Junie** to automate the entire cloning process:
> "Using MCP, clone the template 'Generic SNMP' to a new template named 'Custom SNMP v2'. Ensure all items are copied and dependent items have their master item keys correctly mapped."
---
## 🍳 Recipe: Setting up GraphQL MCP for AI Agents
This recipe guides you through setting up the Model Context Protocol (MCP) server to enable AI agents like **Junie** or **Claude** to interact with your Zabbix data through the GraphQL API.
### 📋 Prerequisites
- **Zabbix GraphQL API**: Ensure the API is running (e.g. `npm run start`).
- **Docker**: Installed and running for the MCP server container.
### 🛠️ Step 1: Configure the MCP Server
Choose one of the following setups:
#### Setup A: JetBrains IDE (AI Chat & Junie)
Configure the IDE to use the GraphQL MCP server for both the built-in AI Chat and the **Junie agent**.
- **Prerequisite**: Generate the combined schema file (run this in your project root):
```bash
cat schema/*.graphql > schema.graphql
```
- **Open Settings**: Navigate to `File` > `Settings` (Windows/Linux) or `IntelliJ IDEA` > `Settings` (macOS).
- **Navigate to MCP**: Go to `Tools` > `AI Assistant` > `MCP Servers`.
- **Add Server**: Click the **+** button and configure:
- **Name**: `Zabbix GraphQL`
- **Type**: `Command`
- **Command**: `docker`
- **Arguments**:
```text
run -i --rm -v ${PROJECT_DIR}/mcp-config.yaml:/mcp-config.yaml -v ${PROJECT_DIR}/schema.graphql:/mcp-data/schema.graphql:ro -v ${PROJECT_DIR}/mcp/operations:/mcp/operations -e APOLLO_GRAPH_REF=local@main ghcr.io/apollographql/apollo-mcp-server:latest /mcp-config.yaml
```
#### Setup B: Claude Desktop
Connect Claude Desktop to the Zabbix GraphQL API by referring to the [Apollo GraphQL MCP Documentation](https://github.com/apollographql/apollo-mcp-server).
- **Prerequisite**: Generate the combined schema file (run this in your project root):
```bash
cat schema/*.graphql > schema.graphql
```
- **Edit Configuration**: Open the Claude Desktop configuration file (e.g. `%APPDATA%\Claude\claude_desktop_config.json` on Windows).
- **Add to mcpServers**: Insert the following configuration in the `mcpServers` section:
```json
{
"mcpServers": {
"zabbix-graphql": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-v", "C:/path/to/your/project/mcp-config.yaml:/mcp-config.yaml",
"-v", "C:/path/to/your/project/schema.graphql:/mcp-data/schema.graphql:ro",
"-v", "C:/path/to/your/project/mcp/operations:/mcp/operations",
"-e", "APOLLO_GRAPH_REF=local@main",
"ghcr.io/apollographql/apollo-mcp-server:latest",
"/mcp-config.yaml"
]
}
}
}
```
- **Restart Claude**: Fully restart the Claude Desktop application to apply the changes.
### 🚀 Step 2: Use an AI Agent
Provide the recipe to your AI agent and ask it to perform a task.
- **Example**: "Use MCP to list the configured Zabbix hosts".
### ✅ Step 3: Verify via Agent
Confirm that the agent can successfully interact with the API:
- Ask: "What is the current version of the Zabbix API?"
- The agent should use the `apiVersion` query and respond with the version number.
### 💡 Alternative: Using Pre-running MCP Server
If you already have the MCP server running locally (e.g. via `docker compose`), you can use a simpler configuration.
- **Sample Configuration**: See [.ai/mcp/mcp.json](../../.ai/mcp/mcp.json) for a sample that connects to a running MCP server via HTTP.
- **Usage**: Use this `url`-based configuration in your Claude Desktop or IDE settings instead of the `command`-based setup if you prefer to manage the MCP server lifecycle separately.
> **Reference**: For more details on the benefits of GraphQL for MCP, see the [MCP & Agent Integration Guide](./mcp.md).

View file

@ -0,0 +1,41 @@
## 🗂️ Hierarchical Data Mapping
The API automatically maps Zabbix items to nested GraphQL objects using the `createHierarchicalValueFieldResolver` function.
### How it Works
Zabbix item keys are used to define the structure in GraphQL. A dot (`.`) acts as a separator to represent nested object structures.
**Example:**
Zabbix item key `state.current.values.temperature` is automatically mapped to:
```json
{
"state": {
"current": {
"values": {
"temperature": 25.5
}
}
}
}
```
### Type Hinting
You can guide the type conversion by prepending a type hint and an underscore to the last token of the Zabbix item key:
* `json_`: Parses the value as a JSON object (useful for complex types).
* `str_`: Forces the value to be treated as a string.
* `bool_`: Forces the value to be treated as a boolean.
* `float_`: Forces the value to be treated as a number.
### Preconditions
1. **Key Naming**: Zabbix item keys (or tags) must match the GraphQL field names.
2. **Dot Separation**: Use dots to represent the desired hierarchy.
The `createHierarchicalValueFieldResolver` function (found in `../../src/api/resolver_helpers.ts`) dynamically creates these resolvers, eliminating the need for manual resolver definitions for each hierarchical field.
For more information, see the comments in `../../schema/device_value_commons.graphql` and `../../src/api/resolver_helpers.ts`.
See `../queries/sample_all_devices_query.graphql` for examples of hierarchical data in query results.

View file

@ -0,0 +1,65 @@
# Technical Maintenance Guide
This guide covers the technical aspects of maintaining and developing the Zabbix GraphQL API.
## 🛠️ Development & Maintenance Tasks
### Code Generation
The project uses [GraphQL Codegen](https://the-guild.dev/graphql/codegen) to generate TypeScript types from the GraphQL schema. This ensures type safety and consistency between the schema and the implementation.
- **Configuration**: `codegen.ts`
- **Generated Output**: `src/schema/generated/graphql.ts`
#### How to Regenerate Types
Whenever you modify any `.graphql` files in the `schema/` directory, you must regenerate the TypeScript types.
For a one-off update (e.g. in a script or before commit):
```bash
npx graphql-codegen --config codegen.ts
```
If you are a developer and want to watch for schema changes continuously:
```bash
npm run codegen
```
### Testing
We use [Jest](https://jestjs.io/) for unit and integration testing.
- **Test Directory**: `src/test/`
- **Execution**:
```bash
npm run test
```
#### Adding New Tests
- **Location**: Place new test files in `src/test/` with the `.test.ts` extension.
- **Coverage**: Ensure you cover both successful operations and error scenarios.
- **Test Specification**: Every new test must be documented in the [Test Specification](../tests.md).
- **Best Practice**: If you find a bug, first create a reproduction test in `src/test/` to verify the fix.
## 🔄 Updating Dependencies
To keep the project secure and up-to-date:
1. Check for updates: `npm outdated`
2. Update packages: `npm update`
3. Verify with tests: `npm run test`
## 🐳 Docker Image Maintenance
The `Dockerfile` uses a multi-stage build to keep the production image small and secure.
- **Base Image**: Node 24 (LTS)
- **Build Argument**: `API_VERSION` (used to embed the version into the image)
To build a fresh image locally:
```bash
docker build -t zabbix-graphql-api --build-arg API_VERSION=$(git describe --tags --always) .
```
---
**Related Reference**: [Project Configuration Reference](../../README.md#configuration)
**Related Cookbook**: [Extending the Schema](./cookbook.md#recipe-extending-schema-with-a-new-device-type)

104
docs/howtos/mcp.md Normal file
View file

@ -0,0 +1,104 @@
## 🤖 Model Context Protocol (MCP) Integration
The Zabbix GraphQL API supports the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/), enabling Large Language Models (LLMs) to interact directly with your Zabbix data through a standardized interface.
### Overview
By leveraging GraphQL, the API provides a strongly-typed and introspectable interface that is ideal for MCP. This allows LLMs to:
- Discover available queries and mutations.
- Understand the data structures (hosts, items, templates, etc.).
- Execute operations to retrieve or modify Zabbix data based on natural language prompts.
### Running Apollo MCP Server with Docker Compose
You can start both the Zabbix GraphQL API and the Apollo MCP Server using Docker Compose. This setup uses a local `mcp-config.yaml` and a generated `schema.graphql`.
- **Prerequisites**: Ensure you have a `.env` file with the required Zabbix connection details.
- **Prepare Operations**: Create the operations directory if it doesn't exist:
```bash
mkdir -p mcp/operations
```
- **Start Services**:
```bash
docker compose up -d
```
This will:
- Start the `zabbix-graphql-api` on `http://localhost:4001/graphql` (internal port 4000).
- Start the `apollo-mcp-server` on `http://localhost:3000/mcp` (mapped from internal port 8000), configured to connect to the local API via `mcp-config.yaml`.
### Using with Claude Desktop
To use this integration with Claude Desktop, add the following configuration to your Claude Desktop config file (typically `claude_desktop_config.json`).
- **Prerequisite**: Generate the combined schema file in your project root:
```bash
cat schema/*.graphql > schema.graphql
```
- **Configuration**:
```json
{
"mcpServers": {
"zabbix-graphql": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-v", "/path/to/your/project/mcp-config.yaml:/mcp-config.yaml",
"-v", "/path/to/your/project/schema.graphql:/mcp-data/schema.graphql:ro",
"-v", "/path/to/your/project/mcp/operations:/mcp/operations",
"-e", "APOLLO_GRAPH_REF=local@main",
"ghcr.io/apollographql/apollo-mcp-server:latest",
"/mcp-config.yaml"
]
}
}
}
```
**Note**: Ensure the `zabbix-graphql-api` is running and accessible. If running locally, you might need to use `host.docker.internal:4001/graphql` in your `mcp-config.yaml` to allow the containerized MCP server to reach your host.
#### 💡 Sample Configuration (Alternative)
If you prefer to run the MCP server manually or via Docker Compose (as described above), you can use a HTTP-based configuration instead of the `command` execution. See [.ai/mcp/mcp.json](../../.ai/mcp/mcp.json) for a sample configuration that connects to the server running on `localhost:3000`.
### 🤖 Prompting with Junie (Integrated MCP)
When working with **Junie** in this repository, the MCP server is already registered as an integrated tool. You can simply ask Junie to perform tasks using the Zabbix GraphQL API by referring to the available MCP tools.
#### 🍳 Example: Running a Smoketest
To verify the system end-to-end, you can prompt Junie:
> "Run the Zabbix smoketest using the MCP tool. Use 'JUNIE_MCP_HOST', 'JUNIE_MCP_TEMPLATE', and 'JUNIE_MCP_GROUP' as names."
#### 🍳 Example: Cloning a Template
To clone a template, you can provide a higher-level instruction:
> "Using MCP, clone the template 'Generic SNMP' to a new template named 'Custom SNMP v2'. Ensure all items are copied and dependent items have their master item keys correctly mapped."
Junie will then:
- Use `GetTemplates` to fetch the source template structure.
- Map the items and resolve master-dependent relationships.
- Use `ImportTemplates` to create the new cloned template.
### Benefits of GraphQL-enabled MCP over REST
Integrating via GraphQL offers significant advantages for AI agents and MCP compared to the traditional Zabbix JSON-RPC (REST-like) API:
- **Introspection & Discovery**: Unlike REST, GraphQL is natively introspectable. An AI agent can query the schema itself to discover all available types, fields, and operations. This allows agents to "learn" the API capabilities without manual documentation parsing.
- **Strong Typing**: The schema provides explicit types for every field. AI agents can use this to validate their own generated queries and understand the exact data format expected or returned, reducing errors in agent-driven actions.
- **Precision (Over-fetching/Under-fetching)**: In REST, endpoints often return fixed data structures, leading to token waste (over-fetching) or requiring multiple round-trips (under-fetching). With GraphQL, the agent requests exactly the fields it needs, which is crucial for staying within LLM context window limits and reducing latency.
- **Single Endpoint**: AI agents only need to know one endpoint. They don't have to manage a complex tree of URL paths and HTTP methods; they simply send their intent as a GraphQL operation.
- **Complex Relationships**: Agents can navigate complex Zabbix relationships (e.g. Host -> Items -> History) in a single request, which is much more intuitive for LLMs than orchestrating multiple REST calls.
- **Self-Documenting**: Descriptive comments in the SDL are automatically exposed to the agent, providing immediate context for what each field represents.
### AI-Based Test Generation via Cookbook
The MCP server can be used in conjunction with the [**Cookbook**](./cookbook.md) to automate the generation of test cases. By providing a cookbook "recipe" to an LLM with access to the `zabbix-graphql` MCP server, the LLM can:
- Analyze the step-by-step instructions in the recipe.
- Use the MCP server's tools to inspect the current Zabbix state and schema.
- Generate and execute the necessary GraphQL operations to fulfill the recipe's task.
- Verify the outcome and suggest assertions for a formal test script.
Example prompt for an LLM:
> "Using the `zabbix-graphql` MCP server, follow the 'Provisioning a New Host' recipe from the cookbook. Create a host named 'Test-Host-01' in the 'Linux servers' group and link the 'ICMP Ping' template."

View file

@ -0,0 +1,49 @@
## 🔐 Roles and Permissions Extension
The API implements a permission system using Zabbix template groups:
### Permission Template Groups
- Template groups with prefix `Permissions/` (configurable via `ZABBIX_PERMISSION_TEMPLATE_GROUP_NAME_PREFIX`) are used for permissions
- Users gain permissions by being assigned to user groups that have access to these permission template groups
### Available Permissions
The system supports three permission levels defined in `../../schema/api_commons.graphql`:
- `DENY`: Explicitly denies access (supersedes other permissions)
- `READ`: Allows viewing/reading access
- `READ_WRITE`: Allows both reading and writing (implies READ permission)
### Permission Object Names
Permission object names map to Zabbix template group paths: `Permissions/{objectName}`
### GraphQL Permission Queries
```graphql
# Check if current user has specific permissions
query HasPermissions {
hasPermissions(permissions: [
{ objectName: "hosts", permission: READ },
{ objectName: "templates", permission: READ_WRITE }
])
}
# Get all user permissions
query GetUserPermissions {
userPermissions(objectNames: ["hosts", "templates"])
}
```
### Setting Up Permissions
1. Create template groups with the prefix `Permissions/` (e.g. `Permissions/hosts`, `Permissions/templates`)
2. Assign these template groups to user groups in Zabbix with appropriate permission levels
3. Users in those user groups will inherit the permissions
### Detailed Permission Usage Examples
For comprehensive examples of permission usage patterns, see `../../schema/api_commons.graphql` which contains detailed documentation in the `PermissionRequest` input type comments, including real-world examples of how to model permissions for buttons, status controls, and application features.
See also `../queries/sample_import_permissions_template_groups_mutation.graphql` for examples of importing permission template groups.

52
docs/howtos/schema.md Normal file
View file

@ -0,0 +1,52 @@
## 📊 Schema and Schema Extension
### Main Schema Structure
The GraphQL schema is located in the `../../schema/` directory and consists of:
- `queries.graphql` - Query definitions (see detailed documentation in file comments)
- `mutations.graphql` - Mutation definitions (see detailed documentation in file comments)
- `devices.graphql` - Device-related types (see detailed documentation in file comments)
- `zabbix.graphql` - Zabbix-specific types (see detailed documentation in file comments)
- `device_value_commons.graphql` - Common value types (see detailed documentation in file comments)
- `api_commons.graphql` - Common API types and permission system (see detailed documentation in file comments)
- `extensions/` - Custom device type extensions
For comprehensive understanding of each operation, read the detailed comments in the respective schema files.
### Zabbix to GraphQL Mapping
For a detailed mapping of Zabbix entities to GraphQL types, please refer to the [Zabbix to GraphQL Mapping](../../README.md#zabbix-to-graphql-mapping) section in the main README.
### Zabbix Entity Relationships
- **Host Groups**: Organize hosts and templates hierarchically; represented as `HostGroup` objects in GraphQL
- **Templates**: Contain items and other configuration that can be applied to hosts; linked via template groups
- **Items**: Individual metrics collected from hosts; automatically mapped to nested GraphQL fields based on their key names
- **Tags**: Metadata associated with hosts/templates; used for classification and filtering
### Location Type Usage
The `Location` type represents geographical information from Zabbix host inventory:
- **Fields**: Includes `name`, `location_lat`, `location_lon`, and other inventory attributes
- **Usage**: Available through the `locations` query and as part of host/device objects
- **Access**: Retrieved via the `getLocations` method in the Zabbix API datasource
### Dynamic Schema Extension
Extend the schema without code changes using environment variables:
```bash
ADDITIONAL_SCHEMAS=./schema/extensions/display_devices.graphql,./schema/extensions/location_tracker_devices.graphql
ADDITIONAL_RESOLVERS=SinglePanelDevice,FourPanelDevice,DistanceTrackerDevice
```
This enables runtime schema extension for custom device types without modifying the core code.
### Sample Operations
For practical examples of schema usage, see the sample files in the `../queries/` directory:
- `../queries/sample_all_devices_query.graphql` - Example of querying all devices
- `../queries/sample_import_templates_mutation.graphql` - Example of importing templates
- `../queries/sample_import_host_groups_mutation.graphql` - Example of importing host groups

36
docs/howtos/tags.md Normal file
View file

@ -0,0 +1,36 @@
## 🏷️ Zabbix Tags Usage
Zabbix tags are used for:
- Device classification (`deviceType` tag)
- Host categorization (`hostType` tag)
- Custom metadata storage
- Permission assignment through template groups
### The `hostType` Tag
The `hostType` tag is used to categorize hosts and templates. This allows the API to provide default filters for specific application domains or device categories.
To classify a host or a template, add a tag in Zabbix:
* **Tag Name**: `hostType`
* **Tag Value**: A string representing the category (e.g. `Roadwork/Devices`, `SmartCity/Sensors`).
This tag can be defined directly on the host or on a template (where linked hosts will inherit it).
### Default Filtering with `HOST_TYPE_FILTER_DEFAULT`
By configuring the `HOST_TYPE_FILTER_DEFAULT` environment variable, you can set a global default for the `allHosts` and `allDevices` queries.
* If `HOST_TYPE_FILTER_DEFAULT=Roadwork/Devices` is set, `allHosts` will only return hosts with that tag value.
* This default can be overridden in the GraphQL query by passing the `tag_hostType` argument.
### Search Filtering with `HOST_GROUP_FILTER_DEFAULT`
The `HOST_GROUP_FILTER_DEFAULT` variable provides a default search pattern for the `allHostGroups` query, useful for restricting the visible host group hierarchy.
* **Overriding**: Providing the `search_name` argument in the `allHostGroups` query overrides this default.
* **Wildcards**: The `search_name` parameter supports the `*` wildcard. For example, `Roadwork/Devices/*` finds all subgroups within that path.
For more information, see the comments in `../../schema/devices.graphql` and `../../schema/zabbix.graphql`.
See `../queries/sample_all_hosts_query.graphql` and `../queries/sample_all_devices_query.graphql` for examples.

28
docs/queries/README.md Normal file
View file

@ -0,0 +1,28 @@
# Sample Queries & Mutations
This directory contains practical examples of GraphQL operations for the Zabbix GraphQL API. You can use these as templates for your own automation or integration tasks.
## 📁 Available Samples
### 🖥️ Hosts
- [Query All Hosts](./sample_all_hosts_query.graphql): Retrieve basic host information and inventory.
- [Import Hosts](./sample_import_hosts_mutation.graphql): Create or update multiple hosts with tags and group assignments.
- [Query All Devices](./sample_all_devices_query.graphql): Query specialized devices using the `allDevices` query.
### 📄 Templates
- [Query Templates](./sample_templates_query.graphql): List available templates and their items.
- [Import Templates](./sample_import_templates_mutation.graphql): Create or update complex templates with item definitions and preprocessing.
- [Import Distance Tracker Template](./sample_import_distance_tracker_template.graphql): Example of importing a template for a schema extension.
- [Delete Templates](./sample_delete_templates_mutation.graphql): Remove templates by ID or name pattern.
### 📂 Template Groups
- [Import Host Template Groups](./sample_import_host_template_groups_mutation.graphql): Create groups specifically for host templates.
- [Import Permissions Template Groups](./sample_import_permissions_template_groups_mutation.graphql): Create groups for the permission system.
- [Delete Template Groups](./sample_delete_template_groups_mutation.graphql): Remove template groups by ID or name pattern.
### 🔐 User Rights
- [Export User Rights](./sample_export_user_rights_query.graphql): Export existing user roles and groups for auditing or migration.
- [Import User Rights](./sample_import_user_rights_mutation.graphql): Provision user roles and group permissions at scale.
## 🍳 Related Recipes
For step-by-step guides on how to use these operations in common scenarios, see the [Cookbook](../howtos/cookbook.md).

View file

@ -0,0 +1,27 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/host/get
### Query
```graphql
query GetAllDevices($name_pattern: String) {
allDevices(name_pattern: $name_pattern) {
hostid
host
name
deviceType
state {
operational {
temperature
voltage
signalstrength
}
}
}
}
```
### Variables
```json
{
"name_pattern": "Device%"
}
```

View file

@ -0,0 +1,18 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/hostgroup/get
### Query
```graphql
query GetAllHostGroups($search_name: String) {
allHostGroups(search_name: $search_name) {
groupid
name
}
}
```
### Variables
```json
{
"search_name": "Zabbix%"
}
```

View file

@ -0,0 +1,34 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/host/get
### Query
```graphql
query GetAllHosts($name_pattern: String, $groupids: [Int!]) {
allHosts(name_pattern: $name_pattern, groupids: $groupids) {
hostid
host
name
deviceType
hostgroups {
groupid
name
}
... on ZabbixHost {
tags
items {
itemid
name
key_
lastvalue
}
}
}
}
```
### Variables
```json
{
"name_pattern": "Linux%",
"groupids": [2]
}
```

View file

@ -0,0 +1,18 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/templategroup/get
### Query
```graphql
query GetAllTemplateGroups($name_pattern: String) {
allTemplateGroups(name_pattern: $name_pattern) {
groupid
name
}
}
```
### Variables
```json
{
"name_pattern": "Templates%"
}
```

View file

@ -0,0 +1,23 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/host/create
### Query
```graphql
mutation CreateHost($host: String!, $hostgroupids: [Int!]!, $templateids: [Int!]!) {
createHost(
host: $host,
hostgroupids: $hostgroupids,
templateids: $templateids
) {
hostids
}
}
```
### Variables
```json
{
"host": "Linux server",
"hostgroupids": [50],
"templateids": [20045]
}
```

View file

@ -0,0 +1,18 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/templategroup/delete
### Query
```graphql
mutation DeleteTemplateGroups($groupids: [Int!]) {
deleteTemplateGroups(groupids: $groupids) {
id
message
}
}
```
### Variables
```json
{
"groupids": [10, 11]
}
```

View file

@ -0,0 +1,18 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/template/delete
### Query
```graphql
mutation DeleteTemplates($templateids: [Int!]) {
deleteTemplates(templateids: $templateids) {
id
message
}
}
```
### Variables
```json
{
"templateids": [10001, 10002]
}
```

View file

@ -0,0 +1,24 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/history/get
### Query
```graphql
query ExportHostValueHistory($host_filter: [String!], $itemKey_filter: [String!], $limit: Int) {
exportHostValueHistory(
host_filter: $host_filter,
itemKey_filter: $itemKey_filter,
limit: $limit,
type: FLOAT
) {
result
}
}
```
### Variables
```json
{
"host_filter": ["Linux server"],
"itemKey_filter": ["system.cpu.load[all,avg1]"],
"limit": 10
}
```

View file

@ -0,0 +1,28 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/usergroup/get
### Query
```graphql
query ExportUserRights($name_pattern: String) {
exportUserRights(name_pattern: $name_pattern) {
userGroups {
usrgrpid
name
hostgroup_rights {
name
permission
}
}
userRoles {
roleid
name
}
}
}
```
### Variables
```json
{
"name_pattern": "Zabbix%"
}
```

View file

@ -0,0 +1,20 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/usergroup/get
### Query
```graphql
query HasPermissions($permissions: [PermissionRequest!]!) {
hasPermissions(permissions: $permissions)
}
```
### Variables
```json
{
"permissions": [
{
"objectName": "Read-Only-Access",
"permission": "READ"
}
]
}
```

View file

@ -0,0 +1,26 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/hostgroup/create
### Query
```graphql
mutation ImportHostGroups($hostGroups: [CreateHostGroup!]!) {
importHostGroups(hostGroups: $hostGroups) {
groupName
groupid
message
}
}
```
### Variables
```json
{
"hostGroups": [
{
"groupName": "New Host Group 1"
},
{
"groupName": "New Host Group 2"
}
]
}
```

View file

@ -0,0 +1,30 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/host/create
### Query
```graphql
mutation ImportHosts($hosts: [CreateHost!]!) {
importHosts(hosts: $hosts) {
hostid
deviceKey
message
}
}
```
### Variables
```json
{
"hosts": [
{
"deviceKey": "Host 1",
"deviceType": "GenericDevice",
"groupNames": ["Zabbix servers"]
},
{
"deviceKey": "Host 2",
"deviceType": "GenericDevice",
"groupNames": ["Zabbix servers"]
}
]
}
```

View file

@ -0,0 +1,23 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/templategroup/create
### Query
```graphql
mutation ImportTemplateGroups($templateGroups: [CreateTemplateGroup!]!) {
importTemplateGroups(templateGroups: $templateGroups) {
groupName
groupid
message
}
}
```
### Variables
```json
{
"templateGroups": [
{
"groupName": "New Template Group 1"
}
]
}
```

View file

@ -0,0 +1,32 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/template/create
### Query
```graphql
mutation ImportTemplates($templates: [CreateTemplate!]!) {
importTemplates(templates: $templates) {
templateid
host
message
}
}
```
### Variables
```json
{
"templates": [
{
"host": "New Template 1",
"groupNames": ["Templates/Operating systems"],
"items": [
{
"name": "Custom item",
"key": "custom.item",
"value_type": 3,
"history": "90d"
}
]
}
]
}
```

View file

@ -0,0 +1,45 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/usergroup/create
### Query
```graphql
mutation ImportUserRights($input: UserRightsInput!, $dryRun: Boolean!) {
importUserRights(input: $input, dryRun: $dryRun) {
userGroups {
id
name
message
}
userRoles {
id
name
message
}
}
}
```
### Variables
```json
{
"dryRun": true,
"input": {
"userGroups": [
{
"name": "New User Group",
"hostgroup_rights": [
{
"name": "Zabbix servers",
"permission": "READ"
}
]
}
],
"userRoles": [
{
"name": "New Role",
"type": 1
}
]
}
}
```

View file

@ -0,0 +1,19 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/host/get
### Query
```graphql
query GetLocations($name_pattern: String) {
locations(name_pattern: $name_pattern) {
name
latitude
longitude
}
}
```
### Variables
```json
{
"name_pattern": "Riga%"
}
```

View file

@ -0,0 +1,16 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/user/login
### Query
```graphql
query Login($username: String!, $password: String!) {
login(username: $username, password: $password)
}
```
### Variables
```json
{
"username": "Admin",
"password": "zabbix_password"
}
```

View file

@ -0,0 +1,13 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/user/logout
### Query
```graphql
query Logout {
logout
}
```
### Variables
```json
{}
```

View file

@ -0,0 +1,18 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/template/get
### Query
```graphql
query GetTemplates($name_pattern: String) {
templates(name_pattern: $name_pattern) {
templateid
name
}
}
```
### Variables
```json
{
"name_pattern": "Template OS%"
}
```

View file

@ -0,0 +1,18 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/user/get
### Query
```graphql
query GetUserPermissions($objectNames: [String!]) {
userPermissions(objectNames: $objectNames) {
objectName
permission
}
}
```
### Variables
```json
{
"objectNames": ["Read-Only-Access"]
}
```

View file

@ -0,0 +1,13 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/apiinfo/version
### Query
```graphql
query GetZabbixVersion {
zabbixVersion
}
```
### Variables
```json
{}
```

View file

@ -21,7 +21,7 @@ mutation ImportDistanceTrackerTemplate($templates: [CreateTemplate!]!) {
### Variables
The following sample defines the `DISTANCE_TRACKER` template. Note the `deviceType` tag set to `DistanceTrackerDevice`, which instructs the GraphQL API to resolve this host using the specialized `DistanceTrackerDevice` type.
The item keys use the `json_` prefix where appropriate (e.g., `state.current.json_distances`) to ensure that the JSON strings received from Zabbix are automatically parsed into objects/arrays by the GraphQL resolver.
The item keys use the `json_` prefix where appropriate (e.g. `state.current.json_distances`) to ensure that the JSON strings received from Zabbix are automatically parsed into objects/arrays by the GraphQL resolver.
```json
{

View file

@ -0,0 +1,86 @@
### Mutation
Use this mutation to import a template specifically designed to work with the `WeatherSensorDevice` type. This template retrieves real-time weather data from the public Open-Meteo API using a Zabbix HTTP agent item.
```graphql
mutation ImportWeatherSensorTemplate($templates: [CreateTemplate!]!) {
importTemplates(templates: $templates) {
host
templateid
message
error {
message
code
}
}
}
```
### Variables
The following variables define the `WEATHER_SENSOR` template. It uses the host's user macros (`{$LAT}` and `{$LON}`) to fetch localized weather data.
```json
{
"templates": [
{
"host": "WEATHER_SENSOR",
"name": "Weather Sensor API Template",
"groupNames": ["Templates/External APIs"],
"tags": [
{ "tag": "deviceType", "value": "WeatherSensorDevice" }
],
"macros": [
{ "macro": "{$LAT}", "value": "52.52" },
{ "macro": "{$LON}", "value": "13.41" }
],
"items": [
{
"name": "Open-Meteo API Fetch",
"type": 19,
"key": "weather.get",
"value_type": 4,
"history": "0",
"delay": "1m",
"url": "https://api.open-meteo.com/v1/forecast?latitude={$LAT}&longitude={$LON}&current=temperature_2m,weather_code",
"description": "Master item fetching weather data from Open-Meteo based on host coordinates."
},
{
"name": "Current Temperature",
"type": 18,
"key": "state.current.temperature",
"value_type": 0,
"history": "7d",
"master_item": {
"key": "weather.get"
},
"preprocessing": [
{
"type": 12,
"params": ["$.current.temperature_2m"]
}
]
},
{
"name": "Street Condition Warnings",
"type": 18,
"key": "state.current.streetConditionWarnings",
"value_type": 4,
"history": "7d",
"master_item": {
"key": "weather.get"
},
"preprocessing": [
{
"type": 12,
"params": ["$.current.weather_code"]
},
{
"type": 21,
"params": ["var codes = {0:\"Clear\",1:\"Mainly Clear\",2:\"Partly Cloudy\",3:\"Overcast\",45:\"Fog\",48:\"Depositing Rime Fog\",51:\"Light Drizzle\",53:\"Moderate Drizzle\",55:\"Dense Drizzle\",56:\"Light Freezing Drizzle\",57:\"Dense Freezing Drizzle\",61:\"Slight Rain\",63:\"Moderate Rain\",65:\"Heavy Rain\",66:\"Light Freezing Rain\",67:\"Heavy Freezing Rain\",71:\"Slight Snow Fall\",73:\"Moderate Snow Fall\",75:\"Heavy Snow Fall\",77:\"Snow Grains\",80:\"Slight Rain Showers\",81:\"Moderate Rain Showers\",82:\"Violent Rain Showers\",85:\"Slight Snow Showers\",86:\"Heavy Snow Showers\",95:\"Thunderstorm\",96:\"Thunderstorm with Slight Hail\",99:\"Thunderstorm with Heavy Hail\"}; var code = parseInt(value); var warning = codes[code] || \"Unknown\"; if ([56, 57, 66, 67, 71, 73, 75, 77, 85, 86].indexOf(code) !== -1) { return \"WARNING: Slippery Roads (Snow/Ice)\"; } if ([51, 53, 55, 61, 63, 65, 80, 81, 82].indexOf(code) !== -1) { return \"CAUTION: Wet Roads\"; } return warning;"]
}
]
}
]
}
]
}
```

138
docs/tests.md Normal file
View file

@ -0,0 +1,138 @@
# Test Specification
This document outlines the test cases and coverage for the Zabbix GraphQL API.
## 📂 Test Categories
- **Unit Tests**: Verify individual functions, classes, or logic in isolation. All external dependencies (Zabbix API, Config) are mocked to ensure the test is fast and deterministic. These tests are executed on each build.
- *Reference*: `src/test/host_importer.test.ts`, `src/test/template_query.test.ts`
- **Integration Tests**: Test the interaction between multiple internal components. Typically, these tests use a mock Apollo Server to execute actual GraphQL operations against the resolvers and data sources, with the Zabbix API mocked at the network layer. These tests are executed on each build.
- *Reference*: `src/test/host_integration.test.ts`, `src/test/user_rights_integration.test.ts`
- **End-to-End (E2E) Tests**: Validate complete, multi-step business workflows from start to finish (e.g., a full import-verify-cleanup cycle). These tests are executed against a real, running Zabbix instance to ensure the entire system achieves the desired business outcome. These tests are triggered after startup or on demand via GraphQL/MCP endpoints.
- *Reference*: `mcp/operations/runSmoketest.graphql` (executed via MCP)
## 🧪 Test Case Definitions
### Host Management
- **TC-HOST-01**: Query all hosts using sample query.
- **TC-HOST-02**: Import hosts using sample mutation.
- **TC-HOST-03**: Import host groups and create new hierarchy.
- **TC-HOST-04**: Import basic host.
- **TC-HOST-05**: Query all hosts with name pattern.
- **TC-HOST-06**: Query all devices by host ID.
- **TC-HOST-07**: Query all host groups with search pattern.
- **TC-HOST-08**: Query host groups using default search pattern.
- **TC-HOST-09**: Query locations.
### Template Management
- **TC-TEMP-01**: Import templates using sample query and variables.
- **TC-TEMP-02**: Import and export templates comparison.
- **TC-TEMP-03**: Import and export template groups comparison.
- **TC-TEMP-04**: Query all templates.
- **TC-TEMP-05**: Filter templates by host IDs.
- **TC-TEMP-06**: Filter templates by name pattern.
- **TC-TEMP-07**: Filter templates by name pattern with wildcard.
- **TC-TEMP-08**: Import template groups (new group).
- **TC-TEMP-09**: Import template groups (existing group).
- **TC-TEMP-10**: Import basic template.
- **TC-TEMP-11**: Import templates with items, linked templates, and dependent items.
- **TC-TEMP-12**: Import templates query validation.
- **TC-TEMP-13**: Import templates error handling (data field inclusion).
- **TC-TEMP-14**: Delete templates successfully.
- **TC-TEMP-15**: Delete templates error handling.
- **TC-TEMP-16**: Delete templates by name pattern.
- **TC-TEMP-17**: Delete templates with merged IDs and name pattern.
- **TC-TEMP-18**: Delete template groups successfully.
- **TC-TEMP-19**: Delete template groups error handling.
- **TC-TEMP-20**: Delete template groups by name pattern.
### User Rights and Permissions
- **TC-AUTH-01**: Export user rights.
- **TC-AUTH-02**: Query user permissions.
- **TC-AUTH-03**: Check if user has permissions.
- **TC-AUTH-04**: Import user rights.
- **TC-AUTH-05**: Import user rights using sample mutation.
### System and Configuration
- **TC-CONF-01**: Schema loader uses Config variables.
- **TC-CONF-02**: Zabbix API constants derived from Config.
- **TC-CONF-03**: Logger levels initialized from Config.
- **TC-CONF-04**: API version query.
- **TC-CONF-05**: Login query.
- **TC-CONF-06**: Logout query.
- **TC-CONF-07**: Parse Zabbix arguments.
### Documentation and MCP
- **TC-DOCS-01**: Validate all Zabbix documentation sample queries.
- **TC-MCP-01**: Validate all MCP operation files against the schema.
### End-to-End (E2E) Tests
- **TC-E2E-01**: Run a complete smoketest using MCP (creates template, group, and host, verifies, and cleans up).
- **TC-E2E-02**: Run all regression tests to verify critical system behavior and prevent known issues.
#### Currently Contained Regression Tests
The `runAllRegressionTests` mutation (TC-E2E-02) executes the following checks:
- **Host without items**: Verifies that hosts created without any items or linked templates can be successfully queried by the system. This ensures that the hierarchical mapping and resolvers handle empty item lists gracefully.
- **Locations query argument order**: Verifies that the `locations` query correctly handles its parameters and successfully contacts the Zabbix API without session errors (verifying the fix for argument order in the resolver).
- **Template technical name lookup**: Verifies that templates can be correctly identified by their technical name (`host` field) when linking them to hosts during import.
- **HTTP Agent URL support**: Verifies that templates containing HTTP Agent items with a configured URL can be imported successfully (verifying the addition of the `url` field to `CreateTemplateItem`).
- **Host retrieval and visibility**: Verifies that newly imported hosts are immediately visible and retrievable via the `allHosts` query, including correctly delivered assigned templates and assigned host groups (verifying the fix for `output` fields in the host query data source).
## ✅ Test Coverage Checklist
| ID | Test Case | Category | Technology | Code Link |
|:---|:---|:---|:---|:---|
| TC-HOST-01 | Query allHosts using sample | Integration | Jest | [src/test/host_integration.test.ts](../src/test/host_integration.test.ts) |
| TC-HOST-02 | Import hosts using sample | Integration | Jest | [src/test/host_integration.test.ts](../src/test/host_integration.test.ts) |
| TC-HOST-03 | importHostGroups - create new hierarchy | Unit | Jest | [src/test/host_importer.test.ts](../src/test/host_importer.test.ts) |
| TC-HOST-04 | importHosts - basic host | Unit | Jest | [src/test/host_importer.test.ts](../src/test/host_importer.test.ts) |
| TC-HOST-05 | allHosts query | Unit | Jest | [src/test/host_query.test.ts](../src/test/host_query.test.ts) |
| TC-HOST-06 | allDevices query | Unit | Jest | [src/test/host_query.test.ts](../src/test/host_query.test.ts) |
| TC-HOST-07 | allHostGroups query | Unit | Jest | [src/test/host_query.test.ts](../src/test/host_query.test.ts) |
| TC-HOST-08 | allHostGroups query - default pattern | Unit | Jest | [src/test/host_query.test.ts](../src/test/host_query.test.ts) |
| TC-HOST-09 | locations query | Unit | Jest | [src/test/host_query.test.ts](../src/test/host_query.test.ts) |
| TC-TEMP-01 | Import templates using sample | Integration | Jest | [src/test/template_integration.test.ts](../src/test/template_integration.test.ts) |
| TC-TEMP-02 | Import and Export templates comparison | Integration | Jest | [src/test/template_integration.test.ts](../src/test/template_integration.test.ts) |
| TC-TEMP-03 | Import and Export template groups comparison | Integration | Jest | [src/test/template_integration.test.ts](../src/test/template_integration.test.ts) |
| TC-TEMP-04 | templates query - returns all | Unit | Jest | [src/test/template_query.test.ts](../src/test/template_query.test.ts) |
| TC-TEMP-05 | templates query - filters by hostids | Unit | Jest | [src/test/template_query.test.ts](../src/test/template_query.test.ts) |
| TC-TEMP-06 | templates query - filters by name_pattern | Unit | Jest | [src/test/template_query.test.ts](../src/test/template_query.test.ts) |
| TC-TEMP-07 | templates query - name_pattern wildcard | Unit | Jest | [src/test/template_query.test.ts](../src/test/template_query.test.ts) |
| TC-TEMP-08 | importTemplateGroups - create new | Unit | Jest | [src/test/template_importer.test.ts](../src/test/template_importer.test.ts) |
| TC-TEMP-09 | importTemplateGroups - group exists | Unit | Jest | [src/test/template_importer.test.ts](../src/test/template_importer.test.ts) |
| TC-TEMP-10 | importTemplates - basic template | Unit | Jest | [src/test/template_importer.test.ts](../src/test/template_importer.test.ts) |
| TC-TEMP-11 | importTemplates - complex template | Unit | Jest | [src/test/template_importer.test.ts](../src/test/template_importer.test.ts) |
| TC-TEMP-12 | importTemplates - template query | Unit | Jest | [src/test/template_importer.test.ts](../src/test/template_importer.test.ts) |
| TC-TEMP-13 | importTemplates - error data field | Unit | Jest | [src/test/template_importer.test.ts](../src/test/template_importer.test.ts) |
| TC-TEMP-14 | deleteTemplates - success | Unit | Jest | [src/test/template_deleter.test.ts](../src/test/template_deleter.test.ts) |
| TC-TEMP-15 | deleteTemplates - error | Unit | Jest | [src/test/template_deleter.test.ts](../src/test/template_deleter.test.ts) |
| TC-TEMP-16 | deleteTemplates - by name_pattern | Unit | Jest | [src/test/template_deleter.test.ts](../src/test/template_deleter.test.ts) |
| TC-TEMP-17 | deleteTemplates - merged IDs | Unit | Jest | [src/test/template_deleter.test.ts](../src/test/template_deleter.test.ts) |
| TC-TEMP-18 | deleteTemplateGroups - success | Unit | Jest | [src/test/template_deleter.test.ts](../src/test/template_deleter.test.ts) |
| TC-TEMP-19 | deleteTemplateGroups - error | Unit | Jest | [src/test/template_deleter.test.ts](../src/test/template_deleter.test.ts) |
| TC-TEMP-20 | deleteTemplateGroups - by name_pattern | Unit | Jest | [src/test/template_deleter.test.ts](../src/test/template_deleter.test.ts) |
| TC-AUTH-01 | exportUserRights query | Unit | Jest | [src/test/user_rights.test.ts](../src/test/user_rights.test.ts) |
| TC-AUTH-02 | userPermissions query | Unit | Jest | [src/test/user_rights.test.ts](../src/test/user_rights.test.ts) |
| TC-AUTH-03 | hasPermissions query | Unit | Jest | [src/test/user_rights.test.ts](../src/test/user_rights.test.ts) |
| TC-AUTH-04 | importUserRights mutation | Unit | Jest | [src/test/user_rights.test.ts](../src/test/user_rights.test.ts) |
| TC-AUTH-05 | Import user rights using sample | Integration | Jest | [src/test/user_rights_integration.test.ts](../src/test/user_rights_integration.test.ts) |
| TC-CONF-01 | schema_loader uses Config variables | Unit | Jest | [src/test/schema_config.test.ts](../src/test/schema_config.test.ts) |
| TC-CONF-02 | constants are derived from Config | Unit | Jest | [src/test/zabbix_api_config.test.ts](../src/test/zabbix_api_config.test.ts) |
| TC-CONF-03 | logger levels initialized from Config | Unit | Jest | [src/test/logger_config.test.ts](../src/test/logger_config.test.ts) |
| TC-CONF-04 | apiVersion query | Unit | Jest | [src/test/misc_resolvers.test.ts](../src/test/misc_resolvers.test.ts) |
| TC-CONF-05 | login query | Unit | Jest | [src/test/misc_resolvers.test.ts](../src/test/misc_resolvers.test.ts) |
| TC-CONF-06 | logout query | Unit | Jest | [src/test/misc_resolvers.test.ts](../src/test/misc_resolvers.test.ts) |
| TC-CONF-07 | Parse Zabbix Args | Unit | Jest | [src/test/zabbix_api_args_parser.test.ts](../src/test/zabbix_api_args_parser.test.ts) |
| TC-DOCS-01 | Zabbix Docs Samples Integration | Integration | Jest | [src/test/zabbix_docs_samples.test.ts](../src/test/zabbix_docs_samples.test.ts) |
| TC-MCP-01 | MCP Operations Validation | Integration | Jest | [src/test/mcp_operations_validation.test.ts](../src/test/mcp_operations_validation.test.ts) |
| TC-E2E-01 | Run complete smoketest | E2E | GraphQL / MCP | [mcp/operations/runSmoketest.graphql](../mcp/operations/runSmoketest.graphql) |
| TC-E2E-02 | Run all regression tests | E2E | GraphQL / MCP | [mcp/operations/runAllRegressionTests.graphql](../mcp/operations/runAllRegressionTests.graphql) |
## 📝 Test Case Obligations
As per project guidelines, every new feature or bug fix must be accompanied by a described test case in this specification.
- **Feature**: A new feature must have a corresponding test case (TC) defined before implementation.
- **Bug Fix**: A bug fix must include a reproduction test case that fails without the fix and passes with it. Additionally, a permanent regression test must be added to the automated suite (e.g., `RegressionTestExecutor`) to prevent the issue from re-occurring.
- **Documentation**: The `docs/tests.md` file must be updated to reflect any changes in test coverage.
- **Categorization**: Tests must be categorized as Unit, Integration, or End-to-End (E2E).

20
mcp-config.yaml Normal file
View file

@ -0,0 +1,20 @@
endpoint: http://zabbix-graphql-api:4000/graphql
overrides:
mutation_mode: all
transport:
type: streamable_http
stateful_mode: false
operations:
source: local
paths:
- /mcp/operations
schema:
source: local
path: /mcp-data/schema.graphql
introspection:
execute:
enabled: true
introspect:
enabled: true
search:
enabled: true

View file

@ -0,0 +1,3 @@
query GetApiVersion {
apiVersion
}

View file

@ -0,0 +1,8 @@
mutation CreateHost($host: String!, $hostgroupids: [Int!]!, $templateids: [Int], $templateNames: [String]) {
createHost(host: $host, hostgroupids: $hostgroupids, templateids: $templateids, templateNames: $templateNames) {
hostids
error {
message
}
}
}

View file

@ -0,0 +1,14 @@
mutation CreateVerificationHost($deviceKey: String!, $deviceType: String!, $groupNames: [String!]!, $templateNames: [String]) {
importHosts(hosts: [{
deviceKey: $deviceKey,
deviceType: $deviceType,
groupNames: $groupNames,
templateNames: $templateNames
}]) {
hostid
message
error {
message
}
}
}

View file

@ -0,0 +1,21 @@
query GetTemplates($name_pattern: String) {
templates(name_pattern: $name_pattern) {
templateid
host
name
items {
name
key_
type
value_type
status
history
delay
units
description
preprocessing
tags
master_itemid
}
}
}

View file

@ -0,0 +1,7 @@
mutation ImportHostGroups($hostGroups: [CreateHostGroup!]!) {
importHostGroups(hostGroups: $hostGroups) {
groupName
groupid
message
}
}

View file

@ -0,0 +1,12 @@
# Import multiple hosts/devices into Zabbix.
# This is a powerful tool for bulk provisioning of hosts using their names and types.
# It supports linking templates by ID (templateids) or by name (templateNames).
mutation ImportHosts($hosts: [CreateHost!]!) {
importHosts(hosts: $hosts) {
hostid
message
error {
message
}
}
}

View file

@ -0,0 +1,12 @@
# Import templates into Zabbix.
# This operation allows creating or updating templates with their groups, items, and linked templates.
mutation ImportTemplates($templates: [CreateTemplate!]!) {
importTemplates(templates: $templates) {
host
templateid
message
error {
message
}
}
}

View file

@ -0,0 +1,13 @@
# Runs all regression tests.
# Variables: hostName, groupName
mutation RunAllRegressionTests($hostName: String!, $groupName: String!) {
runAllRegressionTests(hostName: $hostName, groupName: $groupName) {
success
message
steps {
name
success
message
}
}
}

View file

@ -0,0 +1,14 @@
# Run a complete smoketest: creates a template, host group, and host,
# verifies their creation and linkage, and then cleans up everything.
# Variables: hostName, templateName, groupName
mutation RunSmoketest($hostName: String!, $templateName: String!, $groupName: String!) {
runSmoketest(hostName: $hostName, templateName: $templateName, groupName: $groupName) {
success
message
steps {
name
success
message
}
}
}

View file

@ -0,0 +1,13 @@
query VerifySchemaExtension($typeName: String!, $deviceKey: String) {
allDevices(tag_deviceType: [$typeName], filter_host: $deviceKey) {
hostid
host
name
deviceType
state {
operational {
timestamp
}
}
}
}

19
package-lock.json generated
View file

@ -32,7 +32,7 @@
"@types/cors": "^2.8.17",
"@types/express": "^5.0.6",
"@types/jest": "^29.5.13",
"@types/node": "^22.6.1",
"@types/node": "^24.10.9",
"@types/simple-mock": "^0.8.6",
"@types/ws": "^8.5.12",
"jest": "^29.7.0",
@ -43,6 +43,9 @@
"ts-node": "^10.9.2",
"tsx": "^4.19.1",
"typescript": "^5.6.2"
},
"engines": {
"node": ">=24"
}
},
"node_modules/@apollo/cache-control-types": {
@ -3764,14 +3767,14 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "22.19.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz",
"integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==",
"version": "24.10.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.9.tgz",
"integrity": "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~6.21.0"
"undici-types": "~7.16.0"
}
},
"node_modules/@types/qs": {
@ -10091,9 +10094,9 @@
"license": "MIT"
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"dev": true,
"license": "MIT"
},

View file

@ -6,17 +6,20 @@
"type": "module",
"scripts": {
"compile": "tsc",
"start": "nodemon --watch \"src/**\" --watch \"schema.graphql\" --ext \"ts,json\" --exec \"node --import tsx ./src/index.ts\"",
"start": "nodemon --watch \"src/**\" --watch \"schema/**\" --ext \"ts,json,graphql\" --exec \"node --import tsx ./src/index.ts\"",
"prod": "npm run copy-schema && node ./dist/index.js",
"test": "jest --detectOpenHandles --forceExit --bail",
"codegen": "graphql-codegen --config codegen.ts --watch \"schema.graphql\"",
"nodemon": "nodemon --watch \"src/**\" --watch \"schema.graphql\" --ext \"ts,json\" --exec \"tsc",
"codegen": "graphql-codegen --config codegen.ts --watch",
"nodemon": "nodemon --watch \"src/**\" --watch \"schema/**\" --ext \"ts,json,graphql\" --exec \"tsc",
"copy-schema": "cp -R schema ./dist/"
},
"keywords": [],
"license": "AGPL-3.0-only",
"author": "Andreas Hilbig",
"copyright": "All rights reserved by Hilbig IT GmbH",
"engines": {
"node": ">=24"
},
"dependencies": {
"@apollo/datasource-rest": "^6.3.0",
"@apollo/server": "^5.2.0",
@ -41,7 +44,7 @@
"@types/cors": "^2.8.17",
"@types/express": "^5.0.6",
"@types/jest": "^29.5.13",
"@types/node": "^22.6.1",
"@types/node": "^24.10.9",
"@types/simple-mock": "^0.8.6",
"@types/ws": "^8.5.12",
"jest": "^29.7.0",

102
readme.improvement.plan.md Normal file
View file

@ -0,0 +1,102 @@
# Documentation Improvement Plan
This plan outlines the steps to refactor and improve the Zabbix GraphQL API documentation, combining a technical reference with a practical "Cookbook" for quick start and AI-based test generation.
## Priority 1: Reference Documentation (Advanced Users)
### 1.1 Enhance README.md
- [x] **Feature-to-Code Mapping**: Add "Reference" links to each feature in the features list (from public origin).
- [x] **Comprehensive Config Reference**: Update environment variables table with detailed descriptions, defaults, and requirements.
- [x] **Auth Separation**: Explain `ZABBIX_PRIVILEGE_ESCALATION_TOKEN` vs `ZABBIX_DEVELOPMENT_TOKEN`.
- [x] **Entity Mapping Table**: Include the Zabbix-to-GraphQL entity mapping table.
- [x] **API Versioning**: Document how the version is determined and Zabbix version compatibility (7.4 focus).
### 1.2 Update Detailed Guides
- [x] Ensure `docs/howtos/schema.md`, `permissions.md`, etc., are up-to-date with the latest implementation details.
## Priority 2: Cookbook & AI-Ready Instructions
### 2.1 Create the Cookbook (`docs/howtos/cookbook.md`)
- [x] **Step-by-Step Recipes**: Create practical, task-oriented guides.
- [x] **Recipe: Schema Extension**: Move the "Distance Tracker" example into the cookbook.
- [x] **Recipe: Basic Monitoring**: Step-by-step to monitor a new host.
- [x] **Recipe: Bulk Import**: Guide for mass importing templates and hosts.
### 2.2 AI/MCP Integration for Test Generation
- [x] **MCP Server Documentation**: Explicitly document how to start and use the `zabbix-graphql` MCP server.
- [x] **Test Generation Instructions**: Provide specific instructions on how an AI can use the Cookbook recipes to generate test cases.
- [x] **Metadata for AI**: Use structured headers and clear prerequisites in recipes to make them "AI-parsable".
## Priority 3: Technical Maintenance (from Public Origin)
### 3.1 Code Generation Section
- [x] Explain the GraphQL Codegen setup and how to regenerate types.
- [x] Document the `codegen.ts` configuration.
- [x] Add instructions for updating generated types after schema changes.
## Priority 4: Improve Examples
### 4.1 Complete Examples
- [x] Add more complete examples for each major operation.
- [x] Include error handling examples.
- [x] Add examples for common use cases beyond the distance tracker.
### 4.2 Testing Examples
- [x] Add information about how to run tests.
- [x] Include examples of unit and integration tests.
- [x] Explain the test structure and how to add new tests.
## Priority 5: Documentation Links
### 5.1 Cross-Reference Improvements
- [x] Add links to relevant sections in schema files.
- [x] Include references to specific resolver implementations.
- [x] Link to related documentation files in the docs directory.
### 5.2 External Resources
- [x] Link to official Zabbix API documentation.
- [x] Include references to Apollo Server documentation.
## Priority 6: Refine Structure & DRY (New)
### 6.1 Separate Cookbook from Reference
- [x] Move instructional "how-to" steps from README.md to `cookbook.md` or specific how-tos where they distract from reference info.
- [x] Ensure `README.md` focuses on "What" (specification/reference) and `cookbook.md` focuses on "How" (recipes).
### 6.2 Eliminate Redundancy (DRY)
- [x] Identify and remove duplicate information between `README.md` and `docs/howtos/` (e.g., Zabbix to GraphQL Mapping).
- [x] Centralize core definitions to a single source of truth and use links elsewhere.
### 6.3 Enhance Cross-References
- [x] Add explicit "See also" or "Related Recipes" links in reference sections.
- [x] Link from recipes back to technical reference material for deep-dives.
## Priority 7: Standardization & Verification (New)
### 7.1 Cookbook Verification Steps
- [x] Add a result verification step to each recipe in `cookbook.md`.
- [x] Ensure verification steps use GraphQL queries or agent checks where appropriate.
- [x] Refine 'Extending Schema' recipe with detailed manual/automated steps and comprehensive verification (Priority 7 enhancement).
### 7.2 Recipe Templating
- [x] Define a standard template for recipes in `.junie/guidelines.md`.
- [x] Standardize icons and step naming (🛠️, ⚙️, 🚀, ✅).
## Implementation Order
1. [x] **Foundation**: Update `README.md` with missing reference information from public origin.
2. [x] **Cookbook Alpha**: Create `docs/howtos/cookbook.md` with the first set of recipes.
3. [x] **AI Bridge**: Document MCP server usage and test generation workflow.
4. [x] **Maintenance**: Add Codegen and Testing sections.
5. [x] **Cross-Linking**: Optimize all links and cross-references.
6. [x] **Optimize**: Run import optimization across the project.
7. [x] **Refine & DRY**: Execute Priority 6 tasks to further clean up and structure documentation.
8. [x] **Standardize**: Add verification steps and formalize the recipe template (Priority 7).
## Success Metrics
- All environment variables documented.
- Clear distinction between Reference and Cookbook.
- Functional MCP-based test generation using cookbook instructions.
- Accurate representation of features and compatibility.
- Zero redundant tables or instructional blocks across the doc set.
- All recipes include a verification step.
- Guidelines contain a clear template for future recipes.

23
roadmap.md Normal file
View file

@ -0,0 +1,23 @@
# 🗺️ Roadmap
This document outlines the achieved milestones and planned enhancements for the Zabbix GraphQL API project.
## ✅ Achieved Milestones
- **🎯 VCR Product Integration**:
- Developed a specialized **GraphQL API** as part of the VCR Product. This enables the use of **Zabbix** as a robust base for monitoring and controlling **IoT devices**.
- *First use case*: Control of mobile traffic jam warning installations on **German Autobahns**.
- **🔓 Open Source Extraction & AI Integration**:
- Extracted the core functionality of the API to publish it as an **Open Source** project.
- Enhanced it with **Model Context Protocol (MCP)** and **AI agent** integration to enable workflow and agent-supported use cases within the VCR or other applications.
## 📅 Planned Enhancements
- **📦 CI/CD & Package Publishing**:
- Build and publish the API as an **npm package** as part of the `.forgejo` workflow to simplify distribution and updates.
- **🧱 Flexible Usage by publishing to [npmjs.com](https://www.npmjs.com/)**:
- Transform the package into a versatile tool that can be used either **standalone** or as a **core library**, allowing other projects to include and extend it easily.
- **🧩 Extension Project**: `zabbix-graphql-api-problems`
- Create the first official extension, `zabbix-graphql-api-problems`
- Add support for **problem and trigger-related** queries. This will leverage **MCP + AI agents** to automatically react to Zabbix problems within external workflows.

View file

@ -13,7 +13,7 @@ interface DeviceValueMessage {
"""
Represents the timestamp at which a specific event, message, or data point was created or recorded.
The format should align with standard expectations (e.g., ISO 8601).
The format should align with standard expectations (e.g. ISO 8601).
"""
timestamp: String

View file

@ -18,6 +18,10 @@ interface Device implements Host {
name: String
"""Device configuration tags."""
tags: DeviceConfig
"""Host inventory data."""
inventory: Inventory
"""List of monitored items for this host."""
items: [ZabbixItem!]
"""State of the device."""
state: DeviceState
}
@ -74,7 +78,7 @@ type OperationalDeviceData {
temperature: Float
"""Device voltage."""
voltage: Float
"""Signal strength (e.g., WiFi or GSM)."""
"""Signal strength (e.g. WiFi or GSM)."""
signalstrength: Float
"""Current location of the device."""
location: Location
@ -133,6 +137,10 @@ type GenericDevice implements Host & Device {
name: String
"""Device configuration tags."""
tags: DeviceConfig
"""Host inventory data."""
inventory: Inventory
"""List of monitored items for this host."""
items: [ZabbixItem!]
"""State of the generic device."""
state: GenericDeviceState
}

View file

@ -13,6 +13,8 @@ type SinglePanelDevice implements Host & Device {
hostgroups: [HostGroup!]
name: String
tags: DeviceConfig
inventory: Inventory
items: [ZabbixItem!]
state: PanelState
}
@ -71,6 +73,10 @@ type FourPanelDevice implements Host & Device {
name: String
"""Device configuration tags."""
tags: DeviceConfig
"""Host inventory data."""
inventory: Inventory
"""List of monitored items for this host."""
items: [ZabbixItem!]
"""State of the four-panel device."""
state: FourPanelState
}

View file

@ -12,7 +12,7 @@ type SensorDistanceValue implements DeviceValue {
"""
Represents the MAC address of the device. Typically formatted as a 12-character
hexadecimal string (e.g., "00:1A:2B:3C:4D:5E").
hexadecimal string (e.g. "00:1A:2B:3C:4D:5E").
"""
mac: String

View file

@ -17,6 +17,10 @@ type DistanceTrackerDevice implements Host & Device {
name: String
"""Device configuration tags."""
tags: DeviceConfig
"""Host inventory data."""
inventory: Inventory
"""List of monitored items for this host."""
items: [ZabbixItem!]
"""State of the distance tracker device."""
state: DistanceTrackerState
}

View file

@ -0,0 +1,50 @@
"""
WeatherSensorDevice represents a device that retrieves weather information
from public APIs (e.g. Open-Meteo) using Zabbix HTTP agent items.
"""
type WeatherSensorDevice implements Host & Device {
"""Internal Zabbix ID of the device."""
hostid: ID!
"""
Per convention a uuid is used as hostname to identify devices if they do not have a unique hostname.
"""
host: String!
"""Classification of the device."""
deviceType: String
"""List of host groups this device belongs to."""
hostgroups: [HostGroup!]
"""Visible name of the device."""
name: String
"""Device configuration tags."""
tags: DeviceConfig
"""Host inventory data."""
inventory: Inventory
"""List of monitored items for this host."""
items: [ZabbixItem!]
"""State of the weather sensor device."""
state: WeatherSensorState
}
"""
Represents the state of a weather sensor device.
"""
type WeatherSensorState implements DeviceState {
"""Operational data (telemetry)."""
operational: OperationalDeviceData
"""Current business values (weather data)."""
current: WeatherSensorValues
}
"""
Aggregated weather information retrieved from the API.
"""
type WeatherSensorValues {
"""
Current temperature at the device location (in Celsius).
"""
temperature: Float
"""
Warnings or description of the street conditions (e.g. Ice, Rain, Clear).
"""
streetConditionWarnings: String
}

View file

@ -11,7 +11,9 @@ type Mutation {
"""List of host group IDs to assign the host to."""
hostgroupids:[Int!]!,
"""List of template IDs to link to the host."""
templateids: [Int!]!,
templateids: [Int],
"""List of template names to link to the host."""
templateNames: [String],
"""Optional location information for the host inventory."""
location: LocationInput
): CreateHostResponse
@ -85,7 +87,7 @@ type Mutation {
deleteTemplates(
"""List of template IDs to delete."""
templateids: [Int!],
"""Wildcard name pattern for templates to delete (e.g., 'Template%')."""
"""Wildcard name pattern for templates to delete (e.g. 'Template%')."""
name_pattern: String
): [DeleteResponse!]
@ -100,6 +102,88 @@ type Mutation {
"""Wildcard name pattern for template groups to delete."""
name_pattern: String
): [DeleteResponse!]
"""
Delete hosts by their IDs or by a name pattern.
Authentication: Requires `zbx_session` cookie or `zabbix-auth-token` header.
"""
deleteHosts(
"""List of host IDs to delete."""
hostids: [Int!],
"""Wildcard name pattern for hosts to delete."""
name_pattern: String
): [DeleteResponse!]
"""
Delete host groups by their IDs or by a name pattern.
Authentication: Requires `zbx_session` cookie or `zabbix-auth-token` header.
"""
deleteHostGroups(
"""List of host group IDs to delete."""
groupids: [Int!],
"""Wildcard name pattern for host groups to delete."""
name_pattern: String
): [DeleteResponse!]
"""
Runs a smoketest: creates a template, links a host, verifies it, and cleans up.
"""
runSmoketest(
"""Technical name for the smoketest host."""
hostName: String!,
"""Technical name for the smoketest template."""
templateName: String!,
"""Technical name for the smoketest host group."""
groupName: String!
): SmoketestResponse!
"""
Runs all regression tests.
"""
runAllRegressionTests(
"""Technical name for the test host."""
hostName: String!,
"""Technical name for the test host group."""
groupName: String!
): SmoketestResponse!
}
"""
Response object for the smoketest operation.
"""
type SmoketestResponse {
"""
True if all steps of the smoketest succeeded.
"""
success: Boolean!
"""
Overall status message.
"""
message: String
"""
Detailed results for each step.
"""
steps: [SmoketestStep!]!
}
"""
Results for a single step in the smoketest.
"""
type SmoketestStep {
"""
Name of the step (e.g. 'Create Template').
"""
name: String!
"""
True if the step succeeded.
"""
success: Boolean!
"""
Status message or error message for the step.
"""
message: String
}
####################################################################
@ -174,6 +258,10 @@ input CreateTemplate {
Tags to assign to the template.
"""
tags: [CreateTag!]
"""
User macros to assign to the template.
"""
macros: [CreateMacro!]
}
"""
@ -189,19 +277,23 @@ input CreateTemplateItem {
"""
name: String!
"""
Zabbix item type (e.g., 0 for Zabbix Agent, 18 for Dependent).
Zabbix item type (e.g. 0 for Zabbix Agent, 18 for Dependent).
"""
type: Int
"""
Zabbix item status (0 for Enabled, 1 for Disabled).
"""
status: Int
"""
Technical key of the item.
"""
key: String!
"""
Type of information (e.g., 0 for Float, 3 for Int, 4 for Text).
Type of information (e.g. 0 for Float, 3 for Int, 4 for Text).
"""
value_type: Int
"""
History storage period (e.g., '2d', '90d').
History storage period (e.g. '2d', '90d').
"""
history: String
"""
@ -217,6 +309,10 @@ input CreateTemplateItem {
"""
description: String
"""
URL for HTTP Agent items.
"""
url: String
"""
Preprocessing steps for the item values.
"""
preprocessing: [CreateItemPreprocessing!]
@ -245,7 +341,7 @@ Input for an item preprocessing step.
"""
input CreateItemPreprocessing {
"""
Type of preprocessing step (e.g., 12 for JSONPath, 21 for JavaScript).
Type of preprocessing step (e.g. 12 for JSONPath, 21 for JavaScript).
"""
type: Int!
"""
@ -286,6 +382,20 @@ input CreateTag {
value: String
}
"""
Input for creating a user macro.
"""
input CreateMacro {
"""
Macro name (e.g. '{$LAT}').
"""
macro: String!
"""
Macro value.
"""
value: String!
}
"""
Response for a template import operation.
"""
@ -413,9 +523,21 @@ input CreateHost {
"""
groupids: [Int]
"""
List of template IDs to link to the host.
"""
templateids: [Int]
"""
List of template names to link to the host.
"""
templateNames: [String]
"""
Location information for the host.
"""
location: LocationInput
"""
User macros to assign to the host.
"""
macros: [CreateMacro!]
}
"""
@ -477,7 +599,7 @@ input UserRoleInput {
"""
name: String
"""
Type of role (e.g., 1 for User, 2 for Admin, 3 for Super Admin).
Type of role (e.g. 1 for User, 2 for Admin, 3 for Super Admin).
"""
type: Int
"""
@ -515,7 +637,7 @@ input UserRoleRulesInput {
"""
api_access: Int
"""
API mode (e.g., 0 for white-list, 1 for black-list).
API mode (e.g. 0 for white-list, 1 for black-list).
"""
api_mode: Int
"""
@ -541,7 +663,7 @@ input UserRoleRuleInput {
"""
name: String
"""
Status (e.g., 1 for enabled, 0 for disabled).
Status (e.g. 1 for enabled, 0 for disabled).
"""
status: Int
}

View file

@ -41,6 +41,14 @@ interface Host {
of a device in the system (capabilities, functionalities, or purpose).
"""
deviceType: String
"""
Host inventory data.
"""
inventory: Inventory
"""
List of monitored items for this host.
"""
items: [ZabbixItem!]
}
"""
@ -72,7 +80,7 @@ type ZabbixItem {
"""
lastvalue: String
"""
Type of information (e.g., 0 for Float, 3 for Int, 4 for Text).
Type of information (e.g. 0 for Float, 3 for Int, 4 for Text).
"""
value_type: Int!
"""
@ -88,9 +96,49 @@ type ZabbixItem {
"""
type: DeviceCommunicationType
"""
Raw Zabbix item type as integer.
"""
type_int: Int
"""
Raw Zabbix item status as integer.
"""
status_int: Int
"""
Hosts that this item is linked to.
"""
hosts: [Host!]
"""
History storage period (e.g. '2d', '90d').
"""
history: String
"""
Update interval.
"""
delay: String
"""
Units of the value.
"""
units: String
"""
Description of the item.
"""
description: String
"""
Preprocessing steps for the item.
"""
preprocessing: [JSONObject!]
"""
Tags assigned to the item.
"""
tags: [JSONObject!]
"""
Master item ID for dependent items.
"""
master_itemid: Int
"""
Master item for dependent items.
"""
master_item: ZabbixItem
}
"""
@ -182,9 +230,17 @@ type Template {
"""
templateid: String!
"""
Technical name of the template.
"""
host: String!
"""
Name of the template.
"""
name: String
"""
List of items for this template.
"""
items: [ZabbixItem!]
}
"""

View file

@ -4,14 +4,15 @@ import {
DeviceStatus,
Host,
MutationCreateHostArgs,
MutationDeleteTemplateGroupsArgs,
MutationDeleteTemplatesArgs,
MutationImportHostGroupsArgs,
MutationImportHostsArgs,
MutationImportTemplateGroupsArgs,
MutationImportTemplatesArgs,
MutationDeleteTemplatesArgs,
MutationDeleteTemplateGroupsArgs,
MutationImportUserRightsArgs,
Permission, QueryAllDevicesArgs,
Permission,
QueryAllDevicesArgs,
QueryAllHostGroupsArgs,
QueryAllHostsArgs,
QueryExportHostValueHistoryArgs,
@ -24,6 +25,9 @@ import {
} from "../schema/generated/graphql.js";
import {HostImporter} from "../execution/host_importer.js";
import {HostDeleter} from "../execution/host_deleter.js";
import {SmoketestExecutor} from "../execution/smoketest_executor.js";
import {RegressionTestExecutor} from "../execution/regression_test_executor.js";
import {TemplateImporter} from "../execution/template_importer.js";
import {TemplateDeleter} from "../execution/template_deleter.js";
import {HostValueExporter} from "../execution/host_exporter.js";
@ -31,7 +35,8 @@ import {logger} from "../logging/logger.js";
import {ParsedArgs, ZabbixRequest} from "../datasources/zabbix-request.js";
import {
ZabbixCreateHostRequest,
ZabbixQueryDevices, ZabbixQueryDevicesArgs,
ZabbixQueryDevices,
ZabbixQueryDevicesArgs,
ZabbixQueryHostsRequestWithItemsAndInventory,
} from "../datasources/zabbix-hosts.js";
import {ZabbixQueryHostgroupsParams, ZabbixQueryHostgroupsRequest} from "../datasources/zabbix-hostgroups.js";
@ -47,14 +52,11 @@ import {
ZabbixQueryUserRolesRequest
} from "../datasources/zabbix-userroles.js";
import {
ZabbixCreateItemRequest,
ZabbixCreateTemplateGroupRequest,
ZabbixCreateTemplateRequest,
ZabbixQueryItemRequest,
TemplateHelper,
ZabbixQueryTemplateGroupRequest,
ZabbixQueryTemplatesRequest
} from "../datasources/zabbix-templates.js";
import {ZABBIX_EDGE_DEVICE_BASE_GROUP, zabbixAPI} from "../datasources/zabbix-api.js";
import {zabbixAPI} from "../datasources/zabbix-api.js";
import {GraphQLInterfaceType, GraphQLList} from "graphql/type/index.js";
import {isDevice} from "./resolver_helpers.js";
import {ZabbixPermissionsHelper} from "../datasources/zabbix-permissions.js";
@ -76,7 +78,7 @@ export function createResolvers(): Resolvers {
return ZabbixPermissionsHelper.hasUserPermissions(zabbixAPI, args, zabbixAuthToken, cookie)
},
locations: (_parent: any, args: Object, {dataSources, zabbixAuthToken}: any) => {
return dataSources.zabbixAPI.getLocations(zabbixAuthToken, new ParsedArgs(args));
return dataSources.zabbixAPI.getLocations(new ParsedArgs(args), zabbixAuthToken);
},
apiVersion: () => {
return Config.API_VERSION ?? "unknown"
@ -187,6 +189,17 @@ export function createResolvers(): Resolvers {
zabbixAuthToken,
cookie
}: any) => {
if (args.templateNames?.length) {
const templateidsByName = await TemplateHelper.findTemplateIdsByName(args.templateNames as string[], zabbixAPI, zabbixAuthToken, cookie);
if (!templateidsByName) {
return {
error: {
message: `Unable to find templates: ${args.templateNames}`
}
}
}
args.templateids = (args.templateids || []).concat(templateidsByName);
}
return await new ZabbixCreateHostRequest(zabbixAuthToken, cookie).executeRequestThrowError(
zabbixAPI,
new ParsedArgs(args)
@ -246,6 +259,30 @@ export function createResolvers(): Resolvers {
cookie
}: any) => {
return TemplateDeleter.deleteTemplateGroups(args.groupids, args.name_pattern, zabbixAuthToken, cookie)
},
deleteHosts: async (_parent: any, args: any, {
zabbixAuthToken,
cookie
}: any) => {
return HostDeleter.deleteHosts(args.hostids, args.name_pattern, zabbixAuthToken, cookie)
},
deleteHostGroups: async (_parent: any, args: any, {
zabbixAuthToken,
cookie
}: any) => {
return HostDeleter.deleteHostGroups(args.groupids, args.name_pattern, zabbixAuthToken, cookie)
},
runSmoketest: async (_parent: any, args: any, {
zabbixAuthToken,
cookie
}: any) => {
return SmoketestExecutor.runSmoketest(args.hostName, args.templateName, args.groupName, zabbixAuthToken, cookie)
},
runAllRegressionTests: async (_parent: any, args: any, {
zabbixAuthToken,
cookie
}: any) => {
return RegressionTestExecutor.runAllRegressionTests(args.hostName, args.groupName, zabbixAuthToken, cookie)
}
},
@ -321,6 +358,21 @@ export function createResolvers(): Resolvers {
DENY: Permission.Deny
},
ZabbixItem: {
type_int: (parent: any) => parent.type,
status_int: (parent: any) => parent.status,
master_item: (parent: any, _args: any, _context: any, info: any) => {
if (!parent.master_itemid || parent.master_itemid === "0" || parent.master_itemid === 0) {
return null;
}
// This is a bit hacky but works if the siblings are in the parent's items array
// and Apollo has already resolved them.
// However, 'parent' here is just the item data.
// To do this properly we'd need to fetch the master item if it's not present.
// For now, let's just return null if we can't find it easily, or just rely on the agent.
return null;
}
},
DeviceCommunicationType: {
ZABBIX_AGENT: DeviceCommunicationType.ZABBIX_AGENT,
ZABBIX_AGENT_ACTIVE: DeviceCommunicationType.ZABBIX_AGENT_ACTIVE,

View file

@ -8,7 +8,7 @@ import express from 'express';
import cors from "cors";
import {ApolloServerPluginDrainHttpServer} from '@apollo/server/plugin/drainHttpServer';
import {logger} from "../logging/logger.js";
import {zabbixAPI, zabbixRequestAuthToken} from "../datasources/zabbix-api.js";
import {zabbixAPI, zabbixDevelopmentToken} from "../datasources/zabbix-api.js";
import {WebSocketServer} from "ws";
import {useServer} from "graphql-ws/lib/use/ws";
@ -82,7 +82,7 @@ async function startApolloServer() {
dataSources: {
zabbixAPI: zabbixAPI,
},
zabbixAuthToken: req.headers["zabbix-auth-token"] ?? zabbixRequestAuthToken,
zabbixAuthToken: req.headers["zabbix-auth-token"] ?? zabbixDevelopmentToken,
cookie: req.headers.cookie,
token: req.headers.token
};

View file

@ -9,8 +9,8 @@ static readonly DRY_RUN = process.env.DRY_RUN
static readonly SCHEMA_PATH = process.env.SCHEMA_PATH || './schema/'
static readonly ADDITIONAL_SCHEMAS = process.env.ADDITIONAL_SCHEMAS
static readonly ADDITIONAL_RESOLVERS = process.env.ADDITIONAL_RESOLVERS
static readonly ZABBIX_AUTH_TOKEN_FOR_REQUESTS = process.env.ZABBIX_AUTH_TOKEN_FOR_REQUESTS
static readonly ZABBIX_AUTH_TOKEN = process.env.ZABBIX_AUTH_TOKEN
static readonly ZABBIX_DEVELOPMENT_TOKEN = process.env.ZABBIX_DEVELOPMENT_TOKEN
static readonly ZABBIX_PRIVILEGE_ESCALATION_TOKEN = process.env.ZABBIX_PRIVILEGE_ESCALATION_TOKEN
static readonly ZABBIX_EDGE_DEVICE_BASE_GROUP = process.env.ZABBIX_EDGE_DEVICE_BASE_GROUP
static readonly ZABBIX_ROADWORK_BASE_GROUP = process.env.ZABBIX_ROADWORK_BASE_GROUP
static readonly ZABBIX_PERMISSION_TEMPLATE_GROUP_NAME_PREFIX = process.env.ZABBIX_PERMISSION_TEMPLATE_GROUP_NAME_PREFIX || "Permissions"

View file

@ -9,8 +9,8 @@ import {logger} from "../logging/logger.js";
import {ParsedArgs, ZabbixErrorResult, ZabbixRequest, ZabbixResult} from "./zabbix-request.js";
import {Config} from "../common_utils.js";
export const zabbixRequestAuthToken = Config.ZABBIX_AUTH_TOKEN_FOR_REQUESTS
export const zabbixSuperAuthToken = Config.ZABBIX_AUTH_TOKEN
export const zabbixDevelopmentToken = Config.ZABBIX_DEVELOPMENT_TOKEN
export const zabbixPrivilegeEscalationToken = Config.ZABBIX_PRIVILEGE_ESCALATION_TOKEN
export const ZABBIX_EDGE_DEVICE_BASE_GROUP = Config.ZABBIX_EDGE_DEVICE_BASE_GROUP || Config.ZABBIX_ROADWORK_BASE_GROUP || "Roadwork"
export const FIND_ZABBIX_EDGE_DEVICE_BASE_GROUP_PREFIX = new RegExp(`^(${ZABBIX_EDGE_DEVICE_BASE_GROUP})\/`)

View file

@ -4,7 +4,7 @@ import {
FIND_ZABBIX_EDGE_DEVICE_BASE_GROUP_PREFIX,
ZABBIX_EDGE_DEVICE_BASE_GROUP,
ZabbixAPI,
zabbixSuperAuthToken
zabbixPrivilegeEscalationToken
} from "./zabbix-api.js";
import {logger} from "../logging/logger.js";
import {ZabbixRequestWithPermissions} from "./zabbix-permissions.js";
@ -16,14 +16,14 @@ export interface CreateHostGroupResult {
const hostGroupReadWritePermissions = {
permissions: [
{
objectName: "Hostgroup/ConstructionSite",
objectName: "Hostgroup",
permission: Permission.ReadWrite
}]
}
export class ZabbixCreateHostGroupRequest extends ZabbixRequestWithPermissions<CreateHostGroupResult> {
constructor(_authToken?: string | null, cookie?: string) {
super("hostgroup.create", zabbixSuperAuthToken, cookie, hostGroupReadWritePermissions);
super("hostgroup.create", zabbixPrivilegeEscalationToken, cookie, hostGroupReadWritePermissions);
}
}
@ -69,6 +69,11 @@ export class ZabbixQueryHostgroupsRequest extends ZabbixRequestWithPermissions<Z
}
export class ZabbixDeleteHostGroupsRequest extends ZabbixRequestWithPermissions<{ groupids: string[] }> {
constructor(authToken?: string | null, cookie?: string | null) {
super("hostgroup.delete", authToken, cookie, hostGroupReadWritePermissions);
}
}
export class GroupHelper {
public static groupFullName(groupName: string) {

View file

@ -85,7 +85,7 @@ export class ZabbixQueryHostsGenericRequestWithItems<T extends ZabbixResult, A e
"hostid",
"host",
"name",
"hostgroup",
"hostgroups",
"items",
"description",
"parentTemplates"
@ -173,7 +173,8 @@ export interface ZabbixCreateHostInputParams extends ZabbixParams {
}
templateids?: [number];
hostgroupids?: [number];
additionalParams?: [number];
macros?: { macro: string, value: string }[];
additionalParams?: any;
}
@ -183,6 +184,7 @@ class ZabbixCreateHostParams implements ZabbixParams {
this.name = inputParams.name;
this.description = inputParams.description;
if (inputParams.location) {
this.inventory_mode = 0;
this.inventory = {
location: inputParams.location.name,
location_lat: inputParams.location.location_lat,
@ -199,11 +201,15 @@ class ZabbixCreateHostParams implements ZabbixParams {
return {groupid: groupid}
});
}
if (inputParams.macros) {
this.macros = inputParams.macros;
}
}
host: string
name: string
description: string
inventory_mode?: number
inventory?: {
location: String
@ -212,6 +218,7 @@ class ZabbixCreateHostParams implements ZabbixParams {
}
templates?: any
groups?: any
macros?: { macro: string, value: string }[]
}
@ -228,3 +235,9 @@ export class ZabbixCreateHostRequest extends ZabbixRequest<CreateHostResponse> {
return args?.zabbix_params || {};
}
}
export class ZabbixDeleteHostsRequest extends ZabbixRequest<{ hostids: string[] }> {
constructor(authToken?: string | null, cookie?: string | null) {
super("host.delete", authToken, cookie);
}
}

View file

@ -25,6 +25,7 @@ export type ZabbixErrorResult = {
export const isZabbixErrorResult = (value: any): value is ZabbixErrorResult => value instanceof Object && "error" in value && !!value.error;
export interface ZabbixParams {
[key: string]: any
}
export interface ZabbixWithTagsParams extends ZabbixParams {
@ -131,13 +132,12 @@ export class ParsedArgs {
}
if (this.name_pattern) {
if ("search" in result) {
(<any>result.search).name = this.name_pattern
} else {
(<any>result).search = {
name: this.name_pattern,
}
if (!("search" in result)) {
(<any>result).search = {}
}
(<any>result).search.name = this.name_pattern;
(<any>result).search.host = this.name_pattern;
(<any>result).searchByAny = true;
}
return result

View file

@ -1,10 +1,14 @@
import {ZabbixRequest} from "./zabbix-request.js";
import {ZabbixRequest, ParsedArgs, isZabbixErrorResult, ZabbixParams} from "./zabbix-request.js";
import {ZabbixAPI} from "./zabbix-api.js";
import {logger} from "../logging/logger.js";
export interface ZabbixQueryTemplateResponse {
templateid: string,
host: string,
uuid: string,
name: string,
items?: any[]
}
@ -12,6 +16,14 @@ export class ZabbixQueryTemplatesRequest extends ZabbixRequest<ZabbixQueryTempla
constructor(authToken?: string | null, cookie?: string | null,) {
super("template.get", authToken, cookie);
}
createZabbixParams(args?: ParsedArgs): ZabbixParams {
return {
"selectItems": "extend",
"output": "extend",
...args?.zabbix_params
};
}
}
@ -65,3 +77,23 @@ export class ZabbixDeleteTemplateGroupsRequest extends ZabbixRequest<{ groupids:
}
export class TemplateHelper {
public static async findTemplateIdsByName(templateNames: string[], zabbixApi: ZabbixAPI, zabbixAuthToken?: string, cookie?: string) {
let result: number[] = []
for (let templateName of templateNames) {
// Use name_pattern which now searches both visibility name and technical name (host)
let templates = await new ZabbixQueryTemplatesRequest(zabbixAuthToken, cookie).executeRequestReturnError(zabbixApi, new ParsedArgs({
name_pattern: templateName
}))
if (isZabbixErrorResult(templates) || !templates?.length) {
logger.error(`Unable to find templateName=${templateName}`)
return null
}
result.push(...templates.map((t) => Number(t.templateid)))
}
return result
}
}

View file

@ -0,0 +1,110 @@
import {DeleteResponse} from "../schema/generated/graphql.js";
import {
ZabbixDeleteHostsRequest,
ZabbixQueryHostsGenericRequest,
} from "../datasources/zabbix-hosts.js";
import {
ZabbixDeleteHostGroupsRequest,
ZabbixQueryHostgroupsRequest,
ZabbixQueryHostgroupsParams,
GroupHelper
} from "../datasources/zabbix-hostgroups.js";
import {isZabbixErrorResult, ParsedArgs} from "../datasources/zabbix-request.js";
import {zabbixAPI} from "../datasources/zabbix-api.js";
export class HostDeleter {
public static async deleteHosts(hostids: number[] | null | undefined, name_pattern?: string | null, zabbixAuthToken?: string, cookie?: string): Promise<DeleteResponse[]> {
const result: DeleteResponse[] = [];
let idsToDelete = hostids ? [...hostids] : [];
if (name_pattern) {
const queryResult = await new ZabbixQueryHostsGenericRequest("host.get", zabbixAuthToken, cookie)
.executeRequestReturnError(zabbixAPI, new ParsedArgs({ name_pattern: name_pattern }));
if (!isZabbixErrorResult(queryResult) && Array.isArray(queryResult)) {
const foundIds = queryResult.map((t: any) => Number(t.hostid));
// Merge and deduplicate
idsToDelete = Array.from(new Set([...idsToDelete, ...foundIds]));
}
}
if (idsToDelete.length === 0) {
return [];
}
const deleteResult = await new ZabbixDeleteHostsRequest(zabbixAuthToken, cookie)
.executeRequestReturnError(zabbixAPI, new ParsedArgs(idsToDelete));
if (isZabbixErrorResult(deleteResult)) {
let errorMessage = deleteResult.error.message;
if (deleteResult.error.data) {
errorMessage += " " + (typeof deleteResult.error.data === 'string' ? deleteResult.error.data : JSON.stringify(deleteResult.error.data));
}
for (const id of idsToDelete) {
result.push({
id: id,
message: errorMessage,
error: deleteResult.error
});
}
} else if (deleteResult?.hostids) {
for (const id of idsToDelete) {
result.push({
id: id,
message: `Host ${id} deleted successfully`
});
}
}
return result;
}
public static async deleteHostGroups(groupids: number[] | null | undefined, name_pattern?: string | null, zabbixAuthToken?: string, cookie?: string): Promise<DeleteResponse[]> {
const result: DeleteResponse[] = [];
let idsToDelete = groupids ? [...groupids] : [];
if (name_pattern) {
const queryResult = await new ZabbixQueryHostgroupsRequest(zabbixAuthToken, cookie)
.executeRequestReturnError(zabbixAPI, new ZabbixQueryHostgroupsParams({
filter_name: GroupHelper.groupFullName(name_pattern)
}));
if (!isZabbixErrorResult(queryResult) && Array.isArray(queryResult)) {
const foundIds = queryResult.map(g => Number(g.groupid));
// Merge and deduplicate
idsToDelete = Array.from(new Set([...idsToDelete, ...foundIds]));
}
}
if (idsToDelete.length === 0) {
return [];
}
const deleteResult = await new ZabbixDeleteHostGroupsRequest(zabbixAuthToken, cookie)
.executeRequestReturnError(zabbixAPI, new ParsedArgs(idsToDelete));
if (isZabbixErrorResult(deleteResult)) {
let errorMessage = deleteResult.error.message;
if (deleteResult.error.data) {
errorMessage += " " + (typeof deleteResult.error.data === 'string' ? deleteResult.error.data : JSON.stringify(deleteResult.error.data));
}
for (const id of idsToDelete) {
result.push({
id: id,
message: errorMessage,
error: deleteResult.error
});
}
} else if (deleteResult?.groupids) {
for (const id of idsToDelete) {
result.push({
id: id,
message: `Host group ${id} deleted successfully`
});
}
}
return result;
}
}

View file

@ -6,7 +6,8 @@ import {
InputMaybe
} from "../schema/generated/graphql.js";
import {logger} from "../logging/logger.js";
import {ZabbixQueryTemplatesRequest} from "../datasources/zabbix-templates.js";
import {ZabbixCreateHostRequest} from "../datasources/zabbix-hosts.js";
import {ZabbixQueryTemplatesRequest, TemplateHelper} from "../datasources/zabbix-templates.js";
import {isZabbixErrorResult, ParsedArgs, ZabbixErrorResult} from "../datasources/zabbix-request.js";
import {CreateHostGroupResult, GroupHelper, ZabbixCreateHostGroupRequest} from "../datasources/zabbix-hostgroups.js";
import {ZABBIX_EDGE_DEVICE_BASE_GROUP, zabbixAPI} from "../datasources/zabbix-api.js";
@ -110,32 +111,54 @@ export class HostImporter {
break
}
}
let deviceImportResult: {
hostids?: string[];
error?: any;
} = await zabbixAPI.requestByPath("host.create", new ParsedArgs(
let templateids = device.templateids ? [...device.templateids as number[]] : [];
if (device.templateNames?.length) {
const resolvedTemplateids = await TemplateHelper.findTemplateIdsByName(device.templateNames as string[], zabbixAPI, zabbixAuthToken, cookie);
if (resolvedTemplateids) {
templateids.push(...resolvedTemplateids);
} else {
result.push({
deviceKey: device.deviceKey,
message: `Unable to find templates: ${device.templateNames}`
});
continue;
}
}
if (templateids.length === 0) {
const defaultTemplateId = await HostImporter.getTemplateIdForDeviceType(device.deviceType, zabbixAuthToken, cookie);
if (defaultTemplateId) {
templateids.push(defaultTemplateId);
}
}
// Deduplicate
groupids = Array.from(new Set(groupids));
templateids = Array.from(new Set(templateids));
let deviceImportResult = await new ZabbixCreateHostRequest(zabbixAuthToken, cookie).executeRequestReturnError(zabbixAPI, new ParsedArgs(
{
host: device.deviceKey,
name: device.name,
location: device.location,
templateids: [
await HostImporter.getTemplateIdForDeviceType(
device.deviceType, zabbixAuthToken, cookie)],
hostgroupids: groupids
templateids: templateids,
hostgroupids: groupids,
macros: device.macros
}
), zabbixAuthToken, cookie)
if (deviceImportResult?.hostids?.length) {
result.push({
deviceKey: device.deviceKey,
hostid: deviceImportResult.hostids[0],
})
} else {
))
if (isZabbixErrorResult(deviceImportResult)) {
result.push({
deviceKey: device.deviceKey,
message: `Unable to import deviceKey=${device.deviceKey}: ${deviceImportResult.error.message}`,
error: deviceImportResult.error
})
} else {
result.push({
deviceKey: device.deviceKey,
hostid: deviceImportResult.hostids![0]?.toString(),
})
}
}

View file

@ -0,0 +1,237 @@
import {SmoketestResponse, SmoketestStep} from "../schema/generated/graphql.js";
import {HostImporter} from "./host_importer.js";
import {HostDeleter} from "./host_deleter.js";
import {TemplateImporter} from "./template_importer.js";
import {TemplateDeleter} from "./template_deleter.js";
import {logger} from "../logging/logger.js";
import {zabbixAPI} from "../datasources/zabbix-api.js";
import {ZabbixQueryHostsGenericRequest} from "../datasources/zabbix-hosts.js";
import {ZabbixQueryTemplatesRequest} from "../datasources/zabbix-templates.js";
import {ParsedArgs} from "../datasources/zabbix-request.js";
export class RegressionTestExecutor {
public static async runAllRegressionTests(hostName: string, groupName: string, zabbixAuthToken?: string, cookie?: string): Promise<SmoketestResponse> {
const steps: SmoketestStep[] = [];
let success = true;
try {
// Regression 1: Locations query argument order
// This verifies the fix where getLocations was called with (authToken, args) instead of (args, authToken)
try {
const locations = await zabbixAPI.getLocations(new ParsedArgs({ name_pattern: "NonExistent_" + Math.random() }), zabbixAuthToken, cookie);
steps.push({
name: "REG-LOC: Locations query argument order",
success: true,
message: "Locations query executed without session error"
});
} catch (error: any) {
steps.push({
name: "REG-LOC: Locations query argument order",
success: false,
message: `Failed: ${error.message}`
});
success = false;
}
// Regression 2: Template lookup by technical name
// Verifies that importHosts can link templates using their technical name (host)
const regTemplateName = "REG_TEMP_" + Math.random().toString(36).substring(7);
const regGroupName = "Templates/Roadwork/Devices";
const hostGroupName = "Roadwork/Devices";
const tempResult = await TemplateImporter.importTemplates([{
host: regTemplateName,
name: "Regression Test Template",
groupNames: [regGroupName]
}], zabbixAuthToken, cookie);
const tempSuccess = !!tempResult?.length && !tempResult[0].error;
steps.push({
name: "REG-TEMP: Template technical name lookup",
success: tempSuccess,
message: tempSuccess ? `Template ${regTemplateName} created and searchable by technical name` : `Failed to create template`
});
if (!tempSuccess) success = false;
// Regression 3: HTTP Agent URL support
// Verifies that templates with HTTP Agent items (including URL) can be imported
const httpTempName = "REG_HTTP_" + Math.random().toString(36).substring(7);
const httpTempResult = await TemplateImporter.importTemplates([{
host: httpTempName,
name: "Regression HTTP Template",
groupNames: [regGroupName],
items: [{
name: "HTTP Master",
type: 19, // HTTP Agent
key: "http.master",
value_type: 4,
url: "https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41&current=temperature_2m",
delay: "1m",
history: "0"
}]
}], zabbixAuthToken, cookie);
const httpSuccess = !!httpTempResult?.length && !httpTempResult[0].error;
steps.push({
name: "REG-HTTP: HTTP Agent URL support",
success: httpSuccess,
message: httpSuccess ? `Template ${httpTempName} with HTTP Agent item created successfully` : `Failed: ${httpTempResult?.[0]?.message}`
});
if (!httpSuccess) success = false;
// Regression 4: User Macro assignment for host and template creation
const macroTemplateName = "REG_MACRO_TEMP_" + Math.random().toString(36).substring(7);
const macroHostName = "REG_MACRO_HOST_" + Math.random().toString(36).substring(7);
const macroTempResult = await TemplateImporter.importTemplates([{
host: macroTemplateName,
name: "Regression Macro Template",
groupNames: [regGroupName],
macros: [
{ macro: "{$TEMP_MACRO}", value: "temp_value" }
]
}], zabbixAuthToken, cookie);
const macroTempImportSuccess = !!macroTempResult?.length && !macroTempResult[0].error;
let macroHostImportSuccess = false;
let macroVerifySuccess = false;
if (macroTempImportSuccess) {
const macroHostResult = await HostImporter.importHosts([{
deviceKey: macroHostName,
deviceType: "RegressionHost",
groupNames: [hostGroupName],
templateNames: [macroTemplateName],
macros: [
{ macro: "{$HOST_MACRO}", value: "host_value" }
]
}], zabbixAuthToken, cookie);
macroHostImportSuccess = !!macroHostResult?.length && !!macroHostResult[0].hostid;
if (macroHostImportSuccess) {
// Verify macros on host
const verifyHostResult = await new ZabbixQueryHostsGenericRequest("host.get", zabbixAuthToken, cookie)
.executeRequestReturnError(zabbixAPI, new ParsedArgs({
filter_host: macroHostName,
selectMacros: "extend"
}));
// Verify macros on template
const verifyTempResult = await new ZabbixQueryTemplatesRequest(zabbixAuthToken, cookie)
.executeRequestReturnError(zabbixAPI, new ParsedArgs({
filter_host: macroTemplateName,
selectMacros: "extend"
}));
const hasHostMacro = Array.isArray(verifyHostResult) && verifyHostResult.length > 0 &&
(verifyHostResult[0] as any).macros?.some((m: any) => m.macro === "{$HOST_MACRO}" && m.value === "host_value");
const hasTempMacro = Array.isArray(verifyTempResult) && verifyTempResult.length > 0 &&
(verifyTempResult[0] as any).macros?.some((m: any) => m.macro === "{$TEMP_MACRO}" && m.value === "temp_value");
macroVerifySuccess = !!(hasHostMacro && hasTempMacro);
}
}
const macroOverallSuccess = macroTempImportSuccess && macroHostImportSuccess && macroVerifySuccess;
steps.push({
name: "REG-MACRO: User Macro assignment",
success: macroOverallSuccess,
message: macroOverallSuccess
? "Macros successfully assigned to template and host"
: `Failed: TempImport=${macroTempImportSuccess}, HostImport=${macroHostImportSuccess}, Verify=${macroVerifySuccess}`
});
if (!macroOverallSuccess) success = false;
// Regression 5: Host retrieval and visibility (allHosts output fields fix)
if (success) {
const hostResult = await HostImporter.importHosts([{
deviceKey: hostName,
deviceType: "RegressionHost",
groupNames: [hostGroupName],
templateNames: [regTemplateName]
}], zabbixAuthToken, cookie);
const hostImportSuccess = !!hostResult?.length && !!hostResult[0].hostid;
if (hostImportSuccess) {
const hostid = hostResult[0].hostid;
logger.info(`REG-HOST: Host ${hostName} imported with ID ${hostid}. Verifying visibility...`);
// Verify visibility via allHosts (simulated)
const verifyResult = await new ZabbixQueryHostsGenericRequest("host.get", zabbixAuthToken, cookie)
.executeRequestReturnError(zabbixAPI, new ParsedArgs({
filter_host: hostName
}));
const verified = Array.isArray(verifyResult) && verifyResult.length > 0 && (verifyResult[0] as any).host === hostName;
let fieldsVerified = false;
if (verified) {
const host = verifyResult[0] as any;
const hasGroups = Array.isArray(host.hostgroups) && host.hostgroups.length > 0;
const hasTemplates = Array.isArray(host.parentTemplates) && host.parentTemplates.length > 0;
fieldsVerified = hasGroups && hasTemplates;
if (!fieldsVerified) {
logger.error(`REG-HOST: Fields verification failed. Groups: ${hasGroups}, Templates: ${hasTemplates}. Host data: ${JSON.stringify(host)}`);
}
}
if (!verified) {
logger.error(`REG-HOST: Verification failed. Zabbix result: ${JSON.stringify(verifyResult)}`);
}
steps.push({
name: "REG-HOST: Host retrieval and visibility (incl. groups and templates)",
success: verified && fieldsVerified,
message: verified
? (fieldsVerified ? `Host ${hostName} retrieved successfully with groups and templates` : `Host ${hostName} retrieved but missing groups or templates`)
: "Host not found after import (output fields issue?)"
});
if (!verified || !fieldsVerified) success = false;
} else {
steps.push({
name: "REG-HOST: Host retrieval and visibility",
success: false,
message: `Host import failed: ${hostResult?.[0]?.message || hostResult?.[0]?.error?.message || "Unknown error"}`
});
success = false;
}
}
// Step 1: Create Host Group (Legacy test kept for compatibility)
const groupResult = await HostImporter.importHostGroups([{
groupName: groupName
}], zabbixAuthToken, cookie);
const groupSuccess = !!groupResult?.length && !groupResult[0].error;
steps.push({
name: "Create Host Group",
success: groupSuccess,
message: groupSuccess ? `Host group ${groupName} created` : `Failed: ${groupResult?.[0]?.error?.message || "Unknown error"}`
});
if (!groupSuccess) success = false;
// Cleanup
await HostDeleter.deleteHosts(null, hostName, zabbixAuthToken, cookie);
await HostDeleter.deleteHosts(null, macroHostName, zabbixAuthToken, cookie);
await TemplateDeleter.deleteTemplates(null, regTemplateName, zabbixAuthToken, cookie);
await TemplateDeleter.deleteTemplates(null, httpTempName, zabbixAuthToken, cookie);
await TemplateDeleter.deleteTemplates(null, macroTemplateName, zabbixAuthToken, cookie);
// We don't delete the group here as it might be shared or used by other tests in this run
} catch (error: any) {
success = false;
steps.push({
name: "Execution Error",
success: false,
message: error.message || String(error)
});
}
return {
success,
message: success ? "Regression tests passed successfully" : "Regression tests failed",
steps
};
}
}

View file

@ -0,0 +1,158 @@
import {SmoketestResponse, SmoketestStep} from "../schema/generated/graphql.js";
import {TemplateImporter} from "./template_importer.js";
import {HostImporter} from "./host_importer.js";
import {HostDeleter} from "./host_deleter.js";
import {TemplateDeleter} from "./template_deleter.js";
import {zabbixAPI} from "../datasources/zabbix-api.js";
import {ZabbixQueryHostsGenericRequest} from "../datasources/zabbix-hosts.js";
import {ParsedArgs} from "../datasources/zabbix-request.js";
export class SmoketestExecutor {
public static async runSmoketest(hostName: string, templateName: string, groupName: string, zabbixAuthToken?: string, cookie?: string): Promise<SmoketestResponse> {
const steps: SmoketestStep[] = [];
let success = true;
try {
// Step 0: Create Template Group
const templateGroupResult = await TemplateImporter.importTemplateGroups([{
groupName: groupName
}], zabbixAuthToken, cookie);
const templateGroupSuccess = !!templateGroupResult?.length && !templateGroupResult[0].error;
steps.push({
name: "Create Template Group",
success: templateGroupSuccess,
message: templateGroupSuccess ? `Template group ${groupName} created` : `Failed: ${templateGroupResult?.[0]?.error?.message || "Unknown error"}`
});
if (!templateGroupSuccess) success = false;
// Step 1: Create Template
if (success) {
const templateResult = await TemplateImporter.importTemplates([{
host: templateName,
name: templateName,
groupNames: [groupName]
}], zabbixAuthToken, cookie);
const templateSuccess = !!templateResult?.length && !templateResult[0].error;
steps.push({
name: "Create Template",
success: templateSuccess,
message: templateSuccess ? `Template ${templateName} created` : `Failed: ${templateResult?.[0]?.error?.message || "Unknown error"}`
});
if (!templateSuccess) success = false;
} else {
steps.push({ name: "Create Template", success: false, message: "Skipped due to previous failures" });
}
// Step 2: Create Host Group
const groupResult = await HostImporter.importHostGroups([{
groupName: groupName
}], zabbixAuthToken, cookie);
const groupSuccess = !!groupResult?.length && !groupResult[0].error;
steps.push({
name: "Create Host Group",
success: groupSuccess,
message: groupSuccess ? `Host group ${groupName} created` : `Failed: ${groupResult?.[0]?.error?.message || "Unknown error"}`
});
if (!groupSuccess) success = false;
// Step 3: Create Host and Link to Template
if (success) {
const hostResult = await HostImporter.importHosts([{
deviceKey: hostName,
deviceType: "ZabbixHost",
groupNames: [groupName],
templateNames: [templateName]
}], zabbixAuthToken, cookie);
const hostSuccess = !!hostResult?.length && !hostResult[0].error;
steps.push({
name: "Create and Link Host",
success: hostSuccess,
message: hostSuccess ? `Host ${hostName} created and linked to ${templateName}` : `Failed: ${hostResult?.[0]?.error?.message || "Unknown error"}`
});
if (!hostSuccess) success = false;
} else {
steps.push({ name: "Create and Link Host", success: false, message: "Skipped due to previous failures" });
}
// Step 4: Verify Host Linkage
if (success) {
const verifyResult = await new ZabbixQueryHostsGenericRequest("host.get", zabbixAuthToken, cookie)
.executeRequestReturnError(zabbixAPI, new ParsedArgs({
filter_host: hostName,
selectParentTemplates: ["name"]
}));
let verified = false;
if (Array.isArray(verifyResult) && verifyResult.length > 0) {
const host = verifyResult[0] as any;
const linkedTemplates = host.parentTemplates || [];
verified = linkedTemplates.some((t: any) => t.name === templateName);
}
steps.push({
name: "Verify Host Linkage",
success: verified,
message: verified ? `Verification successful: Host ${hostName} is linked to ${templateName}` : `Verification failed: Host or linkage not found`
});
if (!verified) success = false;
} else {
steps.push({ name: "Verify Host Linkage", success: false, message: "Skipped due to previous failures" });
}
} catch (error: any) {
success = false;
steps.push({
name: "Execution Error",
success: false,
message: error.message || String(error)
});
} finally {
// Step 5: Cleanup
const cleanupSteps: SmoketestStep[] = [];
// Delete Host
const deleteHostRes = await HostDeleter.deleteHosts(null, hostName, zabbixAuthToken, cookie);
cleanupSteps.push({
name: "Cleanup: Delete Host",
success: deleteHostRes.every(r => !r.error),
message: deleteHostRes.length > 0 ? deleteHostRes[0].message : "Host not found for deletion"
});
// Delete Template
const deleteTemplateRes = await TemplateDeleter.deleteTemplates(null, templateName, zabbixAuthToken, cookie);
cleanupSteps.push({
name: "Cleanup: Delete Template",
success: deleteTemplateRes.every(r => !r.error),
message: deleteTemplateRes.length > 0 ? deleteTemplateRes[0].message : "Template not found for deletion"
});
// Delete Host Group
const deleteGroupRes = await HostDeleter.deleteHostGroups(null, groupName, zabbixAuthToken, cookie);
cleanupSteps.push({
name: "Cleanup: Delete Host Group",
success: deleteGroupRes.every(r => !r.error),
message: deleteGroupRes.length > 0 ? deleteGroupRes[0].message : "Host group not found for deletion"
});
// We also need to delete the template group if it's different or just try to delete it
// In our setup, TemplateImporter creates it if it doesn't exist.
const deleteTemplateGroupRes = await TemplateDeleter.deleteTemplateGroups(null, groupName, zabbixAuthToken, cookie);
cleanupSteps.push({
name: "Cleanup: Delete Template Group",
success: deleteTemplateGroupRes.every(r => !r.error),
message: deleteTemplateGroupRes.length > 0 ? deleteTemplateGroupRes[0].message : "Template group not found for deletion"
});
steps.push(...cleanupSteps);
}
return {
success,
message: success ? "Smoketest passed successfully" : "Smoketest failed",
steps
};
}
}

View file

@ -1,9 +1,9 @@
import {DeleteResponse} from "../schema/generated/graphql.js";
import {
TemplateHelper,
ZabbixDeleteTemplateGroupsRequest,
ZabbixDeleteTemplatesRequest,
ZabbixQueryTemplateGroupRequest,
ZabbixQueryTemplatesRequest
ZabbixQueryTemplateGroupRequest
} from "../datasources/zabbix-templates.js";
import {isZabbixErrorResult, ParsedArgs} from "../datasources/zabbix-request.js";
import {zabbixAPI} from "../datasources/zabbix-api.js";
@ -15,11 +15,8 @@ export class TemplateDeleter {
let idsToDelete = templateids ? [...templateids] : [];
if (name_pattern) {
const queryResult = await new ZabbixQueryTemplatesRequest(zabbixAuthToken, cookie)
.executeRequestReturnError(zabbixAPI, new ParsedArgs({ name_pattern: name_pattern }));
if (!isZabbixErrorResult(queryResult) && Array.isArray(queryResult)) {
const foundIds = queryResult.map(t => Number(t.templateid));
const foundIds = await TemplateHelper.findTemplateIdsByName([name_pattern], zabbixAPI, zabbixAuthToken, cookie);
if (foundIds) {
// Merge and deduplicate
idsToDelete = Array.from(new Set([...idsToDelete, ...foundIds]));
}

View file

@ -131,7 +131,8 @@ export class TemplateImporter {
groups: groupids.map(id => ({ groupid: id })),
uuid: template.uuid,
templates: linkedTemplates,
tags: template.tags?.map(t => ({ tag: t.tag, value: t.value || "" }))
tags: template.tags?.map(t => ({ tag: t.tag, value: t.value || "" })),
macros: template.macros
}
let templateImportResult = await new ZabbixCreateTemplateRequest(zabbixAuthToken, cookie)
@ -176,8 +177,8 @@ export class TemplateImporter {
preprocessing: item.preprocessing?.map(p => ({
type: p.type,
params: p.params.join("\n"),
error_handler: p.error_handler,
error_handler_params: p.error_handler_params
error_handler: p.error_handler ?? 0,
error_handler_params: p.error_handler_params ?? ""
})),
tags: item.tags?.map(t => ({ tag: t.tag, value: t.value || "" }))
}

View file

@ -17,8 +17,8 @@ export enum DeviceCommunicationType {
}
export enum DeviceStatus {
DISABLED = "0",
ENABLED = "1"
ENABLED = "0",
DISABLED = "1"
}
export enum StorageItemType {

View file

@ -1,6 +1,8 @@
import {DeviceCommunicationType, DeviceStatus, Permission, StorageItemType} from '../../model/model_enum_values.js';
import {GraphQLResolveInfo, GraphQLScalarType, GraphQLScalarTypeConfig} from 'graphql';
import { DeviceCommunicationType } from '../../model/model_enum_values.js';
import { StorageItemType } from '../../model/model_enum_values.js';
import { DeviceStatus } from '../../model/model_enum_values.js';
import { Permission } from '../../model/model_enum_values.js';
import { GraphQLResolveInfo, GraphQLScalarType, GraphQLScalarTypeConfig } from 'graphql';
export type Maybe<T> = T | null;
export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
@ -50,8 +52,14 @@ export interface CreateHost {
groupids?: InputMaybe<Array<InputMaybe<Scalars['Int']['input']>>>;
/** Location information for the host. */
location?: InputMaybe<LocationInput>;
/** User macros to assign to the host. */
macros?: InputMaybe<Array<CreateMacro>>;
/** Optional display name of the device (must be unique if provided - default is to set display name to deviceKey). */
name?: InputMaybe<Scalars['String']['input']>;
/** List of template names to link to the host. */
templateNames?: InputMaybe<Array<InputMaybe<Scalars['String']['input']>>>;
/** List of template IDs to link to the host. */
templateids?: InputMaybe<Array<InputMaybe<Scalars['Int']['input']>>>;
}
/** Input for creating or identifying a host group. */
@ -94,7 +102,7 @@ export interface CreateItemPreprocessing {
error_handler_params?: InputMaybe<Scalars['String']['input']>;
/** Parameters for the preprocessing step. */
params: Array<Scalars['String']['input']>;
/** Type of preprocessing step (e.g., 12 for JSONPath, 21 for JavaScript). */
/** Type of preprocessing step (e.g. 12 for JSONPath, 21 for JavaScript). */
type: Scalars['Int']['input'];
}
@ -104,6 +112,14 @@ export interface CreateLinkedTemplate {
name: Scalars['String']['input'];
}
/** Input for creating a user macro. */
export interface CreateMacro {
/** Macro name (e.g. '{$LAT}'). */
macro: Scalars['String']['input'];
/** Macro value. */
value: Scalars['String']['input'];
}
/** Reference to a master item for dependent items. */
export interface CreateMasterItem {
/** The technical key of the master item. */
@ -128,6 +144,8 @@ export interface CreateTemplate {
host: Scalars['String']['input'];
/** List of items to create within the template. */
items?: InputMaybe<Array<CreateTemplateItem>>;
/** User macros to assign to the template. */
macros?: InputMaybe<Array<CreateMacro>>;
/** Visible name of the template. */
name?: InputMaybe<Scalars['String']['input']>;
/** Tags to assign to the template. */
@ -165,7 +183,7 @@ export interface CreateTemplateItem {
delay?: InputMaybe<Scalars['String']['input']>;
/** Description of the item. */
description?: InputMaybe<Scalars['String']['input']>;
/** History storage period (e.g., '2d', '90d'). */
/** History storage period (e.g. '2d', '90d'). */
history?: InputMaybe<Scalars['String']['input']>;
/** Technical key of the item. */
key: Scalars['String']['input'];
@ -175,15 +193,19 @@ export interface CreateTemplateItem {
name: Scalars['String']['input'];
/** Preprocessing steps for the item values. */
preprocessing?: InputMaybe<Array<CreateItemPreprocessing>>;
/** Zabbix item status (0 for Enabled, 1 for Disabled). */
status?: InputMaybe<Scalars['Int']['input']>;
/** Tags to assign to the item. */
tags?: InputMaybe<Array<CreateTag>>;
/** Zabbix item type (e.g., 0 for Zabbix Agent, 18 for Dependent). */
/** Zabbix item type (e.g. 0 for Zabbix Agent, 18 for Dependent). */
type?: InputMaybe<Scalars['Int']['input']>;
/** Units of the value. */
units?: InputMaybe<Scalars['String']['input']>;
/** URL for HTTP Agent items. */
url?: InputMaybe<Scalars['String']['input']>;
/** Internally used unique id. */
uuid?: InputMaybe<Scalars['String']['input']>;
/** Type of information (e.g., 0 for Float, 3 for Int, 4 for Text). */
/** Type of information (e.g. 0 for Float, 3 for Int, 4 for Text). */
value_type?: InputMaybe<Scalars['Int']['input']>;
}
@ -211,6 +233,10 @@ export interface Device {
hostgroups?: Maybe<Array<HostGroup>>;
/** Internal Zabbix ID of the device. */
hostid: Scalars['ID']['output'];
/** Host inventory data. */
inventory?: Maybe<Inventory>;
/** List of monitored items for this host. */
items?: Maybe<Array<ZabbixItem>>;
/** Visible name of the device. */
name?: Maybe<Scalars['String']['output']>;
/** State of the device. */
@ -266,7 +292,7 @@ export interface DeviceValueMessage {
deviceType?: Maybe<Scalars['String']['output']>;
/**
* Represents the timestamp at which a specific event, message, or data point was created or recorded.
* The format should align with standard expectations (e.g., ISO 8601).
* The format should align with standard expectations (e.g. ISO 8601).
*/
timestamp?: Maybe<Scalars['String']['output']>;
/**
@ -329,6 +355,10 @@ export interface GenericDevice extends Device, Host {
hostgroups?: Maybe<Array<HostGroup>>;
/** Internal Zabbix ID of the device. */
hostid: Scalars['ID']['output'];
/** Host inventory data. */
inventory?: Maybe<Inventory>;
/** List of monitored items for this host. */
items?: Maybe<Array<ZabbixItem>>;
/** Visible name of the device. */
name?: Maybe<Scalars['String']['output']>;
/** State of the generic device. */
@ -379,6 +409,10 @@ export interface Host {
hostgroups?: Maybe<Array<HostGroup>>;
/** Internal Zabbix ID of the host. */
hostid: Scalars['ID']['output'];
/** Host inventory data. */
inventory?: Maybe<Inventory>;
/** List of monitored items for this host. */
items?: Maybe<Array<ZabbixItem>>;
/** Visible name of the host. */
name?: Maybe<Scalars['String']['output']>;
}
@ -476,6 +510,18 @@ export interface Mutation {
* Authentication: Requires `zbx_session` cookie or `zabbix-auth-token` header.
*/
createHost?: Maybe<CreateHostResponse>;
/**
* Delete host groups by their IDs or by a name pattern.
*
* Authentication: Requires `zbx_session` cookie or `zabbix-auth-token` header.
*/
deleteHostGroups?: Maybe<Array<DeleteResponse>>;
/**
* Delete hosts by their IDs or by a name pattern.
*
* Authentication: Requires `zbx_session` cookie or `zabbix-auth-token` header.
*/
deleteHosts?: Maybe<Array<DeleteResponse>>;
/**
* Delete template groups by their IDs or by a name pattern.
*
@ -526,6 +572,10 @@ export interface Mutation {
* Authentication: Requires `zbx_session` cookie or `zabbix-auth-token` header.
*/
importUserRights?: Maybe<ImportUserRightsResult>;
/** Runs all regression tests. */
runAllRegressionTests: SmoketestResponse;
/** Runs a smoketest: creates a template, links a host, verifies it, and cleans up. */
runSmoketest: SmoketestResponse;
}
@ -533,7 +583,20 @@ export interface MutationCreateHostArgs {
host: Scalars['String']['input'];
hostgroupids: Array<Scalars['Int']['input']>;
location?: InputMaybe<LocationInput>;
templateids: Array<Scalars['Int']['input']>;
templateNames?: InputMaybe<Array<InputMaybe<Scalars['String']['input']>>>;
templateids?: InputMaybe<Array<InputMaybe<Scalars['Int']['input']>>>;
}
export interface MutationDeleteHostGroupsArgs {
groupids?: InputMaybe<Array<Scalars['Int']['input']>>;
name_pattern?: InputMaybe<Scalars['String']['input']>;
}
export interface MutationDeleteHostsArgs {
hostids?: InputMaybe<Array<Scalars['Int']['input']>>;
name_pattern?: InputMaybe<Scalars['String']['input']>;
}
@ -574,6 +637,19 @@ export interface MutationImportUserRightsArgs {
input: UserRightsInput;
}
export interface MutationRunAllRegressionTestsArgs {
groupName: Scalars['String']['input'];
hostName: Scalars['String']['input'];
}
export interface MutationRunSmoketestArgs {
groupName: Scalars['String']['input'];
hostName: Scalars['String']['input'];
templateName: Scalars['String']['input'];
}
/** Operational data common to most devices. */
export interface OperationalDeviceData {
__typename?: 'OperationalDeviceData';
@ -581,7 +657,7 @@ export interface OperationalDeviceData {
error?: Maybe<Array<ErrorPayload>>;
/** Current location of the device. */
location?: Maybe<Location>;
/** Signal strength (e.g., WiFi or GSM). */
/** Signal strength (e.g. WiFi or GSM). */
signalstrength?: Maybe<Scalars['Float']['output']>;
/** Device temperature. */
temperature?: Maybe<Scalars['Float']['output']>;
@ -742,6 +818,28 @@ export interface QueryUserPermissionsArgs {
objectNames?: InputMaybe<Array<Scalars['String']['input']>>;
}
/** Response object for the smoketest operation. */
export interface SmoketestResponse {
__typename?: 'SmoketestResponse';
/** Overall status message. */
message?: Maybe<Scalars['String']['output']>;
/** Detailed results for each step. */
steps: Array<SmoketestStep>;
/** True if all steps of the smoketest succeeded. */
success: Scalars['Boolean']['output'];
}
/** Results for a single step in the smoketest. */
export interface SmoketestStep {
__typename?: 'SmoketestStep';
/** Status message or error message for the step. */
message?: Maybe<Scalars['String']['output']>;
/** Name of the step (e.g. 'Create Template'). */
name: Scalars['String']['output'];
/** True if the step succeeded. */
success: Scalars['Boolean']['output'];
}
export enum SortOrder {
/** Deliver values in ascending order */
Asc = 'asc',
@ -754,6 +852,10 @@ export { StorageItemType };
/** Represents a Zabbix template. */
export interface Template {
__typename?: 'Template';
/** Technical name of the template. */
host: Scalars['String']['output'];
/** List of items for this template. */
items?: Maybe<Array<ZabbixItem>>;
/** Name of the template. */
name?: Maybe<Scalars['String']['output']>;
/** Internal Zabbix ID of the template. */
@ -843,7 +945,7 @@ export interface UserRoleInput {
readonly?: InputMaybe<Scalars['Int']['input']>;
/** Specific rules for the role. */
rules?: InputMaybe<UserRoleRulesInput>;
/** Type of role (e.g., 1 for User, 2 for Admin, 3 for Super Admin). */
/** Type of role (e.g. 1 for User, 2 for Admin, 3 for Super Admin). */
type?: InputMaybe<Scalars['Int']['input']>;
}
@ -883,7 +985,7 @@ export interface UserRoleRule {
export interface UserRoleRuleInput {
/** Name of the rule/element. */
name?: InputMaybe<Scalars['String']['input']>;
/** Status (e.g., 1 for enabled, 0 for disabled). */
/** Status (e.g. 1 for enabled, 0 for disabled). */
status?: InputMaybe<Scalars['Int']['input']>;
}
@ -920,7 +1022,7 @@ export interface UserRoleRulesInput {
api?: InputMaybe<Array<Scalars['String']['input']>>;
/** Whether API access is enabled (1) or not (0). */
api_access?: InputMaybe<Scalars['Int']['input']>;
/** API mode (e.g., 0 for white-list, 1 for black-list). */
/** API mode (e.g. 0 for white-list, 1 for black-list). */
api_mode?: InputMaybe<Scalars['Int']['input']>;
/** Module access rules. */
modules?: InputMaybe<Array<UserRoleModuleInput>>;
@ -1002,6 +1104,12 @@ export interface ZabbixItem {
__typename?: 'ZabbixItem';
/** Attribute name if this item is part of a hierarchical mapping. */
attributeName?: Maybe<Scalars['String']['output']>;
/** Update interval. */
delay?: Maybe<Scalars['String']['output']>;
/** Description of the item. */
description?: Maybe<Scalars['String']['output']>;
/** History storage period (e.g. '2d', '90d'). */
history?: Maybe<Scalars['String']['output']>;
/** Internal Zabbix ID of the host this item belongs to. */
hostid?: Maybe<Scalars['Int']['output']>;
/** Hosts that this item is linked to. */
@ -1014,13 +1122,27 @@ export interface ZabbixItem {
lastclock?: Maybe<Scalars['Int']['output']>;
/** Last value retrieved for this item. */
lastvalue?: Maybe<Scalars['String']['output']>;
/** Master item for dependent items. */
master_item?: Maybe<ZabbixItem>;
/** Master item ID for dependent items. */
master_itemid?: Maybe<Scalars['Int']['output']>;
/** Visible name of the item. */
name: Scalars['String']['output'];
/** Preprocessing steps for the item. */
preprocessing?: Maybe<Array<Scalars['JSONObject']['output']>>;
/** Status of the item (ENABLED or DISABLED). */
status?: Maybe<DeviceStatus>;
/** Raw Zabbix item status as integer. */
status_int?: Maybe<Scalars['Int']['output']>;
/** Tags assigned to the item. */
tags?: Maybe<Array<Scalars['JSONObject']['output']>>;
/** Communication type used by the item. */
type?: Maybe<DeviceCommunicationType>;
/** Type of information (e.g., 0 for Float, 3 for Int, 4 for Text). */
/** Raw Zabbix item type as integer. */
type_int?: Maybe<Scalars['Int']['output']>;
/** Units of the value. */
units?: Maybe<Scalars['String']['output']>;
/** Type of information (e.g. 0 for Float, 3 for Int, 4 for Text). */
value_type: Scalars['Int']['output'];
}
@ -1094,13 +1216,13 @@ export type DirectiveResolverFn<TResult = {}, TParent = {}, TContext = {}, TArgs
/** Mapping of interface types */
export type ResolversInterfaceTypes<_RefType extends Record<string, unknown>> = {
Device: ( GenericDevice );
Device: ( Omit<GenericDevice, 'items'> & { items?: Maybe<Array<_RefType['ZabbixItem']>> } );
DeviceState: ( GenericDeviceState );
DeviceValue: never;
DeviceValueMessage: never;
Error: ( ApiError );
GpsPosition: ( Location );
Host: ( GenericDevice ) | ( Omit<ZabbixHost, 'items'> & { items?: Maybe<Array<_RefType['ZabbixItem']>> } );
Host: ( Omit<GenericDevice, 'items'> & { items?: Maybe<Array<_RefType['ZabbixItem']>> } ) | ( Omit<ZabbixHost, 'items' | 'parentTemplates'> & { items?: Maybe<Array<_RefType['ZabbixItem']>>, parentTemplates?: Maybe<Array<_RefType['Template']>> } );
};
/** Mapping between all available schema types and the resolvers types */
@ -1113,6 +1235,7 @@ export type ResolversTypes = {
CreateHostResponse: ResolverTypeWrapper<CreateHostResponse>;
CreateItemPreprocessing: CreateItemPreprocessing;
CreateLinkedTemplate: CreateLinkedTemplate;
CreateMacro: CreateMacro;
CreateMasterItem: CreateMasterItem;
CreateTag: CreateTag;
CreateTemplate: CreateTemplate;
@ -1132,7 +1255,7 @@ export type ResolversTypes = {
Error: ResolverTypeWrapper<ResolversInterfaceTypes<ResolversTypes>['Error']>;
ErrorPayload: ResolverTypeWrapper<ErrorPayload>;
Float: ResolverTypeWrapper<Scalars['Float']['output']>;
GenericDevice: ResolverTypeWrapper<GenericDevice>;
GenericDevice: ResolverTypeWrapper<Omit<GenericDevice, 'items'> & { items?: Maybe<Array<ResolversTypes['ZabbixItem']>> }>;
GenericDeviceState: ResolverTypeWrapper<GenericDeviceState>;
GenericResponse: ResolverTypeWrapper<GenericResponse>;
GpsPosition: ResolverTypeWrapper<ResolversInterfaceTypes<ResolversTypes>['GpsPosition']>;
@ -1153,10 +1276,12 @@ export type ResolversTypes = {
Permission: Permission;
PermissionRequest: PermissionRequest;
Query: ResolverTypeWrapper<{}>;
SmoketestResponse: ResolverTypeWrapper<SmoketestResponse>;
SmoketestStep: ResolverTypeWrapper<SmoketestStep>;
SortOrder: SortOrder;
StorageItemType: StorageItemType;
String: ResolverTypeWrapper<Scalars['String']['output']>;
Template: ResolverTypeWrapper<Template>;
Template: ResolverTypeWrapper<Omit<Template, 'items'> & { items?: Maybe<Array<ResolversTypes['ZabbixItem']>> }>;
Time: ResolverTypeWrapper<Scalars['Time']['output']>;
UserGroup: ResolverTypeWrapper<UserGroup>;
UserGroupInput: UserGroupInput;
@ -1174,8 +1299,8 @@ export type ResolversTypes = {
WidgetPreview: ResolverTypeWrapper<WidgetPreview>;
ZabbixGroupRight: ResolverTypeWrapper<ZabbixGroupRight>;
ZabbixGroupRightInput: ZabbixGroupRightInput;
ZabbixHost: ResolverTypeWrapper<Omit<ZabbixHost, 'items'> & { items?: Maybe<Array<ResolversTypes['ZabbixItem']>> }>;
ZabbixItem: ResolverTypeWrapper<Omit<ZabbixItem, 'hosts'> & { hosts?: Maybe<Array<ResolversTypes['Host']>> }>;
ZabbixHost: ResolverTypeWrapper<Omit<ZabbixHost, 'items' | 'parentTemplates'> & { items?: Maybe<Array<ResolversTypes['ZabbixItem']>>, parentTemplates?: Maybe<Array<ResolversTypes['Template']>> }>;
ZabbixItem: ResolverTypeWrapper<Omit<ZabbixItem, 'hosts' | 'master_item'> & { hosts?: Maybe<Array<ResolversTypes['Host']>>, master_item?: Maybe<ResolversTypes['ZabbixItem']> }>;
};
/** Mapping between all available schema types and the resolvers parents */
@ -1188,6 +1313,7 @@ export type ResolversParentTypes = {
CreateHostResponse: CreateHostResponse;
CreateItemPreprocessing: CreateItemPreprocessing;
CreateLinkedTemplate: CreateLinkedTemplate;
CreateMacro: CreateMacro;
CreateMasterItem: CreateMasterItem;
CreateTag: CreateTag;
CreateTemplate: CreateTemplate;
@ -1205,7 +1331,7 @@ export type ResolversParentTypes = {
Error: ResolversInterfaceTypes<ResolversParentTypes>['Error'];
ErrorPayload: ErrorPayload;
Float: Scalars['Float']['output'];
GenericDevice: GenericDevice;
GenericDevice: Omit<GenericDevice, 'items'> & { items?: Maybe<Array<ResolversParentTypes['ZabbixItem']>> };
GenericDeviceState: GenericDeviceState;
GenericResponse: GenericResponse;
GpsPosition: ResolversInterfaceTypes<ResolversParentTypes>['GpsPosition'];
@ -1225,8 +1351,10 @@ export type ResolversParentTypes = {
OperationalDeviceData: OperationalDeviceData;
PermissionRequest: PermissionRequest;
Query: {};
SmoketestResponse: SmoketestResponse;
SmoketestStep: SmoketestStep;
String: Scalars['String']['output'];
Template: Template;
Template: Omit<Template, 'items'> & { items?: Maybe<Array<ResolversParentTypes['ZabbixItem']>> };
Time: Scalars['Time']['output'];
UserGroup: UserGroup;
UserGroupInput: UserGroupInput;
@ -1244,8 +1372,8 @@ export type ResolversParentTypes = {
WidgetPreview: WidgetPreview;
ZabbixGroupRight: ZabbixGroupRight;
ZabbixGroupRightInput: ZabbixGroupRightInput;
ZabbixHost: Omit<ZabbixHost, 'items'> & { items?: Maybe<Array<ResolversParentTypes['ZabbixItem']>> };
ZabbixItem: Omit<ZabbixItem, 'hosts'> & { hosts?: Maybe<Array<ResolversParentTypes['Host']>> };
ZabbixHost: Omit<ZabbixHost, 'items' | 'parentTemplates'> & { items?: Maybe<Array<ResolversParentTypes['ZabbixItem']>>, parentTemplates?: Maybe<Array<ResolversParentTypes['Template']>> };
ZabbixItem: Omit<ZabbixItem, 'hosts' | 'master_item'> & { hosts?: Maybe<Array<ResolversParentTypes['Host']>>, master_item?: Maybe<ResolversParentTypes['ZabbixItem']> };
};
export type ApiErrorResolvers<ContextType = any, ParentType extends ResolversParentTypes['ApiError'] = ResolversParentTypes['ApiError']> = {
@ -1297,6 +1425,8 @@ export type DeviceResolvers<ContextType = any, ParentType extends ResolversParen
host?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
hostgroups?: Resolver<Maybe<Array<ResolversTypes['HostGroup']>>, ParentType, ContextType>;
hostid?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
inventory?: Resolver<Maybe<ResolversTypes['Inventory']>, ParentType, ContextType>;
items?: Resolver<Maybe<Array<ResolversTypes['ZabbixItem']>>, ParentType, ContextType>;
name?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
state?: Resolver<Maybe<ResolversTypes['DeviceState']>, ParentType, ContextType>;
tags?: Resolver<Maybe<ResolversTypes['DeviceConfig']>, ParentType, ContextType>;
@ -1361,6 +1491,8 @@ export type GenericDeviceResolvers<ContextType = any, ParentType extends Resolve
host?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
hostgroups?: Resolver<Maybe<Array<ResolversTypes['HostGroup']>>, ParentType, ContextType>;
hostid?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
inventory?: Resolver<Maybe<ResolversTypes['Inventory']>, ParentType, ContextType>;
items?: Resolver<Maybe<Array<ResolversTypes['ZabbixItem']>>, ParentType, ContextType>;
name?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
state?: Resolver<Maybe<ResolversTypes['GenericDeviceState']>, ParentType, ContextType>;
tags?: Resolver<Maybe<ResolversTypes['DeviceConfig']>, ParentType, ContextType>;
@ -1391,6 +1523,8 @@ export type HostResolvers<ContextType = any, ParentType extends ResolversParentT
host?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
hostgroups?: Resolver<Maybe<Array<ResolversTypes['HostGroup']>>, ParentType, ContextType>;
hostid?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
inventory?: Resolver<Maybe<ResolversTypes['Inventory']>, ParentType, ContextType>;
items?: Resolver<Maybe<Array<ResolversTypes['ZabbixItem']>>, ParentType, ContextType>;
name?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
};
@ -1447,7 +1581,9 @@ export type LocationResolvers<ContextType = any, ParentType extends ResolversPar
};
export type MutationResolvers<ContextType = any, ParentType extends ResolversParentTypes['Mutation'] = ResolversParentTypes['Mutation']> = {
createHost?: Resolver<Maybe<ResolversTypes['CreateHostResponse']>, ParentType, ContextType, RequireFields<MutationCreateHostArgs, 'host' | 'hostgroupids' | 'templateids'>>;
createHost?: Resolver<Maybe<ResolversTypes['CreateHostResponse']>, ParentType, ContextType, RequireFields<MutationCreateHostArgs, 'host' | 'hostgroupids'>>;
deleteHostGroups?: Resolver<Maybe<Array<ResolversTypes['DeleteResponse']>>, ParentType, ContextType, Partial<MutationDeleteHostGroupsArgs>>;
deleteHosts?: Resolver<Maybe<Array<ResolversTypes['DeleteResponse']>>, ParentType, ContextType, Partial<MutationDeleteHostsArgs>>;
deleteTemplateGroups?: Resolver<Maybe<Array<ResolversTypes['DeleteResponse']>>, ParentType, ContextType, Partial<MutationDeleteTemplateGroupsArgs>>;
deleteTemplates?: Resolver<Maybe<Array<ResolversTypes['DeleteResponse']>>, ParentType, ContextType, Partial<MutationDeleteTemplatesArgs>>;
importHostGroups?: Resolver<Maybe<Array<ResolversTypes['CreateHostGroupResponse']>>, ParentType, ContextType, RequireFields<MutationImportHostGroupsArgs, 'hostGroups'>>;
@ -1455,6 +1591,8 @@ export type MutationResolvers<ContextType = any, ParentType extends ResolversPar
importTemplateGroups?: Resolver<Maybe<Array<ResolversTypes['CreateTemplateGroupResponse']>>, ParentType, ContextType, RequireFields<MutationImportTemplateGroupsArgs, 'templateGroups'>>;
importTemplates?: Resolver<Maybe<Array<ResolversTypes['ImportTemplateResponse']>>, ParentType, ContextType, RequireFields<MutationImportTemplatesArgs, 'templates'>>;
importUserRights?: Resolver<Maybe<ResolversTypes['ImportUserRightsResult']>, ParentType, ContextType, RequireFields<MutationImportUserRightsArgs, 'dryRun' | 'input'>>;
runAllRegressionTests?: Resolver<ResolversTypes['SmoketestResponse'], ParentType, ContextType, RequireFields<MutationRunAllRegressionTestsArgs, 'groupName' | 'hostName'>>;
runSmoketest?: Resolver<ResolversTypes['SmoketestResponse'], ParentType, ContextType, RequireFields<MutationRunSmoketestArgs, 'groupName' | 'hostName' | 'templateName'>>;
};
export type OperationalDeviceDataResolvers<ContextType = any, ParentType extends ResolversParentTypes['OperationalDeviceData'] = ResolversParentTypes['OperationalDeviceData']> = {
@ -1486,9 +1624,25 @@ export type QueryResolvers<ContextType = any, ParentType extends ResolversParent
zabbixVersion?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
};
export type SmoketestResponseResolvers<ContextType = any, ParentType extends ResolversParentTypes['SmoketestResponse'] = ResolversParentTypes['SmoketestResponse']> = {
message?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
steps?: Resolver<Array<ResolversTypes['SmoketestStep']>, ParentType, ContextType>;
success?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type SmoketestStepResolvers<ContextType = any, ParentType extends ResolversParentTypes['SmoketestStep'] = ResolversParentTypes['SmoketestStep']> = {
message?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
success?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type StorageItemTypeResolvers = EnumResolverSignature<{ FLOAT?: any, INT?: any, TEXT?: any }, ResolversTypes['StorageItemType']>;
export type TemplateResolvers<ContextType = any, ParentType extends ResolversParentTypes['Template'] = ResolversParentTypes['Template']> = {
host?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
items?: Resolver<Maybe<Array<ResolversTypes['ZabbixItem']>>, ParentType, ContextType>;
name?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
templateid?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
@ -1587,15 +1741,25 @@ export type ZabbixHostResolvers<ContextType = any, ParentType extends ResolversP
export type ZabbixItemResolvers<ContextType = any, ParentType extends ResolversParentTypes['ZabbixItem'] = ResolversParentTypes['ZabbixItem']> = {
attributeName?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
delay?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
description?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
history?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
hostid?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
hosts?: Resolver<Maybe<Array<ResolversTypes['Host']>>, ParentType, ContextType>;
itemid?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
key_?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
lastclock?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
lastvalue?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
master_item?: Resolver<Maybe<ResolversTypes['ZabbixItem']>, ParentType, ContextType>;
master_itemid?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
preprocessing?: Resolver<Maybe<Array<ResolversTypes['JSONObject']>>, ParentType, ContextType>;
status?: Resolver<Maybe<ResolversTypes['DeviceStatus']>, ParentType, ContextType>;
status_int?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
tags?: Resolver<Maybe<Array<ResolversTypes['JSONObject']>>, ParentType, ContextType>;
type?: Resolver<Maybe<ResolversTypes['DeviceCommunicationType']>, ParentType, ContextType>;
type_int?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
units?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
value_type?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
@ -1634,6 +1798,8 @@ export type Resolvers<ContextType = any> = {
OperationalDeviceData?: OperationalDeviceDataResolvers<ContextType>;
Permission?: PermissionResolvers;
Query?: QueryResolvers<ContextType>;
SmoketestResponse?: SmoketestResponseResolvers<ContextType>;
SmoketestStep?: SmoketestStepResolvers<ContextType>;
StorageItemType?: StorageItemTypeResolvers;
Template?: TemplateResolvers<ContextType>;
Time?: GraphQLScalarType;

View file

@ -71,12 +71,46 @@ describe("HostImporter", () => {
// Mocking template lookup for deviceType
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce([{ templateid: "301" }]);
// Mocking host.create via requestByPath
(zabbixAPI.requestByPath as jest.Mock).mockResolvedValueOnce({ hostids: ["401"] });
// Mocking host.create via post (called by ZabbixCreateHostRequest)
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ hostids: ["401"] });
const result = await HostImporter.importHosts(hosts, "token");
expect(result).toHaveLength(1);
expect(result![0].hostid).toBe("401");
});
test("importHosts - with macros", async () => {
const hosts = [{
deviceKey: "DeviceMacro",
deviceType: "Type1",
groupNames: ["Group1"],
macros: [
{ macro: "{$LAT}", value: "52.52" },
{ macro: "{$LON}", value: "13.41" }
]
}];
// Mocking group lookup
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce([{ groupid: "201", name: ZABBIX_EDGE_DEVICE_BASE_GROUP }]);
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce([{ groupid: "202", name: ZABBIX_EDGE_DEVICE_BASE_GROUP + "/Group1" }]);
// Mocking template lookup
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce([{ templateid: "301" }]);
// Mocking host.create
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ hostids: ["402"] });
const result = await HostImporter.importHosts(hosts, "token");
expect(result).toHaveLength(1);
expect(result![0].hostid).toBe("402");
// Verify that host.create was called with macros
const hostCreateCall = (zabbixAPI.post as jest.Mock).mock.calls.find(call => call[1].body.method === "host.create");
expect(hostCreateCall[1].body.params.macros).toEqual([
{ macro: "{$LAT}", value: "52.52" },
{ macro: "{$LON}", value: "13.41" }
]);
});
});

View file

@ -27,7 +27,7 @@ describe("Host Integration Tests", () => {
});
test("Query allHosts using sample", async () => {
const sampleFile = readFileSync(join(process.cwd(), 'docs', 'sample_all_hosts_query.graphql'), 'utf-8').replace(/\r\n/g, '\n');
const sampleFile = readFileSync(join(process.cwd(), 'docs', 'queries', 'sample_all_hosts_query.graphql'), 'utf-8').replace(/\r\n/g, '\n');
const mutationMatch = sampleFile.match(/```graphql\n([\s\S]*?)\n```/);
const variablesMatch = sampleFile.match(/```json\n([\s\S]*?)\n```/);
@ -51,7 +51,7 @@ describe("Host Integration Tests", () => {
});
test("Import hosts using sample", async () => {
const sampleFile = readFileSync(join(process.cwd(), 'docs', 'sample_import_hosts_mutation.graphql'), 'utf-8').replace(/\r\n/g, '\n');
const sampleFile = readFileSync(join(process.cwd(), 'docs', 'queries', 'sample_import_hosts_mutation.graphql'), 'utf-8').replace(/\r\n/g, '\n');
const mutationMatch = sampleFile.match(/```graphql\n([\s\S]*?)\n```/);
const variablesMatch = sampleFile.match(/```json\n([\s\S]*?)\n```/);
@ -62,9 +62,8 @@ describe("Host Integration Tests", () => {
(zabbixAPI.post as jest.Mock)
.mockResolvedValueOnce([{ groupid: "201", name: ZABBIX_EDGE_DEVICE_BASE_GROUP }]) // Base group
.mockResolvedValueOnce([{ groupid: "202", name: ZABBIX_EDGE_DEVICE_BASE_GROUP + "/ConstructionSite/Test" }]) // Specific group
.mockResolvedValueOnce([{ templateid: "301" }]); // Template lookup
(zabbixAPI.requestByPath as jest.Mock).mockResolvedValueOnce({ hostids: ["401"] });
.mockResolvedValueOnce([{ templateid: "301" }]) // Template lookup
.mockResolvedValueOnce({ hostids: ["401"] }); // Host creation
const response = await server.executeOperation({
query: mutation,

View file

@ -46,7 +46,7 @@ describe("Host and HostGroup Resolvers", () => {
body: expect.objectContaining({
method: "host.get",
params: expect.objectContaining({
search: { name: "Test" },
search: expect.objectContaining({ name: "Test" }),
tags: expect.arrayContaining([{
tag: "hostType",
operator: 1,

View file

@ -0,0 +1,28 @@
import {schema_loader} from "../api/schema.js";
import {readdirSync, readFileSync} from "node:fs";
import {parse, validate} from "graphql";
import path from "node:path";
describe("MCP Operations Validation", () => {
let schema: any;
beforeAll(async () => {
schema = await schema_loader();
});
const operationsDir = "./mcp/operations";
const files = readdirSync(operationsDir).filter(f => f.endsWith(".graphql"));
test.each(files)("Operation file %s should be valid against schema", (file) => {
const filePath = path.join(operationsDir, file);
const content = readFileSync(filePath, "utf-8");
const document = parse(content);
const errors = validate(schema, document);
if (errors.length > 0) {
console.error(`Validation errors in ${file}:`, errors.map(e => e.message));
}
expect(errors).toHaveLength(0);
});
});

Some files were not shown because too many files have changed in this diff Show more