feat: add template and template group management via GraphQL

- Implemented GraphQL endpoints for importing, querying, and deleting Zabbix templates and template groups. - Added support for full template data import, including items, preprocessing steps, tags, and linked templates. - Implemented dependent item support by deferred creation logic in the template importer. - Added ability to query templates and template groups with name pattern filtering (supporting Zabbix wildcards). - Implemented batch deletion for templates and template groups by ID or name pattern. - Improved error reporting by including detailed Zabbix API error data in GraphQL responses. - Added comprehensive unit and integration tests covering all new functionality. - Provided GraphQL sample queries and mutations in the 'docs' directory for all new endpoints.
This commit is contained in:
Andreas Hilbig 2026-01-24 15:42:13 +01:00
parent e641f8e610
commit a3ed4886a3
22 changed files with 2450 additions and 20 deletions

View file

@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="index.ts" type="NodeJSConfigurationType" path-to-node="wsl://Ubuntu@/home/ahilbig/.nvm/versions/node/v24.12.0/bin/node" nameIsGenerated="true" path-to-js-file="src/index.ts" typescript-loader="bundled" working-dir="$PROJECT_DIR$"> <configuration default="false" name="index.ts" type="NodeJSConfigurationType" path-to-node="wsl://Ubuntu@/home/ahilbig/.nvm/versions/node/v24.12.0/bin/node" nameIsGenerated="true" path-to-js-file="src/index.ts" node-parameters="--import tsx" working-dir="$PROJECT_DIR$">
<envs> <envs>
<env name="ADDITIONAL_RESOLVERS" value="SinglePanelDevice,FourPanelDevice,DistanceTrackerDevice" /> <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="ADDITIONAL_SCHEMAS" value="./schema/extensions/display_devices.graphql,./schema/extensions/location_tracker_devices.graphql,./schema/extensions/location_tracker_commons.graphql" />

47
.idea/workspace.xml generated
View file

@ -4,13 +4,16 @@
<option name="autoReloadType" value="SELECTIVE" /> <option name="autoReloadType" value="SELECTIVE" />
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="d7a71994-2699-4ae4-9fd2-ee13b7f33d35" name="Changes" comment="chore: Enhance schema with `DeviceConfig` tags resolver and update IntelliJ workspace adjustments"> <list default="true" id="d7a71994-2699-4ae4-9fd2-ee13b7f33d35" name="Changes" comment="chore: Add `allDevices` query resolver, update Zabbix device query handling, and enhance schema with `DeviceConfig` and `WidgetPreview` types">
<change afterPath="$PROJECT_DIR$/src/testdata/templates/zbx_default_templates_vcr.yaml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/runConfigurations/index_ts.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/runConfigurations/index_ts.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/schema/devices.graphql" beforeDir="false" afterPath="$PROJECT_DIR$/schema/devices.graphql" afterDir="false" /> <change beforePath="$PROJECT_DIR$/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/package.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/schema/mutations.graphql" beforeDir="false" afterPath="$PROJECT_DIR$/schema/mutations.graphql" afterDir="false" />
<change beforePath="$PROJECT_DIR$/schema/queries.graphql" beforeDir="false" afterPath="$PROJECT_DIR$/schema/queries.graphql" afterDir="false" /> <change beforePath="$PROJECT_DIR$/schema/queries.graphql" beforeDir="false" afterPath="$PROJECT_DIR$/schema/queries.graphql" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/api/resolvers.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/api/resolvers.ts" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/api/resolvers.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/api/resolvers.ts" 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/datasources/zabbix-request.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/datasources/zabbix-request.ts" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/datasources/zabbix-request.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/datasources/zabbix-request.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/datasources/zabbix-templates.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/datasources/zabbix-templates.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/schema/generated/graphql.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/schema/generated/graphql.ts" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
@ -22,7 +25,7 @@
<execution /> <execution />
</component> </component>
<component name="EmbeddingIndexingInfo"> <component name="EmbeddingIndexingInfo">
<option name="cachedIndexableFilesCount" value="57" /> <option name="cachedIndexableFilesCount" value="70" />
<option name="fileBasedEmbeddingIndicesEnabled" value="true" /> <option name="fileBasedEmbeddingIndicesEnabled" value="true" />
</component> </component>
<component name="Git.Settings"> <component name="Git.Settings">
@ -52,7 +55,7 @@
"NIXITCH_NIX_PROFILES": "", "NIXITCH_NIX_PROFILES": "",
"NIXITCH_NIX_REMOTE": "", "NIXITCH_NIX_REMOTE": "",
"NIXITCH_NIX_USER_PROFILE_DIR": "", "NIXITCH_NIX_USER_PROFILE_DIR": "",
"Node.js.index.ts.executor": "Debug", "Node.js.index.ts.executor": "Run",
"RunOnceActivity.MCP Project settings loaded": "true", "RunOnceActivity.MCP Project settings loaded": "true",
"RunOnceActivity.ShowReadmeOnStart": "true", "RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true", "RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
@ -63,7 +66,7 @@
"go.import.settings.migrated": "true", "go.import.settings.migrated": "true",
"javascript.preferred.runtime.type.id": "node", "javascript.preferred.runtime.type.id": "node",
"junie.onboarding.icon.badge.shown": "true", "junie.onboarding.icon.badge.shown": "true",
"last_opened_file_path": "//wsl.localhost/Ubuntu/home/ahilbig/git/vcr/zabbix-graphql-api/src/test", "last_opened_file_path": "//wsl.localhost/Ubuntu/home/ahilbig/git/vcr/zabbix-graphql-api/src/testdata/templates",
"node.js.detected.package.eslint": "true", "node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true", "node.js.detected.package.tslint": "true",
"node.js.selected.package.eslint": "(autodetect)", "node.js.selected.package.eslint": "(autodetect)",
@ -74,7 +77,7 @@
"npm.compile.executor": "Run", "npm.compile.executor": "Run",
"npm.copy-schema.executor": "Run", "npm.copy-schema.executor": "Run",
"npm.prod.executor": "Run", "npm.prod.executor": "Run",
"settings.editor.selected.configurable": "preferences.sourceCode.TypeScript", "settings.editor.selected.configurable": "settings.javascript.runtime",
"to.speed.mode.migration.done": "true", "to.speed.mode.migration.done": "true",
"ts.external.directory.path": "\\\\wsl.localhost\\Ubuntu\\home\\ahilbig\\git\\vcr\\zabbix-graphql-api\\node_modules\\typescript\\lib", "ts.external.directory.path": "\\\\wsl.localhost\\Ubuntu\\home\\ahilbig\\git\\vcr\\zabbix-graphql-api\\node_modules\\typescript\\lib",
"vue.rearranger.settings.migration": "true" "vue.rearranger.settings.migration": "true"
@ -89,6 +92,7 @@
</component> </component>
<component name="RecentsManager"> <component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS"> <key name="CopyFile.RECENT_KEYS">
<recent name="\\wsl.localhost\Ubuntu\home\ahilbig\git\vcr\zabbix-graphql-api\src\testdata\templates" />
<recent name="\\wsl.localhost\Ubuntu\home\ahilbig\git\vcr\zabbix-graphql-api\src\test" /> <recent name="\\wsl.localhost\Ubuntu\home\ahilbig\git\vcr\zabbix-graphql-api\src\test" />
<recent name="\\wsl.localhost\Ubuntu\home\ahilbig\git\vcr\zabbix-graphql-api\.forgejo\workflows" /> <recent name="\\wsl.localhost\Ubuntu\home\ahilbig\git\vcr\zabbix-graphql-api\.forgejo\workflows" />
<recent name="\\wsl.localhost\Ubuntu\home\ahilbig\git\vcr\zabbix-graphql-api" /> <recent name="\\wsl.localhost\Ubuntu\home\ahilbig\git\vcr\zabbix-graphql-api" />
@ -160,7 +164,9 @@
<workItem from="1768273025985" duration="11343000" /> <workItem from="1768273025985" duration="11343000" />
<workItem from="1768380302361" duration="9751000" /> <workItem from="1768380302361" duration="9751000" />
<workItem from="1768551040782" duration="6556000" /> <workItem from="1768551040782" duration="6556000" />
<workItem from="1768913192173" duration="7156000" /> <workItem from="1768913192173" duration="14627000" />
<workItem from="1769095609607" duration="1390000" />
<workItem from="1769256682556" duration="8928000" />
</task> </task>
<task id="LOCAL-00001" summary="chore: Update IntelliJ workspace settings and add GitHub Actions workflow for Docker deployment"> <task id="LOCAL-00001" summary="chore: Update IntelliJ workspace settings and add GitHub Actions workflow for Docker deployment">
<option name="closed" value="true" /> <option name="closed" value="true" />
@ -274,7 +280,15 @@
<option name="project" value="LOCAL" /> <option name="project" value="LOCAL" />
<updated>1768592274523</updated> <updated>1768592274523</updated>
</task> </task>
<option name="localTasksCounter" value="15" /> <task id="LOCAL-00015" summary="chore: Add `allDevices` query resolver, update Zabbix device query handling, and enhance schema with `DeviceConfig` and `WidgetPreview` types">
<option name="closed" value="true" />
<created>1768925413032</created>
<option name="number" value="00015" />
<option name="presentableId" value="LOCAL-00015" />
<option name="project" value="LOCAL" />
<updated>1768925413032</updated>
</task>
<option name="localTasksCounter" value="16" />
<servers /> <servers />
</component> </component>
<component name="TypeScriptGeneratedFilesManager"> <component name="TypeScriptGeneratedFilesManager">
@ -308,16 +322,23 @@
<MESSAGE value="chore: Use DeviceConfig type instead of JSONObject scalar for tags - attribute of devices " /> <MESSAGE value="chore: Use DeviceConfig type instead of JSONObject scalar for tags - attribute of devices " />
<MESSAGE value="chore: Update `tags` field schema to use `DeviceConfig`, enhance `isDevice` type check, and adjust IntelliJ workspace" /> <MESSAGE value="chore: Update `tags` field schema to use `DeviceConfig`, enhance `isDevice` type check, and adjust IntelliJ workspace" />
<MESSAGE value="chore: Enhance schema with `DeviceConfig` tags resolver and update IntelliJ workspace adjustments" /> <MESSAGE value="chore: Enhance schema with `DeviceConfig` tags resolver and update IntelliJ workspace adjustments" />
<option name="LAST_COMMIT_MESSAGE" value="chore: Enhance schema with `DeviceConfig` tags resolver and update IntelliJ workspace adjustments" /> <MESSAGE value="chore: Add `allDevices` query resolver, update Zabbix device query handling, and enhance schema with `DeviceConfig` and `WidgetPreview` types" />
<option name="LAST_COMMIT_MESSAGE" value="chore: Add `allDevices` query resolver, update Zabbix device query handling, and enhance schema with `DeviceConfig` and `WidgetPreview` types" />
</component> </component>
<component name="XDebuggerManager"> <component name="XDebuggerManager">
<breakpoint-manager> <breakpoint-manager>
<breakpoints> <breakpoints>
<line-breakpoint enabled="true" type="javascript"> <line-breakpoint enabled="true" type="javascript">
<url>file://$PROJECT_DIR$/src/datasources/zabbix-hosts.ts</url> <url>file://$PROJECT_DIR$/src/datasources/zabbix-request.ts</url>
<line>149</line> <line>133</line>
<properties lambdaOrdinal="-1" /> <properties lambdaOrdinal="-1" />
<option name="timeStamp" value="1" /> <option name="timeStamp" value="5" />
</line-breakpoint>
<line-breakpoint enabled="true" type="javascript">
<url>file://$PROJECT_DIR$/src/datasources/zabbix-request.ts</url>
<line>213</line>
<properties lambdaOrdinal="-1" />
<option name="timeStamp" value="6" />
</line-breakpoint> </line-breakpoint>
</breakpoints> </breakpoints>
</breakpoint-manager> </breakpoint-manager>

View file

@ -0,0 +1,18 @@
### Query
Use this query to list all template groups.
```graphql
query AllTemplateGroups($name_pattern: String) {
allTemplateGroups(name_pattern: $name_pattern) {
groupid
name
}
}
```
### Variables
```json
{
"name_pattern": "Templates/Roadwork/*"
}
```

View file

@ -0,0 +1,30 @@
### Mutation
Use this mutation to delete template groups by their numeric IDs or by a name pattern.
```graphql
mutation DeleteTemplateGroups($groupids: [Int!], $name_pattern: String) {
deleteTemplateGroups(groupids: $groupids, name_pattern: $name_pattern) {
id
message
error {
message
code
data
}
}
}
```
### Variables (by ID)
```json
{
"groupids": [201]
}
```
### Variables (by name pattern)
```json
{
"name_pattern": "Templates/Roadwork/%"
}
```

View file

@ -0,0 +1,30 @@
### Mutation
Use this mutation to delete templates by their numeric IDs or by a name pattern.
```graphql
mutation DeleteTemplates($templateids: [Int!], $name_pattern: String) {
deleteTemplates(templateids: $templateids, name_pattern: $name_pattern) {
id
message
error {
message
code
data
}
}
}
```
### Variables (by ID)
```json
{
"templateids": [501]
}
```
### Variables (by name pattern)
```json
{
"name_pattern": "BT_DEVICE_TRACKER%"
}
```

View file

@ -0,0 +1,59 @@
### Mutation
Use this mutation to import template groups.
```graphql
mutation ImportTemplateGroups($templateGroups: [CreateTemplateGroup!]!) {
importTemplateGroups(templateGroups: $templateGroups) {
groupName
groupid
message
error {
message
code
data
}
}
}
```
### Variables
This sample data is based on the `template_groups` from `src/testdata/templates/zbx_default_templates_vcr.yaml`.
```json
{
"templateGroups": [
{
"uuid": "43aab460fe444f18886b19948413b7e3",
"groupName": "Permissions/ConstructionSite"
},
{
"uuid": "376524057e094c07aaa0cf7f524849dc",
"groupName": "Templates/Roadwork/Controller"
},
{
"uuid": "7d83c76454564390bb0e34600780eaec",
"groupName": "Templates/Roadwork/Device-Capabilities"
},
{
"uuid": "48d5d2a18a08448c96a931b63bb2c97d",
"groupName": "Templates/Roadwork/Device-Capabilities/FLASH_ATTACHABLE"
},
{
"uuid": "785986b84892468ea2e92d912747b1d3",
"groupName": "Templates/Roadwork/Device-Capabilities/GEOLOCALIZABLE"
},
{
"uuid": "a4b79479e97a4b48972dcb476d45e55a",
"groupName": "Templates/Roadwork/Device-Capabilities/HAS_OPERATIONAL_DATA"
},
{
"uuid": "3604af8102644bee9dcaf0f9c1ee93a1",
"groupName": "Templates/Roadwork/Devices"
},
{
"uuid": "5ad0bd9e42a4487e869e9e41b38fe553",
"groupName": "Templates/Roadwork/DisplayLibrary"
}
]
}
```

View file

@ -0,0 +1,97 @@
### Mutation
Use this mutation to import templates along with their items, tags, and linked templates.
```graphql
mutation ImportTemplates($templates: [CreateTemplate!]!) {
importTemplates(templates: $templates) {
host
templateid
message
error {
message
code
data
}
}
}
```
### Variables
This sample data is based on the `BT_DEVICE_TRACKER` template from `src/testdata/templates/zbx_default_templates_vcr.yaml`.
```json
{
"templates": [
{
"uuid": "27474f627cb344b782a81c16d7e0c7d1",
"host": "BT_DEVICE_TRACKER",
"name": "BT_DEVICE_TRACKER",
"groupNames": ["Templates/Roadwork/Devices"],
"templates": [
{ "name": "ROADWORK_DEVICE" }
],
"tags": [
{ "tag": "class", "value": "roadwork" },
{ "tag": "deviceType", "value": "bt_device_tracker_generic" }
],
"items": [
{
"uuid": "d4d3ec9f3ca940a39a721b6cfd2f3471",
"name": "location",
"type": 18,
"key": "location",
"value_type": 4,
"history": "2d",
"preprocessing": [
{
"type": 21,
"params": [
"var obj=JSON.parse(value);\n\nif (obj[\"isFiltered\"]) {\n throw \"Result is filtered\";\n return \"filtered\";\n}\n\nreturn value;"
]
},
{
"type": 15,
"params": ["filtered"],
"error_handler": 1
}
],
"master_item": {
"key": "mqtt.trap[deviceValue/location]"
}
},
{
"uuid": "380c4a7d752848cba3b5a59a0f9b13c0",
"name": "MQTT_LOCATION",
"type": 2,
"key": "mqtt.trap[deviceValue/location]",
"value_type": 4,
"history": "0"
}
]
}
]
}
```
### Mapping Reference
When converting from Zabbix YAML/XML exports, use the following numeric mappings for items and preprocessing:
#### Item Type (`type`)
- `2`: ZABBIX_TRAP (TRAP)
- `18`: DEPENDANT_ITEM (DEPENDENT)
- `21`: SIMULATOR_JAVASCRIPT (JAVASCRIPT)
#### Value Type (`value_type`)
- `0`: Float
- `3`: Int (Numeric unsigned)
- `4`: Text
#### Preprocessing Type (`type`)
- `12`: JSONPATH
- `15`: NOT_MATCHES_REGEX
- `21`: JAVASCRIPT
#### Error Handler (`error_handler`)
- `1`: DISCARD_VALUE
- `2`: SET_VALUE
- `3`: SET_ERROR

View file

@ -0,0 +1,18 @@
### Query
Use this query to verify the results of the template import.
```graphql
query GetTemplates($name_pattern: String) {
templates(name_pattern: $name_pattern) {
templateid
name
}
}
```
### Variables
```json
{
"name_pattern": "BT_DEVICE_TRACKER"
}
```

View file

@ -6,7 +6,7 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"compile": "tsc", "compile": "tsc",
"start": "nodemon --watch \"src/**\" --watch \"schema.graphql\" --ext \"ts,json\" --exec \"tsc & node --require ts-node/register --inspect --import tsx/esm ./src/index.ts\"", "start": "nodemon --watch \"src/**\" --watch \"schema.graphql\" --ext \"ts,json\" --exec \"node --import tsx ./src/index.ts\"",
"prod": "npm run copy-schema && node ./dist/index.js", "prod": "npm run copy-schema && node ./dist/index.js",
"test": "jest --detectOpenHandles --forceExit --bail", "test": "jest --detectOpenHandles --forceExit --bail",
"codegen": "graphql-codegen --config codegen.ts --watch \"schema.graphql\"", "codegen": "graphql-codegen --config codegen.ts --watch \"schema.graphql\"",

View file

@ -29,12 +29,152 @@ type Mutation {
importHosts(hosts: [CreateHost!]!):[ImportHostResponse!] importHosts(hosts: [CreateHost!]!):[ImportHostResponse!]
importUserRights(input: UserRightsInput!, dryRun: Boolean! = true): ImportUserRightsResult importUserRights(input: UserRightsInput!, dryRun: Boolean! = true): ImportUserRightsResult
"""
(Mass) Import template groups
and assign them by groupid or name.
Return value: If no error occurs a groupid be returned for each created group,
otherwise the return object will contain an error message
Authentication: By zbx_session - cookie or zabbix-auth-token - header
"""
importTemplateGroups(templateGroups: [CreateTemplateGroup!]!):[CreateTemplateGroupResponse!]
"""
(Mass) Import templates.
Return value: If no error occurs a templateid will be returned for each created template,
otherwise the return object will contain an error message.
Authentication: By zbx_session - cookie or zabbix-auth-token - header
"""
importTemplates(templates: [CreateTemplate!]!):[ImportTemplateResponse!]
"""
Delete templates.
Authentication: By zbx_session - cookie or zabbix-auth-token - header
"""
deleteTemplates(templateids: [Int!], name_pattern: String): [DeleteResponse!]
"""
Delete template groups.
Authentication: By zbx_session - cookie or zabbix-auth-token - header
"""
deleteTemplateGroups(groupids: [Int!], name_pattern: String): [DeleteResponse!]
} }
#################################################################### ####################################################################
# Input types used for importXXX - and storeXXX - Mutations # Input types used for importXXX - and storeXXX - Mutations
#################################################################### ####################################################################
type DeleteResponse {
id: Int!
message: String
error: ApiError
}
input CreateTemplateGroup {
"""
Name of the template group
"""
groupName: String!
"""
Internally used unique id
(will be assigned by Zabbix if empty)
"""
uuid: String
}
input CreateTemplate {
"""
Name of the template
"""
host: String!
"""
Visible name of the template
"""
name: String
"""
groupNames is used to assign the created object
to a template group.
"""
groupNames: [String!]!
"""
Optionally the internal groupids can be passed - in this case the
groupName is ignored
"""
groupids: [Int]
"""
Internally used unique id
(will be assigned by Zabbix if empty)
"""
uuid: String
"""
Template items
"""
items: [CreateTemplateItem!]
"""
Linked templates
"""
templates: [CreateLinkedTemplate!]
"""
Template tags
"""
tags: [CreateTag!]
}
input CreateTemplateItem {
uuid: String
name: String!
type: Int
key: String!
value_type: Int
history: String
units: String
delay: String
description: String
preprocessing: [CreateItemPreprocessing!]
tags: [CreateTag!]
master_item: CreateMasterItem
}
input CreateMasterItem {
key: String!
}
input CreateItemPreprocessing {
type: Int!
params: [String!]!
error_handler: Int
error_handler_params: String
}
input CreateLinkedTemplate {
name: String!
}
input CreateTag {
tag: String!
value: String
}
type ImportTemplateResponse {
host: String!
templateid: String
message: String
error: ApiError
}
type CreateTemplateGroupResponse {
groupName: String!
groupid: Int
message: String
error: ApiError
}
input CreateHostGroup { input CreateHostGroup {
""" """
Name of the host group Name of the host group

View file

@ -113,5 +113,15 @@ type Query {
exclude_groups_pattern: Regex allowing to exclude all matching hostgroups from group permissions exclude_groups_pattern: Regex allowing to exclude all matching hostgroups from group permissions
""" """
exportUserRights(name_pattern: String = "" exclude_hostgroups_pattern: String = ""): UserRights exportUserRights(name_pattern: String = "" exclude_hostgroups_pattern: String = ""): UserRights
"""
Get templates.
"""
templates(hostids: [Int], name_pattern: String): [Template]
"""
Get template groups.
"""
allTemplateGroups(name_pattern: String): [HostGroup]
} }

View file

@ -6,6 +6,10 @@ import {
MutationCreateHostArgs, MutationCreateHostArgs,
MutationImportHostGroupsArgs, MutationImportHostGroupsArgs,
MutationImportHostsArgs, MutationImportHostsArgs,
MutationImportTemplateGroupsArgs,
MutationImportTemplatesArgs,
MutationDeleteTemplatesArgs,
MutationDeleteTemplateGroupsArgs,
MutationImportUserRightsArgs, MutationImportUserRightsArgs,
Permission, QueryAllDevicesArgs, Permission, QueryAllDevicesArgs,
QueryAllHostGroupsArgs, QueryAllHostGroupsArgs,
@ -13,12 +17,15 @@ import {
QueryExportHostValueHistoryArgs, QueryExportHostValueHistoryArgs,
QueryExportUserRightsArgs, QueryExportUserRightsArgs,
QueryHasPermissionsArgs, QueryHasPermissionsArgs,
QueryTemplatesArgs,
QueryUserPermissionsArgs, QueryUserPermissionsArgs,
Resolvers, Resolvers,
StorageItemType, StorageItemType,
} from "../schema/generated/graphql.js"; } from "../schema/generated/graphql.js";
import {HostImporter} from "../execution/host_importer.js"; import {HostImporter} from "../execution/host_importer.js";
import {TemplateImporter} from "../execution/template_importer.js";
import {TemplateDeleter} from "../execution/template_deleter.js";
import {HostValueExporter} from "../execution/host_exporter.js"; import {HostValueExporter} from "../execution/host_exporter.js";
import {logger} from "../logging/logger.js"; import {logger} from "../logging/logger.js";
import {ParsedArgs, ZabbixRequest} from "../datasources/zabbix-request.js"; import {ParsedArgs, ZabbixRequest} from "../datasources/zabbix-request.js";
@ -39,6 +46,14 @@ import {
ZabbixImportUserRolesRequest, ZabbixImportUserRolesRequest,
ZabbixQueryUserRolesRequest ZabbixQueryUserRolesRequest
} from "../datasources/zabbix-userroles.js"; } from "../datasources/zabbix-userroles.js";
import {
ZabbixCreateItemRequest,
ZabbixCreateTemplateGroupRequest,
ZabbixCreateTemplateRequest,
ZabbixQueryItemRequest,
ZabbixQueryTemplateGroupRequest,
ZabbixQueryTemplatesRequest
} from "../datasources/zabbix-templates.js";
import {ZABBIX_EDGE_DEVICE_BASE_GROUP, zabbixAPI} from "../datasources/zabbix-api.js"; import {ZABBIX_EDGE_DEVICE_BASE_GROUP, zabbixAPI} from "../datasources/zabbix-api.js";
import {GraphQLInterfaceType, GraphQLList} from "graphql/type/index.js"; import {GraphQLInterfaceType, GraphQLList} from "graphql/type/index.js";
import {isDevice} from "./resolver_helpers.js"; import {isDevice} from "./resolver_helpers.js";
@ -129,6 +144,37 @@ export function createResolvers(): Resolvers {
userGroups: groups, userGroups: groups,
userRoles: roles userRoles: roles
} }
},
templates: async (_parent: any, args: QueryTemplatesArgs, {
zabbixAuthToken,
cookie
}: any) => {
let params: any = {}
if (args.hostids) {
params.templateids = args.hostids
}
if (args.name_pattern) {
params.search = {
name: args.name_pattern
}
}
return await new ZabbixQueryTemplatesRequest(zabbixAuthToken, cookie)
.executeRequestThrowError(zabbixAPI, new ParsedArgs(params));
},
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(params));
} }
}, },
Mutation: { Mutation: {
@ -172,6 +218,30 @@ export function createResolvers(): Resolvers {
userRoles: userRolesImport, userRoles: userRolesImport,
userGroups: userGroupsImport userGroups: userGroupsImport
} }
},
importTemplateGroups: async (_parent: any, args: MutationImportTemplateGroupsArgs, {
zabbixAuthToken,
cookie
}: any) => {
return TemplateImporter.importTemplateGroups(args.templateGroups, zabbixAuthToken, cookie)
},
importTemplates: async (_parent: any, args: MutationImportTemplatesArgs, {
zabbixAuthToken,
cookie
}: any) => {
return TemplateImporter.importTemplates(args.templates, zabbixAuthToken, cookie)
},
deleteTemplates: async (_parent: any, args: MutationDeleteTemplatesArgs, {
zabbixAuthToken,
cookie
}: any) => {
return TemplateDeleter.deleteTemplates(args.templateids, args.name_pattern, zabbixAuthToken, cookie)
},
deleteTemplateGroups: async (_parent: any, args: MutationDeleteTemplateGroupsArgs, {
zabbixAuthToken,
cookie
}: any) => {
return TemplateDeleter.deleteTemplateGroups(args.groupids, args.name_pattern, zabbixAuthToken, cookie)
} }
}, },

View file

@ -39,7 +39,7 @@ export class ParsedArgs {
constructor(params?: any) { constructor(params?: any) {
if (Array.isArray(params)) { if (Array.isArray(params)) {
this.zabbix_params = params.map(arg => this.parseArgObject(arg)) this.zabbix_params = params.map(arg => this.parseArgObject(arg))
} else if (params instanceof Object) { } else {
this.zabbix_params = this.parseArgObject(params) this.zabbix_params = this.parseArgObject(params)
} }
} }
@ -52,9 +52,12 @@ export class ParsedArgs {
return paramName in this.zabbix_params ? this.zabbix_params[paramName] : undefined return paramName in this.zabbix_params ? this.zabbix_params[paramName] : undefined
} }
parseArgObject(args?: Object) { parseArgObject(args?: any) {
if (args && (typeof args !== 'object' || args.constructor !== Object)) {
return args;
}
let result: ZabbixParams let result: ZabbixParams
if (args) { if (args && typeof args === 'object' && args.constructor === Object) {
if ("name_pattern" in args && typeof args["name_pattern"] == "string") { if ("name_pattern" in args && typeof args["name_pattern"] == "string") {
if (args["name_pattern"]) { if (args["name_pattern"]) {
this.name_pattern = args["name_pattern"] this.name_pattern = args["name_pattern"]
@ -159,7 +162,10 @@ export class ZabbixRequest<T extends ZabbixResult, A extends ParsedArgs = Parsed
let params: ZabbixParams let params: ZabbixParams
if (Array.isArray(args?.zabbix_params)) { if (Array.isArray(args?.zabbix_params)) {
params = args?.zabbix_params.map(paramsObj => { params = args?.zabbix_params.map(paramsObj => {
if (paramsObj !== null && typeof paramsObj === 'object' && paramsObj.constructor === Object) {
return {...this.requestBodyTemplate.params, ...paramsObj} return {...this.requestBodyTemplate.params, ...paramsObj}
}
return paramsObj;
}) })
} else { } else {
params = {...this.requestBodyTemplate.params, ...zabbixParams ?? this.createZabbixParams(args)} params = {...this.requestBodyTemplate.params, ...zabbixParams ?? this.createZabbixParams(args)}

View file

@ -1,5 +1,7 @@
import { ZabbixRequest } from "./zabbix-request.js"; import {isZabbixErrorResult, ParsedArgs, ZabbixRequest} from "./zabbix-request.js";
import {ZabbixAPI} from "./zabbix-api.js";
import {logger} from "../logging/logger.js";
@ -30,3 +32,40 @@ export class ZabbixQueryTemplateGroupRequest extends ZabbixRequest<ZabbixQueryTe
} }
export class ZabbixCreateTemplateGroupRequest extends ZabbixRequest<{ groupids: string[] }> {
constructor(authToken?: string | null, cookie?: string | null) {
super("templategroup.create", authToken, cookie);
}
}
export class ZabbixCreateTemplateRequest extends ZabbixRequest<{ templateids: string[] }> {
constructor(authToken?: string | null, cookie?: string | null) {
super("template.create", authToken, cookie);
}
}
export class ZabbixQueryItemRequest extends ZabbixRequest<any[]> {
constructor(authToken?: string | null, cookie?: string | null) {
super("item.get", authToken, cookie);
}
}
export class ZabbixCreateItemRequest extends ZabbixRequest<{ itemids: string[] }> {
constructor(authToken?: string | null, cookie?: string | null) {
super("item.create", authToken, cookie);
}
}
export class ZabbixDeleteTemplatesRequest extends ZabbixRequest<{ templateids: string[] }> {
constructor(authToken?: string | null, cookie?: string | null) {
super("template.delete", authToken, cookie);
}
}
export class ZabbixDeleteTemplateGroupsRequest extends ZabbixRequest<{ groupids: string[] }> {
constructor(authToken?: string | null, cookie?: string | null) {
super("templategroup.delete", authToken, cookie);
}
}

View file

@ -0,0 +1,107 @@
import {DeleteResponse} from "../schema/generated/graphql.js";
import {
ZabbixDeleteTemplateGroupsRequest,
ZabbixDeleteTemplatesRequest,
ZabbixQueryTemplateGroupRequest,
ZabbixQueryTemplatesRequest
} from "../datasources/zabbix-templates.js";
import {isZabbixErrorResult, ParsedArgs} from "../datasources/zabbix-request.js";
import {zabbixAPI} from "../datasources/zabbix-api.js";
export class TemplateDeleter {
public static async deleteTemplates(templateids: number[] | null | undefined, name_pattern?: string | null, zabbixAuthToken?: string, cookie?: string): Promise<DeleteResponse[]> {
const result: DeleteResponse[] = [];
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));
// Merge and deduplicate
idsToDelete = Array.from(new Set([...idsToDelete, ...foundIds]));
}
}
if (idsToDelete.length === 0) {
return [];
}
// Zabbix template.delete accepts an array of template IDs
const deleteResult = await new ZabbixDeleteTemplatesRequest(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));
}
// If the whole batch fails, we report the error for each ID
for (const id of idsToDelete) {
result.push({
id: id,
message: errorMessage,
error: deleteResult.error
});
}
} else if (deleteResult?.templateids) {
for (const id of idsToDelete) {
result.push({
id: id,
message: `Template ${id} deleted successfully`
});
}
}
return result;
}
public static async deleteTemplateGroups(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 ZabbixQueryTemplateGroupRequest(zabbixAuthToken, cookie)
.executeRequestReturnError(zabbixAPI, new ParsedArgs({ name_pattern: 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 ZabbixDeleteTemplateGroupsRequest(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: `Template group ${id} deleted successfully`
});
}
}
return result;
}
}

View file

@ -0,0 +1,220 @@
import {
CreateTemplate,
CreateTemplateGroup,
CreateTemplateGroupResponse,
ImportTemplateResponse,
InputMaybe
} from "../schema/generated/graphql.js";
import {logger} from "../logging/logger.js";
import {
ZabbixCreateItemRequest,
ZabbixCreateTemplateGroupRequest,
ZabbixCreateTemplateRequest,
ZabbixQueryItemRequest,
ZabbixQueryTemplateGroupRequest,
ZabbixQueryTemplatesRequest
} from "../datasources/zabbix-templates.js";
import {isZabbixErrorResult, ParsedArgs, ZabbixErrorResult} from "../datasources/zabbix-request.js";
import {zabbixAPI} from "../datasources/zabbix-api.js";
export class TemplateImporter {
public static async importTemplateGroups(templateGroups: InputMaybe<Array<CreateTemplateGroup>> | undefined, zabbixAuthToken?: string, cookie?: string) {
if (!templateGroups) {
return null
}
let result: CreateTemplateGroupResponse[] = []
for (let group of templateGroups) {
let createGroupResult: { groupids: string[] } | ZabbixErrorResult | undefined = undefined;
// Try to find if it exists by name first
let groups = await new ZabbixQueryTemplateGroupRequest(zabbixAuthToken, cookie).executeRequestReturnError(zabbixAPI, new ParsedArgs({
filter_name: group.groupName
}))
let groupid = 0
let message: string | undefined = undefined
if (!isZabbixErrorResult(groups) && groups?.length) {
groupid = Number(groups[0].groupid)
message = `Template group ${group.groupName} already exists with groupid=${groupid} - skipping`
logger.debug(message)
} else {
createGroupResult = await new ZabbixCreateTemplateGroupRequest(zabbixAuthToken, cookie)
.executeRequestReturnError(zabbixAPI, new ParsedArgs({
name: group.groupName,
uuid: group.uuid
}))
if (isZabbixErrorResult(createGroupResult)) {
let errorMessage = createGroupResult.error.message;
if (createGroupResult.error.data) {
errorMessage += " " + (typeof createGroupResult.error.data === 'string' ? createGroupResult.error.data : JSON.stringify(createGroupResult.error.data));
}
result.push({
groupName: group.groupName,
message: `Unable to create template group ${group.groupName}: ${errorMessage}`,
error: createGroupResult.error
})
continue
} else if (createGroupResult?.groupids?.length) {
groupid = Number(createGroupResult.groupids[0])
}
}
if (groupid) {
result.push({
groupName: group.groupName,
groupid: groupid,
message: message
})
} else {
result.push({
groupName: group.groupName,
message: `Unable to create template group ${group.groupName}: Unknown error`,
error: { message: "Unknown error - no groupid returned" }
})
}
}
return result
}
public static async importTemplates(templates: InputMaybe<Array<CreateTemplate>> | undefined, zabbixAuthToken?: string, cookie?: string) {
if (!templates) {
return null
}
let result: ImportTemplateResponse[] = []
for (let template of templates) {
// 1. Resolve Group IDs
let groupids = template.groupids
if (!groupids || groupids.length === 0) {
let groups = await new ZabbixQueryTemplateGroupRequest(zabbixAuthToken, cookie).executeRequestReturnError(zabbixAPI, new ParsedArgs({
filter_name: template.groupNames
}))
if (isZabbixErrorResult(groups) || !groups?.length) {
result.push({
host: template.host,
message: `Unable to find template groups=${template.groupNames}`
})
continue
}
groupids = groups.map(g => Number(g.groupid))
}
// 2. Resolve Linked Templates IDs
let linkedTemplates: { templateid: string }[] = []
if (template.templates && template.templates.length > 0) {
let templateNames = template.templates.map(t => t.name)
let queryResult = await new ZabbixQueryTemplatesRequest(zabbixAuthToken, cookie).executeRequestReturnError(zabbixAPI, new ParsedArgs({
filter_host: templateNames
}))
if (isZabbixErrorResult(queryResult)) {
let errorMessage = queryResult.error.message;
if (queryResult.error.data) {
errorMessage += " " + (typeof queryResult.error.data === 'string' ? queryResult.error.data : JSON.stringify(queryResult.error.data));
}
result.push({
host: template.host,
message: `Error querying linked templates: ${errorMessage}`,
error: queryResult.error
})
continue
}
linkedTemplates = queryResult.map(t => ({ templateid: t.templateid }))
}
// 3. Create Template
let templateCreateParams: any = {
host: template.host,
name: template.name || template.host,
groups: groupids.map(id => ({ groupid: id })),
uuid: template.uuid,
templates: linkedTemplates,
tags: template.tags?.map(t => ({ tag: t.tag, value: t.value || "" }))
}
let templateImportResult = await new ZabbixCreateTemplateRequest(zabbixAuthToken, cookie)
.executeRequestReturnError(zabbixAPI, new ParsedArgs(templateCreateParams))
if (isZabbixErrorResult(templateImportResult) || !templateImportResult?.templateids?.length) {
let errorMessage = isZabbixErrorResult(templateImportResult) ? templateImportResult.error.message : "Unknown error";
if (isZabbixErrorResult(templateImportResult) && templateImportResult.error.data) {
errorMessage += " " + (typeof templateImportResult.error.data === 'string' ? templateImportResult.error.data : JSON.stringify(templateImportResult.error.data));
}
result.push({
host: template.host,
message: `Unable to import template=${template.host}: ${errorMessage}`,
error: isZabbixErrorResult(templateImportResult) ? templateImportResult.error : undefined
})
continue
}
let templateid = templateImportResult.templateids[0]
// 4. Create Items if any
if (template.items && template.items.length > 0) {
const createdItemKeyToId = new Map<string, string>();
let itemsToCreate = [...template.items];
let retry = true;
while (retry && itemsToCreate.length > 0) {
retry = false;
const remainingItems: typeof itemsToCreate = [];
for (let item of itemsToCreate) {
if (item.master_item && !createdItemKeyToId.has(item.master_item.key)) {
remainingItems.push(item);
continue;
}
let { key, master_item, ...itemData } = item;
let itemCreateParams: any = {
...itemData,
key_: key,
hostid: templateid,
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
})),
tags: item.tags?.map(t => ({ tag: t.tag, value: t.value || "" }))
}
if (master_item) {
itemCreateParams.master_itemid = createdItemKeyToId.get(master_item.key);
}
let itemResult = await new ZabbixCreateItemRequest(zabbixAuthToken, cookie)
.executeRequestReturnError(zabbixAPI, new ParsedArgs(itemCreateParams))
if (isZabbixErrorResult(itemResult)) {
let errorMessage = itemResult.error.message;
if (itemResult.error.data) {
errorMessage += " " + (typeof itemResult.error.data === 'string' ? itemResult.error.data : JSON.stringify(itemResult.error.data));
}
logger.error(`Unable to create item ${item.name} for template ${template.host}: ${errorMessage}`)
} else if (itemResult?.itemids?.length) {
createdItemKeyToId.set(key, itemResult.itemids[0]);
retry = true;
}
}
itemsToCreate = remainingItems;
}
if (itemsToCreate.length > 0) {
logger.error(`Unable to create ${itemsToCreate.length} items for template ${template.host} due to missing master items: ${itemsToCreate.map(i => i.name).join(", ")}`);
}
}
result.push({
host: template.host,
templateid: templateid
})
}
return result
}
}

View file

@ -81,6 +81,94 @@ export interface CreateHostResponse {
itemids?: Maybe<Array<Maybe<Scalars['Int']['output']>>>; itemids?: Maybe<Array<Maybe<Scalars['Int']['output']>>>;
} }
export interface CreateItemPreprocessing {
error_handler?: InputMaybe<Scalars['Int']['input']>;
error_handler_params?: InputMaybe<Scalars['String']['input']>;
params: Array<Scalars['String']['input']>;
type: Scalars['Int']['input'];
}
export interface CreateLinkedTemplate {
name: Scalars['String']['input'];
}
export interface CreateMasterItem {
key: Scalars['String']['input'];
}
export interface CreateTag {
tag: Scalars['String']['input'];
value?: InputMaybe<Scalars['String']['input']>;
}
export interface CreateTemplate {
/**
* groupNames is used to assign the created object
* to a template group.
*/
groupNames: Array<Scalars['String']['input']>;
/**
* Optionally the internal groupids can be passed - in this case the
* groupName is ignored
*/
groupids?: InputMaybe<Array<InputMaybe<Scalars['Int']['input']>>>;
/** Name of the template */
host: Scalars['String']['input'];
/** Template items */
items?: InputMaybe<Array<CreateTemplateItem>>;
/** Visible name of the template */
name?: InputMaybe<Scalars['String']['input']>;
/** Template tags */
tags?: InputMaybe<Array<CreateTag>>;
/** Linked templates */
templates?: InputMaybe<Array<CreateLinkedTemplate>>;
/**
* Internally used unique id
* (will be assigned by Zabbix if empty)
*/
uuid?: InputMaybe<Scalars['String']['input']>;
}
export interface CreateTemplateGroup {
/** Name of the template group */
groupName: Scalars['String']['input'];
/**
* Internally used unique id
* (will be assigned by Zabbix if empty)
*/
uuid?: InputMaybe<Scalars['String']['input']>;
}
export interface CreateTemplateGroupResponse {
__typename?: 'CreateTemplateGroupResponse';
error?: Maybe<ApiError>;
groupName: Scalars['String']['output'];
groupid?: Maybe<Scalars['Int']['output']>;
message?: Maybe<Scalars['String']['output']>;
}
export interface CreateTemplateItem {
delay?: InputMaybe<Scalars['String']['input']>;
description?: InputMaybe<Scalars['String']['input']>;
history?: InputMaybe<Scalars['String']['input']>;
key: Scalars['String']['input'];
master_item?: InputMaybe<CreateMasterItem>;
name: Scalars['String']['input'];
preprocessing?: InputMaybe<Array<CreateItemPreprocessing>>;
tags?: InputMaybe<Array<CreateTag>>;
type?: InputMaybe<Scalars['Int']['input']>;
units?: InputMaybe<Scalars['String']['input']>;
uuid?: InputMaybe<Scalars['String']['input']>;
value_type?: InputMaybe<Scalars['Int']['input']>;
}
export interface DeleteResponse {
__typename?: 'DeleteResponse';
error?: Maybe<ApiError>;
id: Scalars['Int']['output'];
message?: Maybe<Scalars['String']['output']>;
}
/** /**
* (IoT / Edge - ) Devices are hosts having a state containing the "output" / the business data which is exposed * (IoT / Edge - ) Devices are hosts having a state containing the "output" / the business data which is exposed
* besides monitoring information. * besides monitoring information.
@ -232,6 +320,14 @@ export interface ImportHostResponse {
message?: Maybe<Scalars['String']['output']>; message?: Maybe<Scalars['String']['output']>;
} }
export interface ImportTemplateResponse {
__typename?: 'ImportTemplateResponse';
error?: Maybe<ApiError>;
host: Scalars['String']['output'];
message?: Maybe<Scalars['String']['output']>;
templateid?: Maybe<Scalars['String']['output']>;
}
export interface ImportUserRightResult { export interface ImportUserRightResult {
__typename?: 'ImportUserRightResult'; __typename?: 'ImportUserRightResult';
errors?: Maybe<Array<ApiError>>; errors?: Maybe<Array<ApiError>>;
@ -268,6 +364,18 @@ export interface Mutation {
__typename?: 'Mutation'; __typename?: 'Mutation';
/** Authentication: By zbx_session - cookie or zabbix-auth-token - header */ /** Authentication: By zbx_session - cookie or zabbix-auth-token - header */
createHost?: Maybe<CreateHostResponse>; createHost?: Maybe<CreateHostResponse>;
/**
* Delete template groups.
*
* Authentication: By zbx_session - cookie or zabbix-auth-token - header
*/
deleteTemplateGroups?: Maybe<Array<DeleteResponse>>;
/**
* Delete templates.
*
* Authentication: By zbx_session - cookie or zabbix-auth-token - header
*/
deleteTemplates?: Maybe<Array<DeleteResponse>>;
/** /**
* (Mass) Import zabbix groups * (Mass) Import zabbix groups
* and assign them to the corresponding hosts by groupid or groupName. * and assign them to the corresponding hosts by groupid or groupName.
@ -287,6 +395,25 @@ export interface Mutation {
* Authentication: By zbx_session - cookie or zabbix-auth-token - header * Authentication: By zbx_session - cookie or zabbix-auth-token - header
*/ */
importHosts?: Maybe<Array<ImportHostResponse>>; importHosts?: Maybe<Array<ImportHostResponse>>;
/**
* (Mass) Import template groups
* and assign them by groupid or name.
*
* Return value: If no error occurs a groupid be returned for each created group,
* otherwise the return object will contain an error message
*
* Authentication: By zbx_session - cookie or zabbix-auth-token - header
*/
importTemplateGroups?: Maybe<Array<CreateTemplateGroupResponse>>;
/**
* (Mass) Import templates.
*
* Return value: If no error occurs a templateid will be returned for each created template,
* otherwise the return object will contain an error message.
*
* Authentication: By zbx_session - cookie or zabbix-auth-token - header
*/
importTemplates?: Maybe<Array<ImportTemplateResponse>>;
importUserRights?: Maybe<ImportUserRightsResult>; importUserRights?: Maybe<ImportUserRightsResult>;
} }
@ -299,6 +426,18 @@ export interface MutationCreateHostArgs {
} }
export interface MutationDeleteTemplateGroupsArgs {
groupids?: InputMaybe<Array<Scalars['Int']['input']>>;
name_pattern?: InputMaybe<Scalars['String']['input']>;
}
export interface MutationDeleteTemplatesArgs {
name_pattern?: InputMaybe<Scalars['String']['input']>;
templateids?: InputMaybe<Array<Scalars['Int']['input']>>;
}
export interface MutationImportHostGroupsArgs { export interface MutationImportHostGroupsArgs {
hostGroups: Array<CreateHostGroup>; hostGroups: Array<CreateHostGroup>;
} }
@ -309,6 +448,16 @@ export interface MutationImportHostsArgs {
} }
export interface MutationImportTemplateGroupsArgs {
templateGroups: Array<CreateTemplateGroup>;
}
export interface MutationImportTemplatesArgs {
templates: Array<CreateTemplate>;
}
export interface MutationImportUserRightsArgs { export interface MutationImportUserRightsArgs {
dryRun?: Scalars['Boolean']['input']; dryRun?: Scalars['Boolean']['input'];
input: UserRightsInput; input: UserRightsInput;
@ -359,6 +508,8 @@ export interface Query {
* Authentication: By zbx_session - cookie or zabbix-auth-token - header * Authentication: By zbx_session - cookie or zabbix-auth-token - header
*/ */
allHosts?: Maybe<Array<Maybe<Host>>>; allHosts?: Maybe<Array<Maybe<Host>>>;
/** Get template groups. */
allTemplateGroups?: Maybe<Array<Maybe<HostGroup>>>;
/** Get api (build) version */ /** Get api (build) version */
apiVersion: Scalars['String']['output']; apiVersion: Scalars['String']['output'];
/** /**
@ -401,6 +552,8 @@ export interface Query {
* operation. Returns true on success * operation. Returns true on success
*/ */
logout?: Maybe<Scalars['Boolean']['output']>; logout?: Maybe<Scalars['Boolean']['output']>;
/** Get templates. */
templates?: Maybe<Array<Maybe<Template>>>;
/** /**
* Return all user permissions. If objectNames is provided return only the permissions related to the objects within * Return all user permissions. If objectNames is provided return only the permissions related to the objects within
* the objectNames - list * the objectNames - list
@ -439,6 +592,11 @@ export interface QueryAllHostsArgs {
} }
export interface QueryAllTemplateGroupsArgs {
name_pattern?: InputMaybe<Scalars['String']['input']>;
}
export interface QueryExportHostValueHistoryArgs { export interface QueryExportHostValueHistoryArgs {
host_filter?: InputMaybe<Array<Scalars['String']['input']>>; host_filter?: InputMaybe<Array<Scalars['String']['input']>>;
itemKey_filter?: InputMaybe<Array<Scalars['String']['input']>>; itemKey_filter?: InputMaybe<Array<Scalars['String']['input']>>;
@ -474,6 +632,12 @@ export interface QueryLoginArgs {
} }
export interface QueryTemplatesArgs {
hostids?: InputMaybe<Array<InputMaybe<Scalars['Int']['input']>>>;
name_pattern?: InputMaybe<Scalars['String']['input']>;
}
export interface QueryUserPermissionsArgs { export interface QueryUserPermissionsArgs {
objectNames?: InputMaybe<Array<Scalars['String']['input']>>; objectNames?: InputMaybe<Array<Scalars['String']['input']>>;
} }
@ -749,7 +913,16 @@ export type ResolversTypes = {
CreateHostGroup: CreateHostGroup; CreateHostGroup: CreateHostGroup;
CreateHostGroupResponse: ResolverTypeWrapper<CreateHostGroupResponse>; CreateHostGroupResponse: ResolverTypeWrapper<CreateHostGroupResponse>;
CreateHostResponse: ResolverTypeWrapper<CreateHostResponse>; CreateHostResponse: ResolverTypeWrapper<CreateHostResponse>;
CreateItemPreprocessing: CreateItemPreprocessing;
CreateLinkedTemplate: CreateLinkedTemplate;
CreateMasterItem: CreateMasterItem;
CreateTag: CreateTag;
CreateTemplate: CreateTemplate;
CreateTemplateGroup: CreateTemplateGroup;
CreateTemplateGroupResponse: ResolverTypeWrapper<CreateTemplateGroupResponse>;
CreateTemplateItem: CreateTemplateItem;
DateTime: ResolverTypeWrapper<Scalars['DateTime']['output']>; DateTime: ResolverTypeWrapper<Scalars['DateTime']['output']>;
DeleteResponse: ResolverTypeWrapper<DeleteResponse>;
Device: ResolverTypeWrapper<ResolversInterfaceTypes<ResolversTypes>['Device']>; Device: ResolverTypeWrapper<ResolversInterfaceTypes<ResolversTypes>['Device']>;
DeviceCommunicationType: DeviceCommunicationType; DeviceCommunicationType: DeviceCommunicationType;
DeviceConfig: ResolverTypeWrapper<DeviceConfig>; DeviceConfig: ResolverTypeWrapper<DeviceConfig>;
@ -769,6 +942,7 @@ export type ResolversTypes = {
HostGroup: ResolverTypeWrapper<HostGroup>; HostGroup: ResolverTypeWrapper<HostGroup>;
ID: ResolverTypeWrapper<Scalars['ID']['output']>; ID: ResolverTypeWrapper<Scalars['ID']['output']>;
ImportHostResponse: ResolverTypeWrapper<ImportHostResponse>; ImportHostResponse: ResolverTypeWrapper<ImportHostResponse>;
ImportTemplateResponse: ResolverTypeWrapper<ImportTemplateResponse>;
ImportUserRightResult: ResolverTypeWrapper<ImportUserRightResult>; ImportUserRightResult: ResolverTypeWrapper<ImportUserRightResult>;
ImportUserRightsResult: ResolverTypeWrapper<ImportUserRightsResult>; ImportUserRightsResult: ResolverTypeWrapper<ImportUserRightsResult>;
Int: ResolverTypeWrapper<Scalars['Int']['output']>; Int: ResolverTypeWrapper<Scalars['Int']['output']>;
@ -814,7 +988,16 @@ export type ResolversParentTypes = {
CreateHostGroup: CreateHostGroup; CreateHostGroup: CreateHostGroup;
CreateHostGroupResponse: CreateHostGroupResponse; CreateHostGroupResponse: CreateHostGroupResponse;
CreateHostResponse: CreateHostResponse; CreateHostResponse: CreateHostResponse;
CreateItemPreprocessing: CreateItemPreprocessing;
CreateLinkedTemplate: CreateLinkedTemplate;
CreateMasterItem: CreateMasterItem;
CreateTag: CreateTag;
CreateTemplate: CreateTemplate;
CreateTemplateGroup: CreateTemplateGroup;
CreateTemplateGroupResponse: CreateTemplateGroupResponse;
CreateTemplateItem: CreateTemplateItem;
DateTime: Scalars['DateTime']['output']; DateTime: Scalars['DateTime']['output'];
DeleteResponse: DeleteResponse;
Device: ResolversInterfaceTypes<ResolversParentTypes>['Device']; Device: ResolversInterfaceTypes<ResolversParentTypes>['Device'];
DeviceConfig: DeviceConfig; DeviceConfig: DeviceConfig;
DeviceState: ResolversInterfaceTypes<ResolversParentTypes>['DeviceState']; DeviceState: ResolversInterfaceTypes<ResolversParentTypes>['DeviceState'];
@ -832,6 +1015,7 @@ export type ResolversParentTypes = {
HostGroup: HostGroup; HostGroup: HostGroup;
ID: Scalars['ID']['output']; ID: Scalars['ID']['output'];
ImportHostResponse: ImportHostResponse; ImportHostResponse: ImportHostResponse;
ImportTemplateResponse: ImportTemplateResponse;
ImportUserRightResult: ImportUserRightResult; ImportUserRightResult: ImportUserRightResult;
ImportUserRightsResult: ImportUserRightsResult; ImportUserRightsResult: ImportUserRightsResult;
Int: Scalars['Int']['output']; Int: Scalars['Int']['output'];
@ -890,10 +1074,25 @@ export type CreateHostResponseResolvers<ContextType = any, ParentType extends Re
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
}; };
export type CreateTemplateGroupResponseResolvers<ContextType = any, ParentType extends ResolversParentTypes['CreateTemplateGroupResponse'] = ResolversParentTypes['CreateTemplateGroupResponse']> = {
error?: Resolver<Maybe<ResolversTypes['ApiError']>, ParentType, ContextType>;
groupName?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
groupid?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
message?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export interface DateTimeScalarConfig extends GraphQLScalarTypeConfig<ResolversTypes['DateTime'], any> { export interface DateTimeScalarConfig extends GraphQLScalarTypeConfig<ResolversTypes['DateTime'], any> {
name: 'DateTime'; name: 'DateTime';
} }
export type DeleteResponseResolvers<ContextType = any, ParentType extends ResolversParentTypes['DeleteResponse'] = ResolversParentTypes['DeleteResponse']> = {
error?: Resolver<Maybe<ResolversTypes['ApiError']>, ParentType, ContextType>;
id?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
message?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type DeviceResolvers<ContextType = any, ParentType extends ResolversParentTypes['Device'] = ResolversParentTypes['Device']> = { export type DeviceResolvers<ContextType = any, ParentType extends ResolversParentTypes['Device'] = ResolversParentTypes['Device']> = {
__resolveType: TypeResolveFn<'GenericDevice', ParentType, ContextType>; __resolveType: TypeResolveFn<'GenericDevice', ParentType, ContextType>;
deviceType?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>; deviceType?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
@ -1011,6 +1210,14 @@ export type ImportHostResponseResolvers<ContextType = any, ParentType extends Re
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
}; };
export type ImportTemplateResponseResolvers<ContextType = any, ParentType extends ResolversParentTypes['ImportTemplateResponse'] = ResolversParentTypes['ImportTemplateResponse']> = {
error?: Resolver<Maybe<ResolversTypes['ApiError']>, ParentType, ContextType>;
host?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
message?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
templateid?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type ImportUserRightResultResolvers<ContextType = any, ParentType extends ResolversParentTypes['ImportUserRightResult'] = ResolversParentTypes['ImportUserRightResult']> = { export type ImportUserRightResultResolvers<ContextType = any, ParentType extends ResolversParentTypes['ImportUserRightResult'] = ResolversParentTypes['ImportUserRightResult']> = {
errors?: Resolver<Maybe<Array<ResolversTypes['ApiError']>>, ParentType, ContextType>; errors?: Resolver<Maybe<Array<ResolversTypes['ApiError']>>, ParentType, ContextType>;
id?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>; id?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
@ -1043,8 +1250,12 @@ export type LocationResolvers<ContextType = any, ParentType extends ResolversPar
export type MutationResolvers<ContextType = any, ParentType extends ResolversParentTypes['Mutation'] = ResolversParentTypes['Mutation']> = { 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' | 'templateids'>>;
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'>>; importHostGroups?: Resolver<Maybe<Array<ResolversTypes['CreateHostGroupResponse']>>, ParentType, ContextType, RequireFields<MutationImportHostGroupsArgs, 'hostGroups'>>;
importHosts?: Resolver<Maybe<Array<ResolversTypes['ImportHostResponse']>>, ParentType, ContextType, RequireFields<MutationImportHostsArgs, 'hosts'>>; importHosts?: Resolver<Maybe<Array<ResolversTypes['ImportHostResponse']>>, ParentType, ContextType, RequireFields<MutationImportHostsArgs, 'hosts'>>;
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'>>; importUserRights?: Resolver<Maybe<ResolversTypes['ImportUserRightsResult']>, ParentType, ContextType, RequireFields<MutationImportUserRightsArgs, 'dryRun' | 'input'>>;
}; };
@ -1064,6 +1275,7 @@ export type QueryResolvers<ContextType = any, ParentType extends ResolversParent
allDevices?: Resolver<Maybe<Array<Maybe<ResolversTypes['Device']>>>, ParentType, ContextType, RequireFields<QueryAllDevicesArgs, 'filter_host' | 'groupids' | 'name_pattern' | 'tag_deviceType' | 'with_items'>>; allDevices?: Resolver<Maybe<Array<Maybe<ResolversTypes['Device']>>>, ParentType, ContextType, RequireFields<QueryAllDevicesArgs, 'filter_host' | 'groupids' | 'name_pattern' | 'tag_deviceType' | 'with_items'>>;
allHostGroups?: Resolver<Maybe<Array<Maybe<ResolversTypes['HostGroup']>>>, ParentType, ContextType, RequireFields<QueryAllHostGroupsArgs, 'with_hosts'>>; allHostGroups?: Resolver<Maybe<Array<Maybe<ResolversTypes['HostGroup']>>>, ParentType, ContextType, RequireFields<QueryAllHostGroupsArgs, 'with_hosts'>>;
allHosts?: Resolver<Maybe<Array<Maybe<ResolversTypes['Host']>>>, ParentType, ContextType, RequireFields<QueryAllHostsArgs, 'filter_host' | 'groupids' | 'name_pattern' | 'tag_deviceType' | 'with_items'>>; allHosts?: Resolver<Maybe<Array<Maybe<ResolversTypes['Host']>>>, ParentType, ContextType, RequireFields<QueryAllHostsArgs, 'filter_host' | 'groupids' | 'name_pattern' | 'tag_deviceType' | 'with_items'>>;
allTemplateGroups?: Resolver<Maybe<Array<Maybe<ResolversTypes['HostGroup']>>>, ParentType, ContextType, Partial<QueryAllTemplateGroupsArgs>>;
apiVersion?: Resolver<ResolversTypes['String'], ParentType, ContextType>; apiVersion?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
exportHostValueHistory?: Resolver<Maybe<ResolversTypes['GenericResponse']>, ParentType, ContextType, RequireFields<QueryExportHostValueHistoryArgs, 'sortOrder' | 'type'>>; exportHostValueHistory?: Resolver<Maybe<ResolversTypes['GenericResponse']>, ParentType, ContextType, RequireFields<QueryExportHostValueHistoryArgs, 'sortOrder' | 'type'>>;
exportUserRights?: Resolver<Maybe<ResolversTypes['UserRights']>, ParentType, ContextType, RequireFields<QueryExportUserRightsArgs, 'exclude_hostgroups_pattern' | 'name_pattern'>>; exportUserRights?: Resolver<Maybe<ResolversTypes['UserRights']>, ParentType, ContextType, RequireFields<QueryExportUserRightsArgs, 'exclude_hostgroups_pattern' | 'name_pattern'>>;
@ -1071,6 +1283,7 @@ export type QueryResolvers<ContextType = any, ParentType extends ResolversParent
locations?: Resolver<Maybe<Array<Maybe<ResolversTypes['Location']>>>, ParentType, ContextType, RequireFields<QueryLocationsArgs, 'distinct_by_name' | 'name_pattern' | 'templateids'>>; locations?: Resolver<Maybe<Array<Maybe<ResolversTypes['Location']>>>, ParentType, ContextType, RequireFields<QueryLocationsArgs, 'distinct_by_name' | 'name_pattern' | 'templateids'>>;
login?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType, RequireFields<QueryLoginArgs, 'password' | 'username'>>; login?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType, RequireFields<QueryLoginArgs, 'password' | 'username'>>;
logout?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>; logout?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>;
templates?: Resolver<Maybe<Array<Maybe<ResolversTypes['Template']>>>, ParentType, ContextType, Partial<QueryTemplatesArgs>>;
userPermissions?: Resolver<Maybe<Array<ResolversTypes['UserPermission']>>, ParentType, ContextType, Partial<QueryUserPermissionsArgs>>; userPermissions?: Resolver<Maybe<Array<ResolversTypes['UserPermission']>>, ParentType, ContextType, Partial<QueryUserPermissionsArgs>>;
zabbixVersion?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>; zabbixVersion?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
}; };
@ -1193,7 +1406,9 @@ export type Resolvers<ContextType = any> = {
ApiError?: ApiErrorResolvers<ContextType>; ApiError?: ApiErrorResolvers<ContextType>;
CreateHostGroupResponse?: CreateHostGroupResponseResolvers<ContextType>; CreateHostGroupResponse?: CreateHostGroupResponseResolvers<ContextType>;
CreateHostResponse?: CreateHostResponseResolvers<ContextType>; CreateHostResponse?: CreateHostResponseResolvers<ContextType>;
CreateTemplateGroupResponse?: CreateTemplateGroupResponseResolvers<ContextType>;
DateTime?: GraphQLScalarType; DateTime?: GraphQLScalarType;
DeleteResponse?: DeleteResponseResolvers<ContextType>;
Device?: DeviceResolvers<ContextType>; Device?: DeviceResolvers<ContextType>;
DeviceCommunicationType?: DeviceCommunicationTypeResolvers; DeviceCommunicationType?: DeviceCommunicationTypeResolvers;
DeviceConfig?: DeviceConfigResolvers<ContextType>; DeviceConfig?: DeviceConfigResolvers<ContextType>;
@ -1211,6 +1426,7 @@ export type Resolvers<ContextType = any> = {
Host?: HostResolvers<ContextType>; Host?: HostResolvers<ContextType>;
HostGroup?: HostGroupResolvers<ContextType>; HostGroup?: HostGroupResolvers<ContextType>;
ImportHostResponse?: ImportHostResponseResolvers<ContextType>; ImportHostResponse?: ImportHostResponseResolvers<ContextType>;
ImportTemplateResponse?: ImportTemplateResponseResolvers<ContextType>;
ImportUserRightResult?: ImportUserRightResultResolvers<ContextType>; ImportUserRightResult?: ImportUserRightResultResolvers<ContextType>;
ImportUserRightsResult?: ImportUserRightsResultResolvers<ContextType>; ImportUserRightsResult?: ImportUserRightsResultResolvers<ContextType>;
Inventory?: InventoryResolvers<ContextType>; Inventory?: InventoryResolvers<ContextType>;

View file

@ -0,0 +1,162 @@
import {TemplateDeleter} from "../execution/template_deleter.js";
import {zabbixAPI} from "../datasources/zabbix-api.js";
// Mocking ZabbixAPI
jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
executeRequest: jest.fn(),
post: jest.fn()
}
}));
describe("TemplateDeleter", () => {
beforeEach(() => {
jest.clearAllMocks();
});
test("deleteTemplates - success", async () => {
const templateids = [1, 2];
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ templateids: ["1", "2"] });
const result = await TemplateDeleter.deleteTemplates(templateids, null, "token");
expect(result).toHaveLength(2);
expect(result[0].id).toBe(1);
expect(result[0].message).toContain("deleted successfully");
expect(result[1].id).toBe(2);
expect(zabbixAPI.post).toHaveBeenCalledWith("template.delete", expect.objectContaining({
body: expect.objectContaining({
params: [1, 2]
})
}));
});
test("deleteTemplates - error", async () => {
const templateids = [1];
const zabbixError = {
error: {
code: -32602,
message: "Invalid params.",
data: "Template does not exist"
}
};
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce(zabbixError);
const result = await TemplateDeleter.deleteTemplates(templateids, null, "token");
expect(result).toHaveLength(1);
expect(result[0].error).toBeDefined();
expect(result[0].message).toContain("Invalid params.");
expect(result[0].message).toContain("Template does not exist");
});
test("deleteTemplates - by name_pattern", async () => {
// Mock template.get
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce([
{ templateid: "10", host: "PatternTemplate 1" },
{ templateid: "11", host: "PatternTemplate 2" }
]);
// Mock template.delete
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ templateids: ["10", "11"] });
const result = await TemplateDeleter.deleteTemplates(null, "PatternTemplate%", "token");
expect(result).toHaveLength(2);
expect(result.map(r => r.id)).toContain(10);
expect(result.map(r => r.id)).toContain(11);
expect(zabbixAPI.post).toHaveBeenCalledWith("template.get", expect.objectContaining({
body: expect.objectContaining({
params: expect.objectContaining({
search: { name: "PatternTemplate%" }
})
})
}));
expect(zabbixAPI.post).toHaveBeenCalledWith("template.delete", expect.objectContaining({
body: expect.objectContaining({
params: expect.arrayContaining([10, 11])
})
}));
});
test("deleteTemplates - merged IDs and name_pattern", async () => {
// Mock template.get
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce([
{ templateid: "10", host: "PatternTemplate 1" },
{ templateid: "12", host: "PatternTemplate 3" }
]);
// Mock template.delete
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ templateids: ["10", "11", "12"] });
const result = await TemplateDeleter.deleteTemplates([11], "PatternTemplate%", "token");
expect(result).toHaveLength(3);
expect(result.map(r => r.id)).toContain(10);
expect(result.map(r => r.id)).toContain(11);
expect(result.map(r => r.id)).toContain(12);
expect(zabbixAPI.post).toHaveBeenCalledWith("template.delete", expect.objectContaining({
body: expect.objectContaining({
params: expect.arrayContaining([10, 11, 12])
})
}));
});
test("deleteTemplateGroups - success", async () => {
const groupids = [101];
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ groupids: ["101"] });
const result = await TemplateDeleter.deleteTemplateGroups(groupids, null, "token");
expect(result).toHaveLength(1);
expect(result[0].id).toBe(101);
expect(result[0].message).toContain("deleted successfully");
});
test("deleteTemplateGroups - error", async () => {
const groupids = [101];
const zabbixError = {
error: {
code: -32602,
message: "Invalid params.",
data: "Group is in use"
}
};
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce(zabbixError);
const result = await TemplateDeleter.deleteTemplateGroups(groupids, null, "token");
expect(result).toHaveLength(1);
expect(result[0].error).toBeDefined();
expect(result[0].message).toContain("Group is in use");
});
test("deleteTemplateGroups - by name_pattern", async () => {
// Mock templategroup.get
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce([
{ groupid: "201", name: "PatternGroup 1" }
]);
// Mock templategroup.delete
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ groupids: ["201"] });
const result = await TemplateDeleter.deleteTemplateGroups(null, "PatternGroup%", "token");
expect(result).toHaveLength(1);
expect(result[0].id).toBe(201);
expect(zabbixAPI.post).toHaveBeenCalledWith("templategroup.get", expect.objectContaining({
body: expect.objectContaining({
params: expect.objectContaining({
search: { name: "PatternGroup%" }
})
})
}));
expect(zabbixAPI.post).toHaveBeenCalledWith("templategroup.delete", expect.objectContaining({
body: expect.objectContaining({
params: [201]
})
}));
});
});

View file

@ -0,0 +1,176 @@
import {TemplateImporter} from "../execution/template_importer.js";
import {zabbixAPI} from "../datasources/zabbix-api.js";
import {
ZabbixCreateItemRequest,
ZabbixCreateTemplateGroupRequest,
ZabbixCreateTemplateRequest,
ZabbixQueryItemRequest,
ZabbixQueryTemplateGroupRequest,
ZabbixQueryTemplatesRequest
} from "../datasources/zabbix-templates.js";
import {ZabbixErrorResult} from "../datasources/zabbix-request.js";
// Mocking ZabbixAPI.executeRequest
jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
executeRequest: jest.fn(),
post: jest.fn()
}
}));
describe("TemplateImporter", () => {
beforeEach(() => {
jest.clearAllMocks();
});
test("importTemplateGroups - create new group", async () => {
const templateGroups = [{ groupName: "New Group", uuid: "uuid1" }];
// Mocking group.get to return empty (group doesn't exist)
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce([]);
// Mocking group.create
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ groupids: ["101"] });
const result = await TemplateImporter.importTemplateGroups(templateGroups, "token");
expect(result).toHaveLength(1);
expect(result![0].groupid).toBe(101);
expect(result![0].groupName).toBe("New Group");
});
test("importTemplateGroups - group already exists", async () => {
const templateGroups = [{ groupName: "Existing Group" }];
// Mocking group.get to return existing group
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce([{ groupid: "102", name: "Existing Group" }]);
const result = await TemplateImporter.importTemplateGroups(templateGroups, "token");
expect(result).toHaveLength(1);
expect(result![0].groupid).toBe(102);
expect(result![0].message).toContain("already exists");
});
test("importTemplates - basic template", async () => {
const templates = [{
host: "Test Template",
groupNames: ["Group1"]
}];
// Mocking group.get for Group1
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce([{ groupid: "201", name: "Group1" }]);
// Mocking template.create
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ templateids: ["301"] });
const result = await TemplateImporter.importTemplates(templates, "token");
expect(result).toHaveLength(1);
expect(result![0].templateid).toBe("301");
expect(result![0].host).toBe("Test Template");
});
test("importTemplates - with items, linked templates and dependent items", async () => {
const templates = [{
host: "Complex Template",
groupNames: ["Group1"],
templates: [{ name: "Linked Template" }],
items: [
{
name: "Dependent Item",
key: "dependent.key",
type: 18,
value_type: 3,
master_item: { key: "master.key" }
},
{
name: "Master Item",
key: "master.key",
type: 0,
value_type: 3,
}
]
}];
// Mocking group.get
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce([{ groupid: "201", name: "Group1" }]);
// Mocking template.get for linked template
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce([{ templateid: "401", host: "Linked Template" }]);
// Mocking template.create
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ templateids: ["501"] });
// Mocking item.create for Master Item (first pass will pick Master Item because Dependent Item is missing its master)
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ itemids: ["601"] });
// Mocking item.create for Dependent Item (second pass)
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ itemids: ["602"] });
const result = await TemplateImporter.importTemplates(templates, "token");
expect(result).toHaveLength(1);
expect(result![0].templateid).toBe("501");
// Check template.create params
const templateCreateCall = (zabbixAPI.post as jest.Mock).mock.calls.find(call => call[1].body.method === "template.create");
expect(templateCreateCall[1].body.params.templates).toContainEqual({ templateid: "401" });
// Check item.create calls
const itemCreateCalls = (zabbixAPI.post as jest.Mock).mock.calls.filter(call => call[1].body.method === "item.create");
expect(itemCreateCalls).toHaveLength(2);
const masterCall = itemCreateCalls.find(c => c[1].body.params.name === "Master Item");
const dependentCall = itemCreateCalls.find(c => c[1].body.params.name === "Dependent Item");
expect(masterCall[1].body.params.key_).toBe("master.key");
expect(dependentCall[1].body.params.key_).toBe("dependent.key");
expect(dependentCall[1].body.params.master_itemid).toBe("601");
});
test("importTemplates - template query", async () => {
// This tests the template.get functionality used during import
const templates = [{
host: "Template A",
groupNames: ["Group1"],
templates: [{ name: "Template B" }]
}];
// Mock Group
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce([{ groupid: "1", name: "Group1" }]);
// Mock Template B lookup
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce([{ templateid: "2", host: "Template B" }]);
// Mock Template A creation
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ templateids: ["3"] });
await TemplateImporter.importTemplates(templates, "token");
// Verify that template.get was called for Template B
const templateQueryCall = (zabbixAPI.post as jest.Mock).mock.calls.find(call => call[1].body.method === "template.get");
expect(templateQueryCall).toBeDefined();
expect(templateQueryCall[1].body.params.filter.host).toContain("Template B");
});
test("importTemplates - error message includes data field", async () => {
const templates = [{
host: "Error Template",
groupNames: ["Group1"]
}];
// Mocking group.get
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce([{ groupid: "201", name: "Group1" }]);
// Mocking template.create with an error including data
const zabbixError = {
error: {
code: -32602,
message: "Invalid params.",
data: "Invalid parameter \"/1\": the parameter \"key_\" is missing."
}
};
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce(zabbixError);
const result = await TemplateImporter.importTemplates(templates, "token");
expect(result).toHaveLength(1);
expect(result![0].message).toContain("Invalid params.");
expect(result![0].message).toContain("the parameter \"key_\" is missing.");
});
});

View file

@ -0,0 +1,229 @@
import { ApolloServer } from '@apollo/server';
import { schema_loader } from '../api/schema.js';
import { readFileSync } from 'fs';
import { join } from 'path';
import { zabbixAPI } from '../datasources/zabbix-api.js';
// Mocking ZabbixAPI.post
jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
post: jest.fn(),
executeRequest: jest.fn(),
baseURL: 'http://localhost/zabbix'
}
}));
describe("Template Integration Tests", () => {
let server: ApolloServer;
beforeAll(async () => {
const schema = await schema_loader();
server = new ApolloServer({
schema,
});
});
test("Import templates using sample query and variables", async () => {
const sampleFile = readFileSync(join(process.cwd(), 'docs', 'sample_import_templates_mutation.graphql'), 'utf-8').replace(/\r\n/g, '\n');
// Extract mutation and variables from the doc file
const mutationMatch = sampleFile.match(/```graphql\n([\s\S]*?)\n```/);
const variablesMatch = sampleFile.match(/```json\n([\s\S]*?)\n```/);
const mutation = mutationMatch![1];
const variables = JSON.parse(variablesMatch![1]);
// Mock Zabbix API calls
// 1. Group lookup
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce([{ groupid: "201", name: "Templates/Roadwork/Devices" }]);
// 2. Linked template lookup
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce([{ templateid: "401", host: "ROADWORK_DEVICE" }]);
// 3. Template creation
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ templateids: ["501"] });
// 4. Item creation (location)
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ itemids: ["601"] });
// 5. Item creation (MQTT_LOCATION)
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ itemids: ["602"] });
const response = await server.executeOperation({
query: mutation,
variables: variables,
}, {
contextValue: {
zabbixAuthToken: 'test-token'
}
});
expect(response.body.kind).toBe('single');
// @ts-ignore
const result = response.body.singleResult;
expect(result.errors).toBeUndefined();
expect(result.data.importTemplates).toHaveLength(1);
expect(result.data.importTemplates[0].host).toBe("BT_DEVICE_TRACKER");
expect(result.data.importTemplates[0].templateid).toBe("501");
});
test("Import and Export templates comparison", async () => {
// 1. Import
const importSample = readFileSync(join(process.cwd(), 'docs', 'sample_import_templates_mutation.graphql'), 'utf-8').replace(/\r\n/g, '\n');
const importMutation = importSample.match(/```graphql\n([\s\S]*?)\n```/)![1];
const importVariables = JSON.parse(importSample.match(/```json\n([\s\S]*?)\n```/)![1]);
// Mock for import
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce([{ groupid: "201", name: "Templates/Roadwork/Devices" }]);
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce([{ templateid: "401", host: "ROADWORK_DEVICE" }]);
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ templateids: ["501"] });
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ itemids: ["601"] });
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ itemids: ["602"] });
await server.executeOperation({
query: importMutation,
variables: importVariables,
}, {
contextValue: { zabbixAuthToken: 'test-token' }
});
// 2. Export (Query)
const querySample = readFileSync(join(process.cwd(), 'docs', 'sample_templates_query.graphql'), 'utf-8').replace(/\r\n/g, '\n');
const query = querySample.match(/```graphql\n([\s\S]*?)\n```/)![1];
const queryVariables = JSON.parse(querySample.match(/```json\n([\s\S]*?)\n```/)![1]);
// Mock for query
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce([{ templateid: "501", host: "BT_DEVICE_TRACKER", name: "BT_DEVICE_TRACKER" }]);
const queryResponse = await server.executeOperation({
query: query,
variables: queryVariables,
}, {
contextValue: { zabbixAuthToken: 'test-token' }
});
expect(queryResponse.body.kind).toBe('single');
// @ts-ignore
const queryResult = queryResponse.body.singleResult;
expect(queryResult.errors).toBeUndefined();
expect(queryResult.data.templates).toHaveLength(1);
expect(queryResult.data.templates[0].name).toBe(importVariables.templates[0].name);
expect(queryResult.data.templates[0].templateid).toBe("501");
// 3. Delete
const deleteMutation = `
mutation DeleteTemplates($templateids: [Int!], $name_pattern: String) {
deleteTemplates(templateids: $templateids, name_pattern: $name_pattern) {
id
message
}
}
`;
// Mock for query (to find ID for name_pattern deletion)
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce([{ templateid: "501", host: "BT_DEVICE_TRACKER" }]);
// Mock for delete
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ templateids: ["501"] });
const deleteResponse = await server.executeOperation({
query: deleteMutation,
variables: { name_pattern: "BT_DEVICE_TRACKER" },
}, {
contextValue: { zabbixAuthToken: 'test-token' }
});
expect(deleteResponse.body.kind).toBe('single');
// @ts-ignore
const deleteResult = deleteResponse.body.singleResult;
expect(deleteResult.errors).toBeUndefined();
expect(deleteResult.data.deleteTemplates).toHaveLength(1);
expect(deleteResult.data.deleteTemplates[0].message).toContain("deleted successfully");
});
test("Import and Export template groups comparison", async () => {
// 1. Import
const importSample = readFileSync(join(process.cwd(), 'docs', 'sample_import_template_groups_mutation.graphql'), 'utf-8').replace(/\r\n/g, '\n');
const importMutation = importSample.match(/```graphql\n([\s\S]*?)\n```/)![1];
const importVariables = JSON.parse(importSample.match(/```json\n([\s\S]*?)\n```/)![1]);
// Mock for import (8 groups in sample)
for (const group of importVariables.templateGroups) {
// Mock lookup (not found)
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce([]);
// Mock creation
const mockGroupId = Math.floor(Math.random() * 1000).toString();
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ groupids: [mockGroupId] });
}
const importResponse = await server.executeOperation({
query: importMutation,
variables: importVariables,
}, {
contextValue: { zabbixAuthToken: 'test-token' }
});
expect(importResponse.body.kind).toBe('single');
// @ts-ignore
const importResult = importResponse.body.singleResult;
expect(importResult.errors).toBeUndefined();
expect(importResult.data.importTemplateGroups).toHaveLength(importVariables.templateGroups.length);
// 2. Export (Query)
const querySample = readFileSync(join(process.cwd(), 'docs', 'sample_all_template_groups_query.graphql'), 'utf-8').replace(/\r\n/g, '\n');
const query = querySample.match(/```graphql\n([\s\S]*?)\n```/)![1];
const queryVariables = JSON.parse(querySample.match(/```json\n([\s\S]*?)\n```/)![1]);
// Mock for query
const mockGroupsResponse = importVariables.templateGroups.map((g: any, index: number) => ({
groupid: (index + 1000).toString(),
name: g.groupName
}));
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce(mockGroupsResponse);
const queryResponse = await server.executeOperation({
query: query,
variables: queryVariables,
}, {
contextValue: { zabbixAuthToken: 'test-token' }
});
expect(queryResponse.body.kind).toBe('single');
// @ts-ignore
const queryResult = queryResponse.body.singleResult;
expect(queryResult.errors).toBeUndefined();
expect(queryResult.data.allTemplateGroups).toHaveLength(importVariables.templateGroups.length);
// Verify names match
const importedNames = importVariables.templateGroups.map((g: any) => g.groupName).sort();
const exportedNames = queryResult.data.allTemplateGroups.map((g: any) => g.name).sort();
expect(exportedNames).toEqual(importedNames);
// 3. Delete Template Groups
const groupidsToDelete = queryResult.data.allTemplateGroups.map((g: any) => parseInt(g.groupid));
// Mock for query (for name_pattern)
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce(queryResult.data.allTemplateGroups.map((g: any) => ({ groupid: g.groupid, name: g.name })));
// Mock for delete
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ groupids: groupidsToDelete.map((id: number) => id.toString()) });
const deleteMutation = `
mutation DeleteTemplateGroups($groupids: [Int!], $name_pattern: String) {
deleteTemplateGroups(groupids: $groupids, name_pattern: $name_pattern) {
id
message
error {
message
}
}
}
`;
const deleteResponse = await server.executeOperation({
query: deleteMutation,
variables: { name_pattern: "Templates/Roadwork/%" },
}, {
contextValue: { zabbixAuthToken: 'test-token' }
});
expect(deleteResponse.body.kind).toBe('single');
// @ts-ignore
const deleteResult = deleteResponse.body.singleResult;
expect(deleteResult.errors).toBeUndefined();
expect(deleteResult.data.deleteTemplateGroups).toHaveLength(groupidsToDelete.length);
expect(deleteResult.data.deleteTemplateGroups[0].message).toContain("deleted successfully");
});
});

View file

@ -0,0 +1,112 @@
import {createResolvers} from "../api/resolvers.js";
import {zabbixAPI} from "../datasources/zabbix-api.js";
import {QueryTemplatesArgs} from "../schema/generated/graphql.js";
// Mocking ZabbixAPI
jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
executeRequest: jest.fn(),
post: jest.fn(),
baseURL: "http://mock-zabbix"
},
ZABBIX_EDGE_DEVICE_BASE_GROUP: "Baustellen-Devices"
}));
describe("Template Resolver", () => {
let resolvers: any;
beforeEach(() => {
jest.clearAllMocks();
resolvers = createResolvers();
});
test("templates query - returns all templates", async () => {
const mockTemplates = [
{ templateid: "1", name: "Template 1", uuid: "uuid1" },
{ templateid: "2", name: "Template 2", uuid: "uuid2" }
];
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce(mockTemplates);
const args: QueryTemplatesArgs = {};
const context = { zabbixAuthToken: "test-token" };
const result = await resolvers.Query.templates(null, args, context);
expect(result).toEqual(mockTemplates);
expect(zabbixAPI.post).toHaveBeenCalledWith("template.get", expect.objectContaining({
body: expect.objectContaining({
method: "template.get",
params: {}
})
}));
});
test("templates query - filters by hostids", async () => {
const mockTemplates = [{ templateid: "1", name: "Template 1" }];
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce(mockTemplates);
const args: QueryTemplatesArgs = { hostids: [1] };
const context = { zabbixAuthToken: "test-token" };
const result = await resolvers.Query.templates(null, args, context);
expect(result).toEqual(mockTemplates);
expect(zabbixAPI.post).toHaveBeenCalledWith("template.get", expect.objectContaining({
body: expect.objectContaining({
method: "template.get",
params: expect.objectContaining({
templateids: [1]
})
})
}));
});
test("templates query - filters by name_pattern", async () => {
const mockTemplates = [{ templateid: "1", name: "Template 1" }];
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce(mockTemplates);
const args: QueryTemplatesArgs = { name_pattern: "Template" };
const context = { zabbixAuthToken: "test-token" };
const result = await resolvers.Query.templates(null, args, context);
expect(result).toEqual(mockTemplates);
expect(zabbixAPI.post).toHaveBeenCalledWith("template.get", expect.objectContaining({
body: expect.objectContaining({
method: "template.get",
params: expect.objectContaining({
search: {
name: "Template"
}
})
})
}));
});
test("templates query - filters by name_pattern with % wildcard", async () => {
const mockTemplates = [{ templateid: "1", name: "Template 1" }];
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce(mockTemplates);
const args: QueryTemplatesArgs = { name_pattern: "Temp%1" };
const context = { zabbixAuthToken: "test-token" };
const result = await resolvers.Query.templates(null, args, context);
expect(result).toEqual(mockTemplates);
expect(zabbixAPI.post).toHaveBeenCalledWith("template.get", expect.objectContaining({
body: expect.objectContaining({
method: "template.get",
params: expect.objectContaining({
search: {
name: "Temp%1"
}
})
})
}));
});
});

View file

@ -0,0 +1,670 @@
zabbix_export:
version: '7.4'
template_groups:
- uuid: 43aab460fe444f18886b19948413b7e3
name: Permissions/ConstructionSite
- uuid: 376524057e094c07aaa0cf7f524849dc
name: Templates/Roadwork/Controller
- uuid: 7d83c76454564390bb0e34600780eaec
name: Templates/Roadwork/Device-Capabilities
- uuid: 48d5d2a18a08448c96a931b63bb2c97d
name: Templates/Roadwork/Device-Capabilities/FLASH_ATTACHABLE
- uuid: 785986b84892468ea2e92d912747b1d3
name: Templates/Roadwork/Device-Capabilities/GEOLOCALIZABLE
- uuid: a4b79479e97a4b48972dcb476d45e55a
name: Templates/Roadwork/Device-Capabilities/HAS_OPERATIONAL_DATA
- uuid: 3604af8102644bee9dcaf0f9c1ee93a1
name: Templates/Roadwork/Devices
- uuid: 5ad0bd9e42a4487e869e9e41b38fe553
name: Templates/Roadwork/DisplayLibrary
templates:
- uuid: 27474f627cb344b782a81c16d7e0c7d1
template: BT_DEVICE_TRACKER
name: BT_DEVICE_TRACKER
vendor:
name: 'Hilbig IT GmbH'
version: 2.1.1
templates:
- name: ROADWORK_DEVICE
groups:
- name: Templates/Roadwork/Devices
items:
- uuid: d4d3ec9f3ca940a39a721b6cfd2f3471
name: location
type: DEPENDENT
key: location
history: 2d
value_type: TEXT
preprocessing:
- type: JAVASCRIPT
parameters:
- |
var obj=JSON.parse(value);
if (obj["isFiltered"]) {
throw "Result is filtered";
return "filtered";
}
return value;
- type: NOT_MATCHES_REGEX
parameters:
- filtered
error_handler: DISCARD_VALUE
master_item:
key: 'mqtt.trap[deviceValue/location]'
- uuid: 380c4a7d752848cba3b5a59a0f9b13c0
name: MQTT_LOCATION
type: TRAP
key: 'mqtt.trap[deviceValue/location]'
history: '0'
value_type: TEXT
- uuid: 29faf53c033840c0b1405f8240e30312
name: coords
type: DEPENDENT
key: state.current.values.coords
history: 2d
value_type: TEXT
preprocessing:
- type: JAVASCRIPT
parameters:
- |
var obj=JSON.parse(value);
var location = obj["location"];
var coords = location["coords"];
return JSON.stringify({
"btDeviceKey": obj["btDeviceKey"],
"timestamp": location["timestamp"],
"deviceName": obj["deviceName"],
"latitude": coords[1],
"longitude": coords[0],
"coords": coords
});
master_item:
key: location
tags:
- tag: hasValue
value: 'true'
- uuid: 1ae9486c18394e56b114c9cb4546deaf
name: geojson
type: DEPENDENT
key: state.current.values.geojson
history: 2d
value_type: TEXT
preprocessing:
- type: JSONPATH
parameters:
- $.location.setup
master_item:
key: location
tags:
- tag: hasValue
value: 'true'
tags:
- tag: class
value: roadwork
- tag: deviceType
value: bt_device_tracker_generic
- uuid: e6905dc6122944f3829ad28a9739e269
template: BT_TRACKER
name: BT_TRACKER
vendor:
name: 'Hilbig IT GmbH'
version: 2.1.1
templates:
- name: ROADWORK_DEVICE
groups:
- name: Templates/Roadwork/Devices
items:
- uuid: b1e3062d67f94f7c8d064eff36a58b13
name: MQTT_STATE
type: DEPENDENT
key: currentstate
value_type: TEXT
preprocessing:
- type: JAVASCRIPT
parameters:
- |
var v = JSON.parse(value);
return JSON.stringify({
"count": v.count,
"timeFrom": v.timeFrom,
"timeUntil": v.timeUntil
});
master_item:
key: 'mqtt.trap[deviceValue/count]'
- uuid: 905c5f1b6e524bd2b227769a59f4df1b
name: MQTT_COUNT
type: TRAP
key: 'mqtt.trap[deviceValue/count]'
history: '0'
value_type: TEXT
- uuid: 6fa441872c3140f4adecf39956245603
name: MQTT_DISTANCE
type: TRAP
key: 'mqtt.trap[deviceValue/distance]'
value_type: TEXT
- uuid: 69d2afa4a0324d818150e9473c3264f3
name: MQTT_NAME
type: TRAP
key: 'mqtt.trap[deviceValue/name]'
value_type: TEXT
- uuid: 45ff9430d27f47a492c98fce03fc7962
name: MQTT_SERVICE_DATA
type: TRAP
key: 'mqtt.trap[deviceValue/ServiceData]'
value_type: TEXT
- uuid: 3bf0d3017ea54e1da2a764c3f96bf97e
name: count
type: DEPENDENT
key: state.current.values.count
trends: '0'
preprocessing:
- type: JSONPATH
parameters:
- $.count
master_item:
key: 'mqtt.trap[deviceValue/count]'
- uuid: f0d1fc72e2154613b349be86c6bdcfd6
name: timeFrom
type: DEPENDENT
key: state.current.values.timeFrom
value_type: TEXT
preprocessing:
- type: JSONPATH
parameters:
- $.timeFrom
- type: REGEX
parameters:
- 'T(\d\d:\d\d:\d\d):'
- \1
master_item:
key: 'mqtt.trap[deviceValue/count]'
- uuid: e55bf604808f4eb4a964ebeefdd9eb9e
name: timeUntil
type: DEPENDENT
key: state.current.values.timeUntil
value_type: TEXT
preprocessing:
- type: JSONPATH
parameters:
- $.timeUntil
- type: REGEX
parameters:
- 'T(\d\d:\d\d:\d\d):'
- \1
master_item:
key: 'mqtt.trap[deviceValue/count]'
tags:
- tag: class
value: roadwork
- tag: deviceType
value: bt_tracker_generic
- tag: deviceWidgetPreview.BOTTOM_LEFT.key
value: timeFrom
- tag: deviceWidgetPreview.BOTTOM_LEFT.unit
value: Startzeit
- tag: deviceWidgetPreview.BOTTOM_LEFT.unit_font_size
value: '8'
- tag: deviceWidgetPreview.BOTTOM_LEFT.value_font_size
value: '16'
- tag: deviceWidgetPreview.BOTTOM_RIGHT.key
value: timeUntil
- tag: deviceWidgetPreview.BOTTOM_RIGHT.unit
value: Endezeit
- tag: deviceWidgetPreview.BOTTOM_RIGHT.unit_font_size
value: '8'
- tag: deviceWidgetPreview.BOTTOM_RIGHT.value_font_size
value: '16'
- tag: deviceWidgetPreview.TOP_LEFT.key
value: count
- tag: deviceWidgetPreview.TOP_LEFT.unit
value: Geräte
- tag: deviceWidgetPreview.TOP_LEFT.unit_font_size
value: '8'
- tag: deviceWidgetPreview.TOP_LEFT.value_font_size
value: '24'
- uuid: 6490907a74964d0797c7acd1938bc553
template: GEOLOCATION
name: GEOLOCATION
vendor:
name: 'Hilbig IT GmbH'
version: 2.1.1
groups:
- name: Templates/Roadwork/Device-Capabilities
- name: Templates/Roadwork/Device-Capabilities/GEOLOCALIZABLE
items:
- uuid: 4ad4d9a769744615816d190c34cb49c7
name: GPS_LOCATION_MQTT
type: TRAP
key: 'mqtt.trap[operationalValue/location]'
history: '0'
value_type: TEXT
description: 'old value: mqtt.get["rabbitmq","operationalValue/{$DEVICETYPE}/{HOST.HOST}/location","voltra_dev:voltradev","rabbit4voltra"]'
- uuid: 0e0012933b2345d4b119fdc50c526c73
name: GPS_LOCATION
type: DEPENDENT
key: state.operational.location.json
history: 90d
value_type: TEXT
master_item:
key: 'mqtt.trap[operationalValue/location]'
tags:
- tag: attributeName
value: location
- tag: hasValue
value: 'true'
- tag: topicType
value: operationalValue
- uuid: e9dcf0279afc4ed4a23e274df4c98356
name: LATITUDE
type: DEPENDENT
key: state.operational.location.latitude
history: 90d
value_type: FLOAT
inventory_link: LOCATION_LAT
preprocessing:
- type: JSONPATH
parameters:
- $.latitude
master_item:
key: state.operational.location.json
- uuid: 49d1677d3a4a4cfab23b2e8e50533833
name: LONGITUDE
type: DEPENDENT
key: state.operational.location.longitude
history: 90d
value_type: FLOAT
inventory_link: LOCATION_LON
preprocessing:
- type: JSONPATH
parameters:
- $.longitude
master_item:
key: state.operational.location.json
- uuid: d7203eaab36749798014b6d3b1a43e24
name: LOCATION_NAME
type: DEPENDENT
key: state.operational.location.name
history: 90d
value_type: TEXT
inventory_link: LOCATION
preprocessing:
- type: JAVASCRIPT
parameters:
- |
var json = JSON.parse(value);
var lat = "lat=" + json.latitude;
var lon = "lon=" + json.longitude;
return lon + ", " + lat;
var geocoderURL = "https://photon.komoot.io/reverse?" + lon + "&" + lat;
var req = new HttpRequest();
req.addHeader('Content-Type: application/json');
resp = req.get(geocoderURL);
if (req.getStatus() != 200) {
throw 'Response code: '+req.getStatus();
}
var resultJson = JSON.parse(resp);
var features = resultJson.features;
if (features.length && features[0].properties) {
var props = features[0].properties;
return props.postcode + " " + props.city;
}
return value;
master_item:
key: state.operational.location.json
tags:
- tag: class
value: roadwork
macros:
- macro: '{$DEVICEKEY}'
value: '{HOST.HOST}'
- uuid: fe8bac9ac30f411cb5a322817760e71d
template: OPERATIONAL_DATA
name: OPERATIONAL_DATA
vendor:
name: 'Hilbig IT GmbH'
version: 2.1.1
groups:
- name: Templates/Roadwork/Device-Capabilities
- name: Templates/Roadwork/Device-Capabilities/HAS_OPERATIONAL_DATA
items:
- uuid: 602290e9f42f4135b548e1cd45abe135
name: DENSITY_MQTT
type: TRAP
key: 'mqtt.trap[operationalValue/density]'
history: '0'
value_type: TEXT
- uuid: 87e0a14266984247b81fdc757dea5bde
name: ERROR_MQTT
type: TRAP
key: 'mqtt.trap[operationalValue/error]'
history: '0'
value_type: TEXT
- uuid: 644b0ec2e3d9448da1a69561ec10d19d
name: SIGNALSTRENGTH_MQTT
type: TRAP
key: 'mqtt.trap[operationalValue/signalstrength]'
history: '0'
value_type: TEXT
- uuid: 67c01d7334a24823832bba74073cf356
name: TEMPERATURE_MQTT
type: TRAP
key: 'mqtt.trap[operationalValue/temperature]'
history: '0'
value_type: TEXT
tags:
- tag: attributeName
value: temperature
- tag: GRAPHQL_TYPE
value: Int
- tag: hasValue
value: 'true'
- tag: subscribeMqtt
value: 'true'
- tag: topicType
value: operationalValue
- uuid: 0352c80c749d4d91b386dab9c74ef3c6
name: VOLTAGE_MQTT
type: TRAP
key: 'mqtt.trap[operationalValue/voltage]'
history: '0'
value_type: TEXT
- uuid: 7aac8212c94044d28ada982c422f2bf7
name: DENSITY
type: DEPENDENT
key: state.operational.density
units: Kfz/Min
preprocessing:
- type: JSONPATH
parameters:
- $.density
master_item:
key: 'mqtt.trap[operationalValue/density]'
tags:
- tag: attributeName
value: density
- tag: hasValue
value: 'true'
- tag: topicType
value: operationalValue
- uuid: 6c8c2f4cdc304b019d02026e7c3225ce
name: ERROR_HIGH
type: DEPENDENT
key: state.operational.errorHigh
value_type: TEXT
preprocessing:
- type: JSONPATH
parameters:
- '$[?(@.code>=1000)]'
error_handler: CUSTOM_VALUE
master_item:
key: state.operational.json_error
triggers:
- uuid: bc381851c3d84534866f3262828817a9
expression: 'last(/OPERATIONAL_DATA/state.operational.errorHigh)<>""'
name: DEVICE_ERROR_HIGH
event_name: DEVICE_ERROR_HIGH
priority: HIGH
manual_close: 'YES'
- uuid: 7634372683af42e7bb807bd2ee0e600a
name: ERROR_INFO
type: DEPENDENT
key: state.operational.errorInfo
value_type: TEXT
preprocessing:
- type: JSONPATH
parameters:
- '$[?(@.code=0)]'
error_handler: CUSTOM_VALUE
master_item:
key: state.operational.json_error
triggers:
- uuid: 201be3f4fd484651a0d2bb34586b408b
expression: 'last(/OPERATIONAL_DATA/state.operational.errorInfo)<>""'
name: DEVICE_ERROR_INFO
event_name: DEVICE_ERROR_INFO
priority: INFO
manual_close: 'YES'
- uuid: 58beaf63a07c44bd918f3f7c93be6d16
name: ERROR_WARNING
type: DEPENDENT
key: state.operational.errorWarning
value_type: TEXT
preprocessing:
- type: JSONPATH
parameters:
- '$[?(@.code>0&&@.code<1000)]'
error_handler: CUSTOM_VALUE
master_item:
key: state.operational.json_error
triggers:
- uuid: 208ef0feec0b469c8b3a8edc3ef12680
expression: 'last(/OPERATIONAL_DATA/state.operational.errorWarning)<>""'
name: DEVICE_ERROR_WARNING
event_name: DEVICE_ERROR_WARNING
priority: WARNING
manual_close: 'YES'
- uuid: cf7b08cec47a46ddb7ea110feab42c94
name: ERROR
type: DEPENDENT
key: state.operational.json_error
history: '0'
value_type: TEXT
preprocessing:
- type: JSONPATH
parameters:
- $.error
master_item:
key: 'mqtt.trap[operationalValue/error]'
tags:
- tag: attributeName
value: error
- tag: hasValue
value: 'true'
- tag: topicType
value: operationalValue
- uuid: a711e858297e4c80a61952b9848dd217
name: SIGNALSTRENGTH
type: DEPENDENT
key: state.operational.signalstrength
history: 90d
value_type: FLOAT
trends: '0'
units: dBm
preprocessing:
- type: JSONPATH
parameters:
- $.signalstrength
master_item:
key: 'mqtt.trap[operationalValue/signalstrength]'
tags:
- tag: attributeName
value: signalstrength
- tag: hasValue
value: 'true'
triggers:
- uuid: 610d7f2cd5db4dc8938a61ffe81eb8e3
expression: 'nodata(/OPERATIONAL_DATA/state.operational.signalstrength,70s)=1'
name: NO_OPERATIONAL_VALUE
priority: WARNING
- uuid: 93df1a6ad1d640c883f446c85a220bd3
name: TEMPERATURE
type: DEPENDENT
key: state.operational.temperature
history: 90d
value_type: FLOAT
trends: '0'
units: °C
preprocessing:
- type: JSONPATH
parameters:
- $.temperature
master_item:
key: 'mqtt.trap[operationalValue/temperature]'
tags:
- tag: attributeName
value: temperature
- tag: hasValue
value: 'true'
triggers:
- uuid: 8bc17b97d7fe4e4f9ac236e90d0b315d
expression: 'last(/OPERATIONAL_DATA/state.operational.temperature,#3)>60'
recovery_mode: RECOVERY_EXPRESSION
recovery_expression: 'last(/OPERATIONAL_DATA/state.operational.temperature,#3)<=60'
name: TEMPERATURE_HIGH
event_name: TEMPERATURE_HIGH
priority: HIGH
dependencies:
- name: TEMPERATURE_WARNING
expression: 'last(/OPERATIONAL_DATA/state.operational.temperature,#3)>50'
recovery_expression: 'last(/OPERATIONAL_DATA/state.operational.temperature,#3)<=50'
- uuid: 086f026b15e5410793d0604b4c7d51f6
expression: 'last(/OPERATIONAL_DATA/state.operational.temperature,#3)>50'
recovery_mode: RECOVERY_EXPRESSION
recovery_expression: 'last(/OPERATIONAL_DATA/state.operational.temperature,#3)<=50'
name: TEMPERATURE_WARNING
event_name: TEMPERATURE_WARNING
priority: AVERAGE
- uuid: cbc60c96e65d4111b902e3b133681067
name: TIMESTAMP
type: CALCULATED
key: state.operational.timestamp
value_type: TEXT
params: 'max(last(/{HOST.HOST}/state.operational.timestampSignalstrength),last(/{HOST.HOST}/state.operational.timestampVoltage))'
inventory_link: POC_2_NOTES
- uuid: ca1a1397f1aa458b88e531f699cacfeb
name: TIMESTAMP_SIGNALSTRENGTH
type: DEPENDENT
key: state.operational.timestampSignalstrength
history: '0'
value_type: TEXT
preprocessing:
- type: JSONPATH
parameters:
- $.timestamp
master_item:
key: 'mqtt.trap[operationalValue/signalstrength]'
tags:
- tag: attributeName
value: timestamp
- tag: hasValue
value: 'true'
- tag: subscribeMqtt
value: 'false'
- uuid: 919a09b55b304fc391bcd569f444b979
name: TIMESTAMP_VOLTAGE
type: DEPENDENT
key: state.operational.timestampVoltage
history: '0'
value_type: TEXT
preprocessing:
- type: JSONPATH
parameters:
- $.timestamp
master_item:
key: 'mqtt.trap[operationalValue/voltage]'
tags:
- tag: attributeName
value: timestamp
- tag: hasValue
value: 'true'
- tag: subscribeMqtt
value: 'false'
- uuid: 3363ed4409b545b48ae7c6197a56aae2
name: VOLTAGE
type: DEPENDENT
key: state.operational.voltage
history: 90d
value_type: FLOAT
trends: '0'
units: V
preprocessing:
- type: JSONPATH
parameters:
- $.voltage
master_item:
key: 'mqtt.trap[operationalValue/voltage]'
tags:
- tag: attributeName
value: voltage
- tag: hasValue
value: 'true'
- tag: topicType
value: operationalValue
triggers:
- uuid: b411e1c0527a470184cac731c072fec2
expression: 'last(/OPERATIONAL_DATA/state.operational.voltage,#3)<=12'
recovery_mode: RECOVERY_EXPRESSION
recovery_expression: 'last(/OPERATIONAL_DATA/state.operational.voltage,#3)>=12'
name: VOLTAGE_LOW
event_name: VOLTAGE_LOW
priority: WARNING
description: |
warning if voltage < 24 of last 3 values
recovery if voltage >=24 of last 3 values
- uuid: f5c351920d544fc6abcf00fd9b26f51b
expression: 'last(/OPERATIONAL_DATA/state.operational.voltage,#3)<=11'
recovery_mode: RECOVERY_EXPRESSION
recovery_expression: 'last(/OPERATIONAL_DATA/state.operational.voltage,#3)>=11'
name: VOLTAGE_TOO_LOW
priority: DISASTER
tags:
- tag: class
value: roadwork
macros:
- macro: '{$DEVICEKEY}'
value: '{HOST.HOST}'
- uuid: 2b814a4751b745bcb08b5ee98f295dc9
template: ROADWORK_DEVICE
name: ROADWORK_DEVICE
vendor:
name: 'Hilbig IT GmbH'
version: 2.1.1
groups:
- name: Templates/Roadwork/Devices
tags:
- tag: class
value: roadwork
- tag: hostType
value: Roadwork/Devices
- uuid: c53977cb7c6a45b18700502fd841db56
template: TEMPLATEGROUP_EXPORT_DUMMY_TEMPLATE
name: TEMPLATEGROUP_EXPORT_DUMMY_TEMPLATE
description: 'This template is linked to all template group which shall be part of the export templates - action because empty template groups will not be exported'
vendor:
name: 'Hilbig IT GmbH'
version: 2.1.1
groups:
- name: Permissions/Automatism
- name: Permissions/Automatism/Status
- name: Permissions/ConstructionSite
- name: Permissions/Library
- name: Permissions/Library/Item
- name: Templates/Roadwork/Controller
- name: Templates/Roadwork/Device-Capabilities
- name: Templates/Roadwork/Device-Capabilities/FLASH_ATTACHABLE
- name: Templates/Roadwork/Device-Capabilities/GEOLOCALIZABLE
- name: Templates/Roadwork/Device-Capabilities/HAS_OPERATIONAL_DATA
- name: Templates/Roadwork/Devices
- name: Templates/Roadwork/DisplayLibrary
tags:
- tag: class
value: roadwork
graphs:
- uuid: 2c8b16c6937a4d07912b6485ac8a1339
name: 'Mac address count'
graph_items:
- drawtype: GRADIENT_LINE
color: 1A7C11
calc_fnc: ALL
item:
host: BT_TRACKER
key: state.current.values.count