diff --git a/.gitignore b/.gitignore
index a612f5d..78ded7b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -74,7 +74,10 @@ web_modules/
# dotenv environment variable files
.env
-.env.*
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index efd7416..d1b9822 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -5,21 +5,30 @@
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
@@ -30,7 +39,7 @@
-
+
@@ -78,50 +87,45 @@
- {
- "keyToString": {
- "NIXITCH_NIXPKGS_CONFIG": "",
- "NIXITCH_NIX_CONF_DIR": "",
- "NIXITCH_NIX_OTHER_STORES": "",
- "NIXITCH_NIX_PATH": "",
- "NIXITCH_NIX_PROFILES": "",
- "NIXITCH_NIX_REMOTE": "",
- "NIXITCH_NIX_USER_PROFILE_DIR": "",
- "Node.js.index.ts.executor": "Run",
- "RunOnceActivity.MCP Project settings loaded": "true",
- "RunOnceActivity.ShowReadmeOnStart": "true",
- "RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
- "RunOnceActivity.git.unshallow": "true",
- "RunOnceActivity.typescript.service.memoryLimit.init": "true",
- "com.intellij.ml.llm.matterhorn.ej.ui.settings.DefaultModelSelectionForGA.v1": "true",
- "git-widget-placeholder": "main",
- "go.import.settings.migrated": "true",
- "javascript.preferred.runtime.type.id": "node",
- "junie.onboarding.icon.badge.shown": "true",
- "last_opened_file_path": "//wsl.localhost/Ubuntu/home/ahilbig/git/vcr/zabbix-graphql-api/docs/use-cases",
- "node.js.detected.package.eslint": "true",
- "node.js.detected.package.tslint": "true",
- "node.js.selected.package.eslint": "(autodetect)",
- "node.js.selected.package.tslint": "(autodetect)",
- "nodejs_interpreter_path": "wsl://Ubuntu@/home/ahilbig/.nvm/versions/node/v24.12.0/bin/node",
- "nodejs_package_manager_path": "npm",
- "npm.codegen.executor": "Run",
- "npm.compile.executor": "Run",
- "npm.copy-schema.executor": "Run",
- "npm.prod.executor": "Run",
- "npm.test.executor": "Run",
- "settings.editor.selected.configurable": "junie.mcp",
- "settings.editor.splitter.proportion": "0.23751687",
- "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"
- },
- "keyToStringList": {
- "com.intellij.ide.scratch.ScratchImplUtil$2/New Scratch File": [
- "TEXT"
- ]
+
+}]]>
@@ -227,10 +231,7 @@
-
-
-
-
+
diff --git a/README.md b/README.md
index 023f0e2..9fd837f 100644
--- a/README.md
+++ b/README.md
@@ -52,7 +52,7 @@ Before you begin, ensure you have met the following requirements:
- **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. See [Zabbix Version Compatibility](#-zabbix-version-compatibility) for details.
+- **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).
@@ -81,20 +81,6 @@ Builds the project and runs the compiled code:
npm run prod
```
-#### ๐ณ Local Zabbix Environment
-For development and testing, you can start a complete environment including Zabbix from scratch:
-
-```bash
-# Start with local Zabbix (latest 7.0)
-docker compose --profile zabbix-local up -d
-
-# Start with a specific Zabbix version (e.g. 6.0)
-ZABBIX_VERSION=alpine-6.0-latest docker compose --profile zabbix-local up -d
-```
-
-> **Guide**: For detailed setup and configuration of the local environment, see [Local Development Environment](./docs/howtos/local_development.md).
-> **Important**: On fresh Zabbix installations, you must manually create the base host group (e.g., `Roadwork`) before the API can import devices.
-
The API will be available at `http://localhost:4000/`.
## โ๏ธ Configuration
@@ -250,20 +236,7 @@ The API version is automatically set during the Docker build process based on th
### ๐ง Zabbix Version Compatibility
-This API is officially supported and productively used with **Zabbix 7.0 (LTS)** and newer. It also maintains compatibility with **Zabbix 6.0 (LTS)** and **6.4**, with the following version-specific behaviors:
-
-- **Zabbix 7.0 (LTS)**:
- - Full feature support.
- - **History Push**: Uses the native `history.push` API for efficient data ingestion.
-- **Zabbix 6.4**:
- - **History Push**: Not supported (requires Zabbix 7.0+). The `pushHistory` mutation returns a clear error.
- - **Group Propagation**: Fully supported via the `hostgroup.propagate` API (introduced in 6.2).
- - **UUIDs**: Fully supported for both Host Groups and Template Groups.
-- **Zabbix 6.0 (LTS)**:
- - **History Push**: Not supported.
- - **Group Propagation**: `hostgroup.propagate` is unavailable. The API automatically performs **manual host group expansion** during import to ensure consistent behavior.
- - **UUIDs**: Host Groups in 6.0 lack UUIDs. The API uses a **name-based fallback** for matching host group permissions during import.
-- **Template Groups**: The API correctly handles the separation of Host Groups and Template Groups introduced in Zabbix 6.0 across all versions.
+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
diff --git a/docker-compose.yml b/docker-compose.yml
index c938d1f..4a56b5c 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -43,50 +43,5 @@ services:
- mcp-shared:/mcp-data
command: sh -c "cat /schema/*.graphql > /mcp-data/schema.graphql"
- postgres-server:
- image: postgres:16-alpine
- profiles:
- - zabbix-local
- environment:
- - POSTGRES_USER=zabbix
- - POSTGRES_PASSWORD=zabbix
- - POSTGRES_DB=zabbix
- volumes:
- - zbx_db_data:/var/lib/postgresql/data
-
- zabbix-server:
- image: zabbix/zabbix-server-pgsql:${ZABBIX_VERSION:-alpine-7.0-latest}
- profiles:
- - zabbix-local
- ports:
- - "10051:10051"
- environment:
- - DB_SERVER_HOST=postgres-server
- - POSTGRES_USER=zabbix
- - POSTGRES_PASSWORD=zabbix
- - POSTGRES_DB=zabbix
- - ZBX_ALLOWUNSUPPORTEDDBVERSIONS=1
- depends_on:
- - postgres-server
-
- zabbix-web:
- image: zabbix/zabbix-web-nginx-pgsql:${ZABBIX_VERSION:-alpine-7.0-latest}
- profiles:
- - zabbix-local
- ports:
- - "8080:8080"
- environment:
- - ZBX_SERVER_HOST=zabbix-server
- - DB_SERVER_HOST=postgres-server
- - POSTGRES_USER=zabbix
- - POSTGRES_PASSWORD=zabbix
- - POSTGRES_DB=zabbix
- - PHP_TZ=UTC
- - ZBX_ALLOWUNSUPPORTEDDBVERSIONS=1
- depends_on:
- - postgres-server
- - zabbix-server
-
volumes:
mcp-shared:
- zbx_db_data:
diff --git a/docs/howtos/README.md b/docs/howtos/README.md
index d619569..43ed518 100644
--- a/docs/howtos/README.md
+++ b/docs/howtos/README.md
@@ -22,9 +22,6 @@ Discover how the permission system works, how to define permission levels using
### ๐ ๏ธ [Technical Maintenance](./maintenance.md)
Guide on code generation (GraphQL Codegen), running Jest tests, and local Docker builds.
-### ๐ป [Local Development Environment](./local_development.md)
-Detailed instructions for setting up a fully isolated local development environment with Zabbix using Docker Compose.
-
### ๐งช [Test Specification](../tests.md)
Detailed list of test cases, categories (Unit, Integration, E2E), and coverage checklist.
diff --git a/docs/howtos/local_development.md b/docs/howtos/local_development.md
deleted file mode 100644
index e8c47d4..0000000
--- a/docs/howtos/local_development.md
+++ /dev/null
@@ -1,88 +0,0 @@
-# ๐ป Local Development Environment
-
-This guide provides detailed instructions on how to set up a fully isolated local development environment for the Zabbix GraphQL API using Docker Compose.
-
-## ๐ Overview
-The project includes a Docker Compose profile that launches a complete Zabbix stack alongside the GraphQL API and MCP server. This allows you to develop and test features against different Zabbix versions without needing an external Zabbix installation.
-
-### Included Services
-- **`zabbix-graphql-api`**: The main GraphQL server.
-- **`apollo-mcp-server`**: The Model Context Protocol server for AI integration.
-- **`postgres-server`**: PostgreSQL 16 database for Zabbix.
-- **`zabbix-server`**: Zabbix Server (PostgreSQL version).
-- **`zabbix-web`**: Zabbix Web interface (Nginx/PostgreSQL).
-
-## ๐ ๏ธ Step-by-Step Setup
-
-### 1. Start the Environment
-Use the `zabbix-local` profile to start all services. You can optionally specify the Zabbix version using the `ZABBIX_VERSION` environment variable.
-
-```bash
-# Start the latest 7.0 (default)
-docker compose --profile zabbix-local up -d
-
-# Start Zabbix 6.0 (LTS)
-ZABBIX_VERSION=alpine-6.0-latest docker compose --profile zabbix-local up -d
-```
-
-### 2. Configure Zabbix
-Once the containers are running, you need to perform a minimal setup in the Zabbix UI:
-
-1. Open `http://localhost:8080` in your browser.
-2. Log in with the default credentials:
- - **User**: `Admin`
- - **Password**: `zabbix`
-3. Navigate to **Administration** > **Users** > **API tokens**.
-4. Create a new token for the `Admin` user (or a dedicated service user) and copy it.
-5. **Create Base Host Group**: The API requires a base host group to exist for importing devices.
- - Navigate to **Configuration** > **Host groups** (or **Data collection** > **Host groups** in Zabbix 7.0).
- - Create a host group named `Roadwork` (or the value of `ZABBIX_EDGE_DEVICE_BASE_GROUP` in your `.env`).
- - **Note**: If your base group is nested (e.g., `Roadwork/Devices`), ensure the full path exists. Zabbix does not automatically create parent groups when creating child groups via the API.
-
-### 3. Connect the API
-To connect the GraphQL API to your local Zabbix instance, update your `.env` file with the following values (referenced in `samples/zabbix-local.env`):
-
-```env
-# Internal Docker network URL
-ZABBIX_BASE_URL=http://zabbix-web:8080/
-
-# The token you created in Step 2
-ZABBIX_PRIVILEGE_ESCALATION_TOKEN=your-newly-created-token
-```
-
-### 4. Restart the API
-Apply the configuration by restarting the API container:
-
-```bash
-docker compose restart zabbix-graphql-api
-```
-
-## โ
Verification
-You can verify the setup by running a simple query against the local API:
-
-```bash
-curl -X POST -H "Content-Type: application/json" \
- -d '{"query": "{ apiVersion zabbixVersion }"}' \
- http://localhost:4001/
-```
-
-The `zabbixVersion` should match the version you specified in `ZABBIX_VERSION`.
-
-## ๐ก Advanced Usage
-
-### Data Persistence
-Zabbix data is stored in a named Docker volume called `zbx_db_data`. This ensures that your configuration, templates, and hosts are preserved even if you stop or remove the containers.
-
-To perform a clean reset of the environment:
-```bash
-docker compose --profile zabbix-local down -v
-```
-
-### Testing Multiple Versions
-The local environment is perfect for testing the API's version-specific logic (e.g. `history.push` vs `zabbix_sender`). Simply change the `ZABBIX_VERSION` variable and restart the stack.
-
-Supported tags can be found on the [Zabbix Docker Hub](https://hub.docker.com/u/zabbix).
-
----
-**Related Guide**: [Technical Maintenance](./maintenance.md)
-**Related Reference**: [Zabbix Version Compatibility](../../README.md#-zabbix-version-compatibility)
diff --git a/docs/howtos/maintenance.md b/docs/howtos/maintenance.md
index 617d76f..2d9b7cd 100644
--- a/docs/howtos/maintenance.md
+++ b/docs/howtos/maintenance.md
@@ -34,9 +34,6 @@ We use [Jest](https://jestjs.io/) for unit and integration testing.
npm run test
```
-#### Local Development Setup
-For running integration tests against a real Zabbix instance, it is recommended to use the [Local Development Environment](./local_development.md). This setup allows you to test the API against specific Zabbix versions (e.g. 6.0, 7.0) in an isolated way.
-
#### 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.
diff --git a/roadmap.md b/roadmap.md
index 88f6341..4abf77e 100644
--- a/roadmap.md
+++ b/roadmap.md
@@ -6,21 +6,12 @@ This document outlines the achieved milestones and planned enhancements for the
- **๐ฏ VCR Product Integration**: Developed a specialized **GraphQL API** as part of the VCR Product to enable 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**.
-- **โก Query Optimization**: Optimized GraphQL API queries to reduce the amount of data fetched from Zabbix depending on the fields really requested.
- - *Implementation*: Added dynamic output selection and field pruning in `ZabbixRequest`.
-
- **๐ Open Source Extraction & AI Integration**: Extracted the core functionality of the API to publish it as an **Open Source** project.
- *AI Integration*: Enhanced with **Model Context Protocol (MCP)** and **AI agent** integration to enable workflow and agent-supported use cases.
-- **๐ณ Local Development Environment**: Integrated a complete Zabbix stack into the Docker Compose configuration using profiles.
- - *Feature*: Support for multiple Zabbix versions (6.0, 6.4, 7.0+) for development and testing.
- - *Implementation*: Added `zabbix-local` profile and `ZABBIX_VERSION` dynamic image tagging.
-
-- **๐ง Multi-Version Compatibility**: Verified and enhanced support for Zabbix 6.0 LTS, 6.2, 6.4, and 7.0 LTS.
- - *Feature*: Automatic fallback logic for older Zabbix versions (auth, name-based matching, manual expansion).
- - *Verification*: Full regression and smoketest suites passed across all mentioned versions.
-
## ๐
Planned Enhancements
+- **โก Query Optimization**: Optimize GraphQL API queries to reduce the amount of data fetched from Zabbix depending on the fields really requested and improve performance.
+
- **๐๏ธ Trade Fair Logistics Use Case**: Extend the API to support trade fair logistics use cases by analyzing requirements from business stakeholders.
- *Analysis*: Analysis of "Trade Fair Logistics" and derived [requirements document](docs/use-cases/trade-fair-logistics-requirements.md).
- *Simulation*:
diff --git a/samples/zabbix-local.env b/samples/zabbix-local.env
deleted file mode 100644
index b304fc5..0000000
--- a/samples/zabbix-local.env
+++ /dev/null
@@ -1,10 +0,0 @@
-# Zabbix Connection for local Docker Compose profile (zabbix-local)
-# Use the contents of this file in your .env or pass them to docker compose
-# when running with 'docker compose --profile zabbix-local up'
-
-# Internal URL for the API to connect to the local Zabbix container
-ZABBIX_BASE_URL=http://zabbix-web:8080/
-
-# Note: After Zabbix starts, you must log in to http://localhost:8080 (Admin/zabbix)
-# and create an API token to use as ZABBIX_PRIVILEGE_ESCALATION_TOKEN.
-ZABBIX_PRIVILEGE_ESCALATION_TOKEN=your-newly-created-zabbix-token
diff --git a/src/api/resolvers.ts b/src/api/resolvers.ts
index d0d3bc4..44e38d0 100644
--- a/src/api/resolvers.ts
+++ b/src/api/resolvers.ts
@@ -166,22 +166,32 @@ export function createResolvers(): Resolvers {
zabbixAuthToken,
cookie, dataSources
}: any, info: any) => {
+ let params: any = {}
if (args.hostids) {
- // @ts-ignore
- args.templateids = args.hostids
- delete args.hostids
+ params.templateids = args.hostids
+ }
+ if (args.name_pattern) {
+ params.search = {
+ name: args.name_pattern
+ }
}
const output = GraphqlParamsToNeededZabbixOutput.mapTemplates(info);
return await new ZabbixQueryTemplatesRequest(zabbixAuthToken, cookie)
- .executeRequestThrowError(dataSources?.zabbixAPI || zabbixAPI, new ParsedArgs(args), output);
+ .executeRequestThrowError(dataSources?.zabbixAPI || zabbixAPI, new ParsedArgs(params), output);
},
allTemplateGroups: async (_parent: any, args: any, {
zabbixAuthToken,
cookie
}: any) => {
+ let params: any = {}
+ if (args.name_pattern) {
+ params.search = {
+ name: args.name_pattern
+ }
+ }
return await new ZabbixQueryTemplateGroupRequest(zabbixAuthToken, cookie)
- .executeRequestThrowError(zabbixAPI, new ParsedArgs(args));
+ .executeRequestThrowError(zabbixAPI, new ParsedArgs(params));
}
},
Mutation: {
diff --git a/src/datasources/zabbix-api.ts b/src/datasources/zabbix-api.ts
index 1ecaa73..17ed27c 100644
--- a/src/datasources/zabbix-api.ts
+++ b/src/datasources/zabbix-api.ts
@@ -80,20 +80,6 @@ export class ZabbixAPI
return super.post(path, request);
}
- private static version: string | undefined
-
- async getVersion(): Promise {
- if (!ZabbixAPI.version) {
- const response = await this.requestByPath("apiinfo.version")
- if (typeof response === "string") {
- ZabbixAPI.version = response
- } else {
- return "0.0.0"
- }
- }
- return ZabbixAPI.version
- }
-
async executeRequest(zabbixRequest: ZabbixRequest, args?: A, throwApiError: boolean = true, output?: string[]): Promise {
return throwApiError ? zabbixRequest.executeRequestThrowError(this, args, output) : zabbixRequest.executeRequestReturnError(this, args, output);
}
diff --git a/src/datasources/zabbix-history.ts b/src/datasources/zabbix-history.ts
index db57459..c3d35b7 100644
--- a/src/datasources/zabbix-history.ts
+++ b/src/datasources/zabbix-history.ts
@@ -81,10 +81,6 @@ export class ZabbixHistoryPushRequest extends ZabbixRequest {
if (!args) return undefined;
- const version = await zabbixAPI.getVersion();
- if (version < "7.0.0") {
- throw new GraphQLError(`history.push is only supported in Zabbix 7.0.0 and newer. Current version is ${version}. For older versions, please use Zabbix trapper items and zabbix_sender protocol.`);
- }
if (!args.itemid && (!args.key || !args.host)) {
throw new GraphQLError("if itemid is empty both key and host must be filled");
diff --git a/src/datasources/zabbix-hostgroups.ts b/src/datasources/zabbix-hostgroups.ts
index a517f7b..67a3d22 100644
--- a/src/datasources/zabbix-hostgroups.ts
+++ b/src/datasources/zabbix-hostgroups.ts
@@ -100,33 +100,4 @@ export class GroupHelper {
}
return result
}
-
- public static async findSubgroupIds(groupids: number[], zabbixApi: ZabbixAPI, zabbixAuthToken?: string, cookie?: string): Promise {
- if (groupids.length === 0) return [];
-
- // Get the names of the parent groups
- const parentGroups = await new ZabbixQueryHostgroupsRequest(zabbixAuthToken, cookie).executeRequestReturnError(zabbixApi, new ZabbixQueryHostgroupsParams({
- groupids: groupids
- }));
-
- if (isZabbixErrorResult(parentGroups) || !parentGroups.length) {
- return [];
- }
-
- const subgroupids: number[] = [];
- for (const parent of parentGroups) {
- const subgroups = await new ZabbixQueryHostgroupsRequest(zabbixAuthToken, cookie).executeRequestReturnError(zabbixApi, new ZabbixQueryHostgroupsParams({
- search: {
- name: parent.name + "/*"
- },
- searchWildcardsEnabled: true
- }));
-
- if (!isZabbixErrorResult(subgroups)) {
- subgroups.forEach(sg => subgroupids.push(Number(sg.groupid)));
- }
- }
-
- return [...new Set(subgroupids)];
- }
}
diff --git a/src/datasources/zabbix-hosts.ts b/src/datasources/zabbix-hosts.ts
index 4232f0e..4c18c6e 100644
--- a/src/datasources/zabbix-hosts.ts
+++ b/src/datasources/zabbix-hosts.ts
@@ -25,23 +25,8 @@ export class ZabbixQueryHostsGenericRequest {
- const version = await zabbixAPI.getVersion();
- const result = await super.executeRequestReturnError(zabbixAPI, args, output);
-
- if (!isZabbixErrorResult(result) && version < "6.2.0") {
- const hosts = result as any[];
- for (const host of hosts) {
- if (host.groups) {
- host.hostgroups = host.groups;
- }
- }
- }
- return result;
- }
-
createZabbixParams(args?: A, output?: string[]): ZabbixParams {
- const params: any = {
+ return this.optimizeZabbixParams({
...super.createZabbixParams(args),
selectParentTemplates: [
"templateid",
@@ -60,15 +45,11 @@ export class ZabbixQueryHostsGenericRequest {
- const version = await zabbixAPI.getVersion();
- const result = await super.executeRequestReturnError(zabbixAPI, args, output);
-
- if (!isZabbixErrorResult(result) && version < "6.2.0") {
- // Map 6.0 'rights' to 6.2+ style 'templategroup_rights'
- const usergroups = result as ZabbixUserGroupResponse[];
- for (const usergroup of usergroups) {
- // @ts-ignore
- if (usergroup.rights) {
- // @ts-ignore
- usergroup.templategroup_rights = usergroup.rights.map((r: any) => ({
- id: r.id,
- permission: r.permission
- }));
- }
- }
- }
- return result;
- }
-
- async createZabbixParams(args?: ParsedArgs) {
- // We don't have access to ZabbixAPI here to check version easily,
- // but we can just include both selectRights and selectTemplateGroupRights.
- // Zabbix 6.2+ will ignore selectRights, Zabbix 6.0 will ignore selectTemplateGroupRights.
+ createZabbixParams(args?: ParsedArgs) {
return {
...super.createZabbixParams(args),
"output": [
@@ -115,8 +87,7 @@ class ZabbixQueryUserGroupPermissionsRequest extends ZabbixRequest (usergroup.templategroup_rights || []).map(templateGroupRight => templateGroupRight.id)));
+ const templateIdsToLoad = new Set(userGroupPermissions.flatMap(usergroup => usergroup.templategroup_rights.map(templateGroupRight => templateGroupRight.id)));
// Remove all templateIds that are already in the permissionObjectNameCache
templateIdsToLoad.forEach(id => {
@@ -174,7 +145,7 @@ export class ZabbixPermissionsHelper {
let objectNamesFilter = this.createMatcherFromWildcardArray(objectNames);
- (usergroup.templategroup_rights || []).forEach(templateGroupPermission => {
+ usergroup.templategroup_rights.forEach(templateGroupPermission => {
const objectName = this.permissionObjectNameCache.get(templateGroupPermission.id);
if (objectName && (objectNamesFilter == undefined || objectNamesFilter.test(objectName))) {
const permissionValue = Number(templateGroupPermission.permission) as PermissionNumber;
diff --git a/src/datasources/zabbix-request.ts b/src/datasources/zabbix-request.ts
index 343ef62..fb54110 100644
--- a/src/datasources/zabbix-request.ts
+++ b/src/datasources/zabbix-request.ts
@@ -9,7 +9,6 @@ class ZabbixRequestBody {
public method
public id = 1
public params?: ZabbixParams
- public auth?: string
constructor(method: string) {
this.method = method;
@@ -139,7 +138,6 @@ export class ParsedArgs {
(result).search.name = this.name_pattern;
(result).search.host = this.name_pattern;
(result).searchByAny = true;
- (result).searchWildcardsEnabled = true;
}
return result
@@ -179,12 +177,6 @@ export class ZabbixRequest topLevelOutput.includes(field));
- // Add any missing top-level fields that are needed
- topLevelOutput.forEach(top => {
- if (!params.output.includes(top)) {
- params.output.push(top);
- }
- });
} else if (params.output === "extend") {
params.output = topLevelOutput;
}
@@ -219,15 +211,10 @@ export class ZabbixRequest {
- if (this.path !== "apiinfo.version") {
- const version = await zabbixAPI.getVersion();
- if (version < "6.2.0") {
- if (this.path.startsWith("templategroup.")) {
- this.path = this.path.replace("templategroup.", "hostgroup.");
- this.method = this.path.split(".", 2).join(".");
- this.requestBodyTemplate.method = this.method;
- }
- }
- }
-
let prepareResult = await this.prepare(zabbixAPI, args);
if (prepareResult) {
return prepareResult;
diff --git a/src/datasources/zabbix-usergroups.ts b/src/datasources/zabbix-usergroups.ts
index bd576a3..5c61a52 100644
--- a/src/datasources/zabbix-usergroups.ts
+++ b/src/datasources/zabbix-usergroups.ts
@@ -17,9 +17,8 @@ import {
ZabbixGroupRightInput
} from "../schema/generated/graphql.js";
import {ZabbixAPI} from "./zabbix-api.js";
-import {logger} from "../logging/logger.js";
import {ZabbixQueryTemplateGroupRequest, ZabbixQueryTemplateGroupResponse} from "./zabbix-templates.js";
-import {GroupHelper, ZabbixQueryHostgroupsRequest, ZabbixQueryHostgroupsResult} from "./zabbix-hostgroups.js";
+import {ZabbixQueryHostgroupsRequest, ZabbixQueryHostgroupsResult} from "./zabbix-hostgroups.js";
import {ApiErrorCode} from "../model/model_enum_values.js";
@@ -78,30 +77,13 @@ export class ZabbixExportUserGroupsRequest extends ZabbixPrepareGetTemplatesAndH
...super.createZabbixParams(args),
output: "extend",
selectTemplateGroupRights: "extend",
- selectHostGroupRights: "extend",
- selectRights: "extend"
+ selectHostGroupRights: "extend"
};
}
async executeRequestReturnError(zabbixAPI: ZabbixAPI, args?: ZabbixExportUserGroupArgs): Promise {
- const version = await zabbixAPI.getVersion();
let result = await super.executeRequestReturnError(zabbixAPI, args);
if (!isZabbixErrorResult(result)) {
- if (version < "6.2.0") {
- for (let userGroup of result) {
- // @ts-ignore
- if (userGroup.rights) {
- // In 6.0, 'rights' contains both host and template group permissions
- // @ts-ignore
- userGroup.hostgroup_rights = userGroup.rights.map((r: any) => ({
- id: r.id,
- permission: r.permission
- }));
- userGroup.templategroup_rights = []; // Or duplicates?
- }
- }
- }
-
for (let userGroup of result) {
for (let template_permission of userGroup.templategroup_rights || []) {
for (let templategroup of this.templategroups) {
@@ -171,55 +153,19 @@ export class ZabbixImportUserGroupsRequest
let createGroupRequest = new ZabbixCreateOrUpdateRequest<
ZabbixCreateUserGroupResponse, ZabbixQueryUserGroupsRequest, ZabbixCreateOrUpdateParams>(
"usergroup", "usrgrpid", ZabbixQueryUserGroupsRequest, this.authToken, this.cookie);
-
- const version = await zabbixAPI.getVersion();
- const needsManualPropagation = version < "6.2.0";
-
for (let userGroup of args?.usergroups || []) {
let templategroup_rights = this.calc_templategroup_rights(userGroup);
let hostgroup_rights = this.calc_hostgroup_rights(userGroup);
- if (needsManualPropagation && hostgroup_rights.hostgroup_rights.length > 0) {
- const parentGroupids = hostgroup_rights.hostgroup_rights.map(r => r.id);
- const subgroupids = await GroupHelper.findSubgroupIds(parentGroupids, zabbixAPI, this.authToken ?? undefined, this.cookie ?? undefined);
-
- for (const sgid of subgroupids) {
- if (!hostgroup_rights.hostgroup_rights.find(r => r.id === sgid)) {
- // Find the permission of the parent group to apply it to the subgroup
- // This is a simplification: if multiple parents match, we take one.
- // Actually, we should find which parent this subgroup belongs to.
- // But for simplicity, we can just look at the parent permissions.
- // In most cases, it's just one parent being propagated.
- const parent = hostgroup_rights.hostgroup_rights.find(r => parentGroupids.includes(r.id)); // just take first
- if (parent) {
- hostgroup_rights.hostgroup_rights.push({
- id: sgid,
- permission: parent.permission
- });
- }
- }
- }
- }
-
let errors: ApiError[] = [];
- let paramsObj: any = {
+ let params = new ZabbixCreateOrUpdateParams({
name: userGroup.name,
gui_access: userGroup.gui_access,
users_status: userGroup.users_status,
- };
-
- if (version < "6.2.0") {
- paramsObj.rights = [
- ...hostgroup_rights.hostgroup_rights,
- ...templategroup_rights.templategroup_rights
- ];
- } else {
- paramsObj.hostgroup_rights = hostgroup_rights.hostgroup_rights;
- paramsObj.templategroup_rights = templategroup_rights.templategroup_rights;
- }
-
- let params = new ZabbixCreateOrUpdateParams(paramsObj, args?.dryRun)
+ hostgroup_rights: hostgroup_rights.hostgroup_rights,
+ templategroup_rights: templategroup_rights.templategroup_rights,
+ }, args?.dryRun)
let result = await createGroupRequest.executeRequestReturnError(zabbixAPI, params);
if (isZabbixErrorResult(result)) {
@@ -268,52 +214,34 @@ export class ZabbixImportUserGroupsRequest
for (let hostgroup_right of usergroup.hostgroup_rights || []) {
let success = false;
let matchedName = "";
- let matchedId: number | undefined = undefined;
-
- // Try matching by UUID first
for (let hostgroup of this.hostgroups) {
- if (hostgroup.uuid && hostgroup_right.uuid && hostgroup.uuid === hostgroup_right.uuid) {
- matchedId = Number(hostgroup.groupid);
- matchedName = hostgroup.name;
- success = true;
- break;
- }
- }
-
- // Fallback to matching by name (important for Zabbix 6.0 which lacks Host Group UUIDs)
- if (!success && hostgroup_right.name) {
- for (let hostgroup of this.hostgroups) {
- if (hostgroup.name === hostgroup_right.name) {
- matchedId = Number(hostgroup.groupid);
- matchedName = hostgroup.name;
- success = true;
- break;
- }
- }
- }
-
- if (success) {
- result.push(
- {
- id: matchedId!,
- permission: hostgroup_right.permission,
- }
- )
-
- if (hostgroup_right.name && hostgroup_right.name != matchedName) {
- errors.push(
+ if (hostgroup.uuid == hostgroup_right.uuid) {
+ result.push(
{
- code: ApiErrorCode.OK,
- message: `WARNING: Hostgroup found and permissions set, but target name=${matchedName} does not match provided name=${hostgroup_right.name}`,
- data: hostgroup_right,
+ id: Number(hostgroup.groupid),
+ permission: hostgroup_right.permission,
}
)
+ success = true;
+ matchedName = hostgroup.name;
+ break;
}
- } else {
+
+ }
+ if (success && hostgroup_right.name && hostgroup_right.name != matchedName) {
+ errors.push(
+ {
+ code: ApiErrorCode.OK,
+ message: `WARNING: Hostgroup found and permissions set, but target name=${matchedName} does not match`,
+ data: hostgroup_right,
+ }
+ )
+ }
+ if (!success) {
errors.push(
{
code: ApiErrorCode.ZABBIX_HOSTGROUP_NOT_FOUND,
- message: `Hostgroup with UUID ${hostgroup_right.uuid} ${hostgroup_right.name ? "or name " + hostgroup_right.name : ""} not found`,
+ message: `Hostgroup with UUID ${hostgroup_right.uuid} not found`,
data: hostgroup_right,
}
)
@@ -334,52 +262,33 @@ export class ZabbixImportUserGroupsRequest
for (let templategroup_right of usergroup.templategroup_rights || []) {
let success = false;
let matchedName = "";
- let matchedId: number | undefined = undefined;
-
- // Try matching by UUID first
for (let templategroup of this.templategroups) {
- if (templategroup.uuid && templategroup_right.uuid && templategroup.uuid === templategroup_right.uuid) {
- matchedId = Number(templategroup.groupid);
- matchedName = templategroup.name;
+ if (templategroup.uuid == templategroup_right.uuid) {
+ result.push(
+ {
+ id: Number(templategroup.groupid),
+ permission: templategroup_right.permission,
+ }
+ )
success = true;
+ matchedName = templategroup.name
break;
}
}
-
- // Fallback to matching by name
- if (!success && templategroup_right.name) {
- for (let templategroup of this.templategroups) {
- if (templategroup.name === templategroup_right.name) {
- matchedId = Number(templategroup.groupid);
- matchedName = templategroup.name;
- success = true;
- break;
- }
- }
- }
-
- if (success) {
- result.push(
+ if (success && templategroup_right.name && templategroup_right.name != matchedName) {
+ errors.push(
{
- id: matchedId!,
- permission: templategroup_right.permission,
+ code: ApiErrorCode.OK,
+ message: `WARNING: Templategroup found and permissions set, but target name=${matchedName} does not match`,
+ data: templategroup_right,
}
)
-
- if (templategroup_right.name && templategroup_right.name != matchedName) {
- errors.push(
- {
- code: ApiErrorCode.OK,
- message: `WARNING: Templategroup found and permissions set, but target name=${matchedName} does not match provided name=${templategroup_right.name}`,
- data: templategroup_right,
- }
- )
- }
- } else {
+ }
+ if (!success) {
errors.push(
{
code: ApiErrorCode.ZABBIX_TEMPLATEGROUP_NOT_FOUND,
- message: `Templategroup with UUID ${templategroup_right.uuid} ${templategroup_right.name ? "or name " + templategroup_right.name : ""} not found`,
+ message: `Templategroup with UUID ${templategroup_right.uuid} not found`,
data: templategroup_right,
}
)
@@ -408,17 +317,6 @@ export class ZabbixPropagateHostGroupsRequest extends ZabbixRequest {
- const version = await zabbixAPI.getVersion();
- if (version < "6.2.0") {
- logger.warn(`hostgroup.propagate is only supported in Zabbix 6.2.0 and newer. Current version is ${version}. Skipping propagation.`);
- return {
- usrgrpids: []
- } as ZabbixCreateUserGroupResponse;
- }
- return super.prepare(zabbixAPI, args);
- }
-
createZabbixParams(args?: ZabbixPropagateHostGroupsParams): ZabbixParams {
return {
groups: [...new Set(args?.groups || [])].map(value => {
diff --git a/src/execution/regression_test_executor.ts b/src/execution/regression_test_executor.ts
index d3c49db..da89787 100644
--- a/src/execution/regression_test_executor.ts
+++ b/src/execution/regression_test_executor.ts
@@ -23,19 +23,6 @@ export class RegressionTestExecutor {
const hostName = "REG_HOST_" + Math.random().toString(36).substring(7);
const groupName = "REG_GROUP_" + Math.random().toString(36).substring(7);
- const regTemplateName = "REG_TEMP_" + Math.random().toString(36).substring(7);
- const httpTempName = "REG_HTTP_" + Math.random().toString(36).substring(7);
- const macroTemplateName = "REG_MACRO_TEMP_" + Math.random().toString(36).substring(7);
- const macroHostName = "REG_MACRO_HOST_" + Math.random().toString(36).substring(7);
- const metaTempName = "REG_META_TEMP_" + Math.random().toString(36).substring(7);
- const metaHostName = "REG_META_HOST_" + Math.random().toString(36).substring(7);
- const depTempName = "REG_DEP_TEMP_" + Math.random().toString(36).substring(7);
- const stateTempName = "REG_STATE_TEMP_" + Math.random().toString(36).substring(7);
- const stateHostName = "REG_STATE_HOST_" + Math.random().toString(36).substring(7);
- const devHostNameWithTag = "REG_DEV_WITH_TAG_" + Math.random().toString(36).substring(7);
- const devHostNameWithoutTag = "REG_DEV_WITHOUT_TAG_" + Math.random().toString(36).substring(7);
- const pushHostName = "REG_PUSH_HOST_" + Math.random().toString(36).substring(7);
-
try {
// Regression 1: Locations query argument order
// This verifies the fix where getLocations was called with (authToken, args) instead of (args, authToken)
@@ -57,6 +44,7 @@ export class RegressionTestExecutor {
// 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";
@@ -67,7 +55,7 @@ export class RegressionTestExecutor {
const tempResult = await TemplateImporter.importTemplates([{
host: regTemplateName,
- name: "Regression Test Template " + regTemplateName,
+ name: "Regression Test Template",
groupNames: [regGroupName]
}], zabbixAuthToken, cookie);
@@ -81,9 +69,10 @@ export class RegressionTestExecutor {
// 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 " + httpTempName,
+ name: "Regression HTTP Template",
groupNames: [regGroupName],
items: [{
name: "HTTP Master",
@@ -105,9 +94,12 @@ export class RegressionTestExecutor {
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 " + macroTemplateName,
+ name: "Regression Macro Template",
groupNames: [regGroupName],
macros: [
{ macro: "{$TEMP_MACRO}", value: "temp_value" }
@@ -221,9 +213,12 @@ export class RegressionTestExecutor {
}
// Regression 6: Item Metadata (preprocessing, units, description, error)
+ const metaTempName = "REG_META_TEMP_" + Math.random().toString(36).substring(7);
+ const metaHostName = "REG_META_HOST_" + Math.random().toString(36).substring(7);
+
const metaTempResult = await TemplateImporter.importTemplates([{
host: metaTempName,
- name: "Regression Meta Template " + metaTempName,
+ name: "Regression Meta Template",
groupNames: [regGroupName],
items: [{
name: "Meta Item",
@@ -314,17 +309,19 @@ export class RegressionTestExecutor {
// 3. Test indirect dependencies: state implies items
const testParams3 = optRequest.createZabbixParams(new ParsedArgs({}), ["hostid", "state"]);
const hasSelectItems3 = "selectItems" in testParams3;
+ const hasOutput3 = Array.isArray(testParams3.output) && testParams3.output.includes("hostid") && testParams3.output.includes("items");
- optSuccess = optSuccess && hasSelectItems3;
+ optSuccess = optSuccess && hasSelectItems3 && hasOutput3;
// 4. Test indirect dependencies: deviceType implies tags
const testParams4 = optRequest.createZabbixParams(new ParsedArgs({}), ["hostid", "deviceType"]);
const hasSelectTags4 = "selectTags" in testParams4;
+ const hasOutput4 = Array.isArray(testParams4.output) && testParams4.output.includes("hostid");
- optSuccess = optSuccess && hasSelectTags4;
+ optSuccess = optSuccess && hasSelectTags4 && hasOutput4;
if (!optSuccess) {
- logger.error(`REG-OPT: Optimization verification failed. hasSelectItems1: ${hasSelectItems1}, hasOutput1: ${hasOutput1}, hasSelectItems2: ${hasSelectItems2}, hasSelectTags2: ${hasSelectTags2}, hasSelectItems3: ${hasSelectItems3}, hasSelectTags4: ${hasSelectTags4}`);
+ logger.error(`REG-OPT: Optimization verification failed. hasSelectItems1: ${hasSelectItems1}, hasOutput1: ${hasOutput1}, hasSelectItems2: ${hasSelectItems2}, hasSelectTags2: ${hasSelectTags2}, hasSelectItems3: ${hasSelectItems3}, hasOutput3: ${hasOutput3}, hasSelectTags4: ${hasSelectTags4}, hasOutput4: ${hasOutput4}`);
}
} catch (error) {
logger.error(`REG-OPT: Error during optimization test: ${error}`);
@@ -360,9 +357,10 @@ export class RegressionTestExecutor {
if (!emptySuccess) success = false;
// Regression 9: Dependent Items in Templates
+ const depTempName = "REG_DEP_TEMP_" + Math.random().toString(36).substring(7);
const depTempResult = await TemplateImporter.importTemplates([{
host: depTempName,
- name: "Regression Dependent Template " + depTempName,
+ name: "Regression Dependent Template",
groupNames: [regGroupName],
items: [
{
@@ -392,9 +390,12 @@ export class RegressionTestExecutor {
if (!depSuccess) success = false;
// Regression 10: State sub-properties retrieval (Optimization indirect dependency)
+ const stateTempName = "REG_STATE_TEMP_" + Math.random().toString(36).substring(7);
+ const stateHostName = "REG_STATE_HOST_" + Math.random().toString(36).substring(7);
+
const stateTempResult = await TemplateImporter.importTemplates([{
host: stateTempName,
- name: "Regression State Template " + stateTempName,
+ name: "Regression State Template",
groupNames: [regGroupName],
tags: [{ tag: "deviceType", value: "GenericDevice" }],
items: [{
@@ -481,6 +482,9 @@ export class RegressionTestExecutor {
// Regression 12: allDevices deviceType filter
// Verifies that allDevices only returns hosts with a deviceType tag
+ const devHostNameWithTag = "REG_DEV_WITH_TAG_" + Math.random().toString(36).substring(7);
+ const devHostNameWithoutTag = "REG_DEV_WITHOUT_TAG_" + Math.random().toString(36).substring(7);
+
// Get groupid for hostGroupName
const groupQuery: any = await new ZabbixRequest("hostgroup.get", zabbixAuthToken, cookie)
.executeRequestReturnError(zabbixAPI, new ParsedArgs({ filter_name: hostGroupName }));
@@ -528,59 +532,48 @@ export class RegressionTestExecutor {
}
// Regression 13: pushHistory mutation
- let pushSuccess = false;
- const version = await zabbixAPI.getVersion();
+ const pushHostName = "REG_PUSH_HOST_" + Math.random().toString(36).substring(7);
+ const pushItemKey = "trap.json";
- if (version < "7.0.0") {
- logger.info(`REG-PUSH: Skipping pushHistory test as it is not supported on Zabbix version ${version}`);
- pushSuccess = true; // Mark as success for old versions to allow overall test success
- } else {
- const pushItemKey = "trap.json";
+ // Create host
+ const pushHostResult = await HostImporter.importHosts([{
+ deviceKey: pushHostName,
+ deviceType: "RegressionHost",
+ groupNames: [hostGroupName],
+ templateNames: []
+ }], zabbixAuthToken, cookie);
+
+ let pushSuccess = false;
+ if (pushHostResult?.length && pushHostResult[0].hostid) {
+ const pushHostId = pushHostResult[0].hostid;
- // Create host
- const pushHostResult = await HostImporter.importHosts([{
- deviceKey: pushHostName,
- deviceType: "RegressionHost",
- groupNames: [hostGroupName],
- templateNames: []
- }], zabbixAuthToken, cookie);
+ // Add trapper item to host
+ const pushItemResult = await new ZabbixRequest("item.create", zabbixAuthToken, cookie).executeRequestReturnError(zabbixAPI, new ParsedArgs({
+ name: "Trapper JSON Item",
+ key_: pushItemKey,
+ hostid: pushHostId,
+ type: 2, // Zabbix trapper
+ value_type: 4, // Text
+ history: "1d"
+ }));
- if (pushHostResult?.length && pushHostResult[0].hostid) {
- const pushHostId = pushHostResult[0].hostid;
+ if (!isZabbixErrorResult(pushItemResult)) {
+ // Push data
+ const pushRequest = new ZabbixHistoryPushRequest(zabbixAuthToken, cookie);
+ const pushParams = new ZabbixHistoryPushParams(
+ [{ timestamp: new Date().toISOString(), value: { hello: "world" } }],
+ undefined, pushItemKey, pushHostName
+ );
- // Add trapper item to host
- const pushItemResult = await new ZabbixRequest("item.create", zabbixAuthToken, cookie).executeRequestReturnError(zabbixAPI, new ParsedArgs({
- name: "Trapper JSON Item",
- key_: pushItemKey,
- hostid: pushHostId,
- type: 2, // Zabbix trapper
- value_type: 4, // Text
- history: "1d"
- }));
-
- if (!isZabbixErrorResult(pushItemResult)) {
- // Push data
- const pushRequest = new ZabbixHistoryPushRequest(zabbixAuthToken, cookie);
- const pushParams = new ZabbixHistoryPushParams(
- [{ timestamp: new Date().toISOString(), value: { hello: "world" } }],
- undefined, pushItemKey, pushHostName
- );
-
- const pushDataResult = await pushRequest.executeRequestReturnError(zabbixAPI, pushParams);
- pushSuccess = !isZabbixErrorResult(pushDataResult) && pushDataResult.response === "success";
- }
-
- // Cleanup push host
- await HostDeleter.deleteHosts([Number(pushHostId)], null, zabbixAuthToken, cookie);
+ const pushDataResult = await pushRequest.executeRequestReturnError(zabbixAPI, pushParams);
+ pushSuccess = !isZabbixErrorResult(pushDataResult) && pushDataResult.response === "success";
}
}
steps.push({
name: "REG-PUSH: pushHistory mutation",
success: pushSuccess,
- message: version < "7.0.0"
- ? `Skipped (not supported on ${version})`
- : (pushSuccess ? "Successfully pushed history data to trapper item" : "Failed to push history data")
+ message: pushSuccess ? "Successfully pushed history data to trapper item" : "Failed to push history data"
});
if (!pushSuccess) success = false;
@@ -620,22 +613,6 @@ export class RegressionTestExecutor {
success: false,
message: error.message || String(error)
});
- } finally {
- // Cleanup
- await HostDeleter.deleteHosts(null, hostName, zabbixAuthToken, cookie);
- await HostDeleter.deleteHosts(null, macroHostName, zabbixAuthToken, cookie);
- await HostDeleter.deleteHosts(null, metaHostName, zabbixAuthToken, cookie);
- await HostDeleter.deleteHosts(null, devHostNameWithTag, zabbixAuthToken, cookie);
- await HostDeleter.deleteHosts(null, devHostNameWithoutTag, zabbixAuthToken, cookie);
- await HostDeleter.deleteHosts(null, pushHostName, zabbixAuthToken, cookie);
- await TemplateDeleter.deleteTemplates(null, regTemplateName, zabbixAuthToken, cookie);
- await TemplateDeleter.deleteTemplates(null, httpTempName, zabbixAuthToken, cookie);
- await TemplateDeleter.deleteTemplates(null, macroTemplateName, zabbixAuthToken, cookie);
- await TemplateDeleter.deleteTemplates(null, metaTempName, zabbixAuthToken, cookie);
- await TemplateDeleter.deleteTemplates(null, depTempName, zabbixAuthToken, cookie);
- await TemplateDeleter.deleteTemplates(null, stateTempName, zabbixAuthToken, cookie);
- await HostDeleter.deleteHosts(null, stateHostName, zabbixAuthToken, cookie);
- // We don't delete the group here as it might be shared or used by other tests in this run
}
return {
diff --git a/src/execution/smoketest_executor.ts b/src/execution/smoketest_executor.ts
index f2a3598..497789f 100644
--- a/src/execution/smoketest_executor.ts
+++ b/src/execution/smoketest_executor.ts
@@ -66,11 +66,11 @@ export class SmoketestExecutor {
templateNames: [templateName]
}], zabbixAuthToken, cookie);
- const hostSuccess = !!hostResult?.length && !hostResult[0].error && !!hostResult[0].hostid;
+ 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 || hostResult?.[0]?.message || "Unknown error"}`
+ message: hostSuccess ? `Host ${hostName} created and linked to ${templateName}` : `Failed: ${hostResult?.[0]?.error?.message || "Unknown error"}`
});
if (!hostSuccess) success = false;
} else {
diff --git a/src/test/history_push.test.ts b/src/test/history_push.test.ts
index 0882e04..a6fbb31 100644
--- a/src/test/history_push.test.ts
+++ b/src/test/history_push.test.ts
@@ -6,7 +6,6 @@ import {GraphQLError} from "graphql";
jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
post: jest.fn(),
- getVersion: jest.fn().mockResolvedValue("7.0.0")
}
}));
@@ -64,12 +63,4 @@ describe("ZabbixHistoryPushRequest", () => {
await expect(request.prepare(zabbixAPI, params)).rejects.toThrow("if itemid is empty both key and host must be filled");
});
-
- test("prepare - throw error if Zabbix version < 7.0.0", async () => {
- (zabbixAPI.getVersion as jest.Mock).mockResolvedValue("6.0.0");
- const values = [{ timestamp: "2024-01-01T10:00:00Z", value: "val" }];
- const params = new ZabbixHistoryPushParams(values, "1");
-
- await expect(request.prepare(zabbixAPI, params)).rejects.toThrow("history.push is only supported in Zabbix 7.0.0 and newer");
- });
});
diff --git a/src/test/history_push_integration.test.ts b/src/test/history_push_integration.test.ts
index b1dbcd6..607c69c 100644
--- a/src/test/history_push_integration.test.ts
+++ b/src/test/history_push_integration.test.ts
@@ -6,7 +6,6 @@ import {zabbixAPI} from '../datasources/zabbix-api.js';
jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
post: jest.fn(),
- getVersion: jest.fn().mockResolvedValue("7.0.0"),
}
}));
diff --git a/src/test/host_importer.test.ts b/src/test/host_importer.test.ts
index c2781d4..14fb94f 100644
--- a/src/test/host_importer.test.ts
+++ b/src/test/host_importer.test.ts
@@ -6,7 +6,6 @@ jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
executeRequest: jest.fn(),
post: jest.fn(),
- getVersion: jest.fn().mockResolvedValue("7.0.0"),
requestByPath: jest.fn()
},
ZABBIX_EDGE_DEVICE_BASE_GROUP: "Roadwork"
diff --git a/src/test/host_integration.test.ts b/src/test/host_integration.test.ts
index 8b3fad4..00d1544 100644
--- a/src/test/host_integration.test.ts
+++ b/src/test/host_integration.test.ts
@@ -8,7 +8,6 @@ import {ZABBIX_EDGE_DEVICE_BASE_GROUP, zabbixAPI} from '../datasources/zabbix-ap
jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
post: jest.fn(),
- getVersion: jest.fn().mockResolvedValue("7.0.0"),
executeRequest: jest.fn(),
baseURL: 'http://localhost/zabbix',
getLocations: jest.fn(),
diff --git a/src/test/host_query.test.ts b/src/test/host_query.test.ts
index d50e5aa..0762084 100644
--- a/src/test/host_query.test.ts
+++ b/src/test/host_query.test.ts
@@ -7,7 +7,6 @@ jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
executeRequest: jest.fn(),
post: jest.fn(),
- getVersion: jest.fn().mockResolvedValue("7.0.0"),
baseURL: "http://mock-zabbix",
getLocations: jest.fn()
},
diff --git a/src/test/indirect_dependencies.test.ts b/src/test/indirect_dependencies.test.ts
index 8e9c49a..19d82cd 100644
--- a/src/test/indirect_dependencies.test.ts
+++ b/src/test/indirect_dependencies.test.ts
@@ -7,7 +7,6 @@ jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
executeRequest: jest.fn(),
post: jest.fn(),
- getVersion: jest.fn().mockResolvedValue("7.0.0"),
baseURL: "http://mock-zabbix",
}
}));
diff --git a/src/test/misc_resolvers.test.ts b/src/test/misc_resolvers.test.ts
index 90a5609..4c6f588 100644
--- a/src/test/misc_resolvers.test.ts
+++ b/src/test/misc_resolvers.test.ts
@@ -5,8 +5,7 @@ import {zabbixAPI} from "../datasources/zabbix-api.js";
jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
executeRequest: jest.fn(),
- post: jest.fn(),
- getVersion: jest.fn().mockResolvedValue("7.0.0"),
+ post: jest.fn()
}
}));
diff --git a/src/test/query_optimization.test.ts b/src/test/query_optimization.test.ts
index 2fa6259..a7765ac 100644
--- a/src/test/query_optimization.test.ts
+++ b/src/test/query_optimization.test.ts
@@ -7,7 +7,6 @@ jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
executeRequest: jest.fn(),
post: jest.fn(),
- getVersion: jest.fn().mockResolvedValue("7.0.0"),
baseURL: "http://mock-zabbix",
}
}));
@@ -91,7 +90,7 @@ describe("Query Optimization", () => {
expect(zabbixAPI.post).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({
body: expect.objectContaining({
params: expect.objectContaining({
- output: expect.arrayContaining(["hostid", "tags"]),
+ output: ["hostid"],
selectTags: expect.any(Array)
})
})
@@ -188,7 +187,7 @@ describe("Query Optimization", () => {
expect(zabbixAPI.post).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({
body: expect.objectContaining({
params: expect.objectContaining({
- output: expect.arrayContaining(["hostid", "tags"]),
+ output: ["hostid"],
selectTags: expect.any(Array)
})
})
diff --git a/src/test/template_deleter.test.ts b/src/test/template_deleter.test.ts
index b1d09e9..68e4164 100644
--- a/src/test/template_deleter.test.ts
+++ b/src/test/template_deleter.test.ts
@@ -5,8 +5,7 @@ import {zabbixAPI} from "../datasources/zabbix-api.js";
jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
executeRequest: jest.fn(),
- post: jest.fn(),
- getVersion: jest.fn().mockResolvedValue("7.0.0"),
+ post: jest.fn()
}
}));
diff --git a/src/test/template_importer.test.ts b/src/test/template_importer.test.ts
index 976ef88..6258c0e 100644
--- a/src/test/template_importer.test.ts
+++ b/src/test/template_importer.test.ts
@@ -5,8 +5,7 @@ import {zabbixAPI} from "../datasources/zabbix-api.js";
jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
executeRequest: jest.fn(),
- post: jest.fn(),
- getVersion: jest.fn().mockResolvedValue("7.0.0"),
+ post: jest.fn()
}
}));
diff --git a/src/test/template_integration.test.ts b/src/test/template_integration.test.ts
index 8c87cc2..22858bb 100644
--- a/src/test/template_integration.test.ts
+++ b/src/test/template_integration.test.ts
@@ -8,7 +8,6 @@ import {zabbixAPI} from '../datasources/zabbix-api.js';
jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
post: jest.fn(),
- getVersion: jest.fn().mockResolvedValue("7.0.0"),
executeRequest: jest.fn(),
baseURL: 'http://localhost/zabbix'
}
diff --git a/src/test/template_link.test.ts b/src/test/template_link.test.ts
index c5acff8..ead11e1 100644
--- a/src/test/template_link.test.ts
+++ b/src/test/template_link.test.ts
@@ -6,7 +6,6 @@ import {zabbixAPI} from '../datasources/zabbix-api.js';
jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
post: jest.fn(),
- getVersion: jest.fn().mockResolvedValue("7.0.0"),
executeRequest: jest.fn(),
baseURL: 'http://localhost/zabbix',
requestByPath: jest.fn()
diff --git a/src/test/template_query.test.ts b/src/test/template_query.test.ts
index 22c5e97..f0a065e 100644
--- a/src/test/template_query.test.ts
+++ b/src/test/template_query.test.ts
@@ -7,7 +7,6 @@ jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
executeRequest: jest.fn(),
post: jest.fn(),
- getVersion: jest.fn().mockResolvedValue("7.0.0"),
baseURL: "http://mock-zabbix"
},
ZABBIX_EDGE_DEVICE_BASE_GROUP: "Roadwork"
@@ -83,11 +82,8 @@ describe("Template Resolver", () => {
method: "template.get",
params: expect.objectContaining({
search: {
- name: "Template",
- host: "Template"
- },
- searchByAny: true,
- searchWildcardsEnabled: true
+ name: "Template"
+ }
})
})
}));
@@ -109,11 +105,8 @@ describe("Template Resolver", () => {
method: "template.get",
params: expect.objectContaining({
search: {
- name: "Temp%1",
- host: "Temp%1"
- },
- searchByAny: true,
- searchWildcardsEnabled: true
+ name: "Temp%1"
+ }
})
})
}));
diff --git a/src/test/user_rights.test.ts b/src/test/user_rights.test.ts
index 99a30eb..0ac3732 100644
--- a/src/test/user_rights.test.ts
+++ b/src/test/user_rights.test.ts
@@ -6,7 +6,6 @@ jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
executeRequest: jest.fn(),
post: jest.fn(),
- getVersion: jest.fn().mockResolvedValue("7.0.0"),
baseURL: "http://mock-zabbix"
}
}));
diff --git a/src/test/user_rights_integration.test.ts b/src/test/user_rights_integration.test.ts
index ea987b4..a7e401c 100644
--- a/src/test/user_rights_integration.test.ts
+++ b/src/test/user_rights_integration.test.ts
@@ -8,7 +8,6 @@ import {zabbixAPI} from '../datasources/zabbix-api.js';
jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
post: jest.fn(),
- getVersion: jest.fn().mockResolvedValue("7.0.0"),
executeRequest: jest.fn(),
baseURL: 'http://localhost/zabbix',
getLocations: jest.fn(),
@@ -44,8 +43,7 @@ describe("User Rights Integration Tests", () => {
.mockResolvedValueOnce([{ groupid: "101", name: "Group1", uuid: "uuid1" }]) // templategroup.get for groups (in prepare)
.mockResolvedValueOnce([{ groupid: "201", name: "ConstructionSite/Test", uuid: "uuid2" }]) // hostgroup.get for groups (in prepare)
.mockResolvedValueOnce([{ usrgrpid: "1", name: "Test Group" }]) // usergroup.get
- .mockResolvedValueOnce({ usrgrpids: ["1"] }) // usergroup.update
- .mockResolvedValueOnce({ usrgrpids: [] }); // hostgroup.propagate
+ .mockResolvedValueOnce({ usrgrpids: ["1"] }); // usergroup.update
const response = await server.executeOperation({
query: mutation,
diff --git a/src/test/zabbix_6_0_compatibility.test.ts b/src/test/zabbix_6_0_compatibility.test.ts
deleted file mode 100644
index 6535d81..0000000
--- a/src/test/zabbix_6_0_compatibility.test.ts
+++ /dev/null
@@ -1,106 +0,0 @@
-import {createResolvers} from "../api/resolvers.js";
-import {zabbixAPI} from "../datasources/zabbix-api.js";
-import {Permission} from "../schema/generated/graphql.js";
-
-// Mocking ZabbixAPI
-jest.mock("../datasources/zabbix-api.js", () => ({
- zabbixAPI: {
- executeRequest: jest.fn(),
- post: jest.fn(),
- getVersion: jest.fn(),
- baseURL: "http://mock-zabbix"
- }
-}));
-
-describe("Zabbix 6.0 Compatibility", () => {
- let resolvers: any;
-
- beforeEach(() => {
- jest.clearAllMocks();
- resolvers = createResolvers();
- (zabbixAPI.getVersion as jest.Mock).mockResolvedValue("6.0.0");
- });
-
- test("importUserRights uses name-based fallback for host groups on Zabbix 6.0", async () => {
- // Mock Zabbix 6.0 behavior where hostgroup.get does NOT return UUID
- (zabbixAPI.post as jest.Mock).mockImplementation((path: string) => {
- if (path === "templategroup.get") return Promise.resolve([{ groupid: "101", name: "TemplateGroup1", uuid: "uuid-tg-1" }]);
- if (path === "hostgroup.get") return Promise.resolve([{ groupid: "201", name: "HostGroup1" }]); // NO UUID
- if (path === "usergroup.get") return Promise.resolve([]);
- if (path.startsWith("usergroup.create")) return Promise.resolve({ usrgrpids: ["301"] });
- if (path === "module.get") return Promise.resolve([]);
- if (path === "role.get") return Promise.resolve([]);
- return Promise.resolve([]);
- });
-
- const args = {
- input: {
- userGroups: [{
- name: "NewGroup",
- hostgroup_rights: [{
- name: "HostGroup1",
- uuid: "some-uuid-from-export", // This UUID won't match anything in 6.0 mock
- permission: Permission.Read
- }]
- }]
- },
- dryRun: false
- };
- const context = { zabbixAuthToken: "test-token" };
-
- const result = await resolvers.Mutation.importUserRights(null, args, context);
-
- expect(result.userGroups).toHaveLength(1);
- expect(result.userGroups[0].name).toBe("NewGroup");
- expect(result.userGroups[0].errors).toHaveLength(0); // Should succeed via name match
-
- // Verify that usergroup.create was called with correct groupid from name match
- const createCall = (zabbixAPI.post as jest.Mock).mock.calls.find(call => call[0].startsWith("usergroup.create"));
- expect(createCall[1].body.params.rights).toContainEqual({
- id: 201,
- permission: Permission.Read
- });
- });
-
- test("importUserRights performs manual expansion on Zabbix 6.0", async () => {
- // Mock Zabbix 6.0 behavior
- (zabbixAPI.post as jest.Mock).mockImplementation((path: string, options: any) => {
- if (path === "templategroup.get") return Promise.resolve([]);
- if (path === "hostgroup.get") {
- // If searching for subgroups
- if (options?.body?.params?.search?.name === "Parent/*") {
- return Promise.resolve([{ groupid: "202", name: "Parent/Child" }]);
- }
- return Promise.resolve([{ groupid: "201", name: "Parent" }]);
- }
- if (path === "usergroup.get") return Promise.resolve([]);
- if (path.startsWith("usergroup.create")) return Promise.resolve({ usrgrpids: ["301"] });
- if (path === "module.get") return Promise.resolve([]);
- if (path === "role.get") return Promise.resolve([]);
- return Promise.resolve([]);
- });
-
- const args = {
- input: {
- userGroups: [{
- name: "NewGroup",
- hostgroup_rights: [{
- name: "Parent",
- uuid: "uuid-parent",
- permission: Permission.Read
- }]
- }]
- },
- dryRun: false
- };
- const context = { zabbixAuthToken: "test-token" };
-
- await resolvers.Mutation.importUserRights(null, args, context);
-
- // Verify that usergroup.create was called with both parent and expanded child
- const createCall = (zabbixAPI.post as jest.Mock).mock.calls.find(call => call[0].startsWith("usergroup.create"));
- expect(createCall[1].body.params.rights).toHaveLength(2);
- expect(createCall[1].body.params.rights).toContainEqual({ id: 201, permission: Permission.Read });
- expect(createCall[1].body.params.rights).toContainEqual({ id: 202, permission: Permission.Read });
- });
-});
diff --git a/src/test/zabbix_docs_samples.test.ts b/src/test/zabbix_docs_samples.test.ts
index 7a5dafd..87c189f 100644
--- a/src/test/zabbix_docs_samples.test.ts
+++ b/src/test/zabbix_docs_samples.test.ts
@@ -8,7 +8,6 @@ import {zabbixAPI} from '../datasources/zabbix-api.js';
jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
post: jest.fn(),
- getVersion: jest.fn().mockResolvedValue("7.0.0"),
executeRequest: jest.fn(),
baseURL: 'http://localhost/zabbix',
getLocations: jest.fn(),