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:
parent
e641f8e610
commit
a3ed4886a3
22 changed files with 2450 additions and 20 deletions
2
.idea/runConfigurations/index_ts.xml
generated
2
.idea/runConfigurations/index_ts.xml
generated
|
|
@ -1,5 +1,5 @@
|
|||
<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>
|
||||
<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" />
|
||||
|
|
|
|||
47
.idea/workspace.xml
generated
47
.idea/workspace.xml
generated
|
|
@ -4,13 +4,16 @@
|
|||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<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$/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$/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-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" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
|
|
@ -22,7 +25,7 @@
|
|||
<execution />
|
||||
</component>
|
||||
<component name="EmbeddingIndexingInfo">
|
||||
<option name="cachedIndexableFilesCount" value="57" />
|
||||
<option name="cachedIndexableFilesCount" value="70" />
|
||||
<option name="fileBasedEmbeddingIndicesEnabled" value="true" />
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
|
|
@ -52,7 +55,7 @@
|
|||
"NIXITCH_NIX_PROFILES": "",
|
||||
"NIXITCH_NIX_REMOTE": "",
|
||||
"NIXITCH_NIX_USER_PROFILE_DIR": "",
|
||||
"Node.js.index.ts.executor": "Debug",
|
||||
"Node.js.index.ts.executor": "Run",
|
||||
"RunOnceActivity.MCP Project settings loaded": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||
|
|
@ -63,7 +66,7 @@
|
|||
"go.import.settings.migrated": "true",
|
||||
"javascript.preferred.runtime.type.id": "node",
|
||||
"junie.onboarding.icon.badge.shown": "true",
|
||||
"last_opened_file_path": "//wsl.localhost/Ubuntu/home/ahilbig/git/vcr/zabbix-graphql-api/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.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
|
|
@ -74,7 +77,7 @@
|
|||
"npm.compile.executor": "Run",
|
||||
"npm.copy-schema.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",
|
||||
"ts.external.directory.path": "\\\\wsl.localhost\\Ubuntu\\home\\ahilbig\\git\\vcr\\zabbix-graphql-api\\node_modules\\typescript\\lib",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
|
|
@ -89,6 +92,7 @@
|
|||
</component>
|
||||
<component name="RecentsManager">
|
||||
<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\.forgejo\workflows" />
|
||||
<recent name="\\wsl.localhost\Ubuntu\home\ahilbig\git\vcr\zabbix-graphql-api" />
|
||||
|
|
@ -160,7 +164,9 @@
|
|||
<workItem from="1768273025985" duration="11343000" />
|
||||
<workItem from="1768380302361" duration="9751000" />
|
||||
<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 id="LOCAL-00001" summary="chore: Update IntelliJ workspace settings and add GitHub Actions workflow for Docker deployment">
|
||||
<option name="closed" value="true" />
|
||||
|
|
@ -274,7 +280,15 @@
|
|||
<option name="project" value="LOCAL" />
|
||||
<updated>1768592274523</updated>
|
||||
</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 />
|
||||
</component>
|
||||
<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: 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" />
|
||||
<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 name="XDebuggerManager">
|
||||
<breakpoint-manager>
|
||||
<breakpoints>
|
||||
<line-breakpoint enabled="true" type="javascript">
|
||||
<url>file://$PROJECT_DIR$/src/datasources/zabbix-hosts.ts</url>
|
||||
<line>149</line>
|
||||
<url>file://$PROJECT_DIR$/src/datasources/zabbix-request.ts</url>
|
||||
<line>133</line>
|
||||
<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>
|
||||
</breakpoints>
|
||||
</breakpoint-manager>
|
||||
|
|
|
|||
18
docs/sample_all_template_groups_query.graphql
Normal file
18
docs/sample_all_template_groups_query.graphql
Normal 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/*"
|
||||
}
|
||||
```
|
||||
30
docs/sample_delete_template_groups_mutation.graphql
Normal file
30
docs/sample_delete_template_groups_mutation.graphql
Normal 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/%"
|
||||
}
|
||||
```
|
||||
30
docs/sample_delete_templates_mutation.graphql
Normal file
30
docs/sample_delete_templates_mutation.graphql
Normal 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%"
|
||||
}
|
||||
```
|
||||
59
docs/sample_import_template_groups_mutation.graphql
Normal file
59
docs/sample_import_template_groups_mutation.graphql
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
97
docs/sample_import_templates_mutation.graphql
Normal file
97
docs/sample_import_templates_mutation.graphql
Normal 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
|
||||
18
docs/sample_templates_query.graphql
Normal file
18
docs/sample_templates_query.graphql
Normal 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"
|
||||
}
|
||||
```
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
"type": "module",
|
||||
"scripts": {
|
||||
"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",
|
||||
"test": "jest --detectOpenHandles --forceExit --bail",
|
||||
"codegen": "graphql-codegen --config codegen.ts --watch \"schema.graphql\"",
|
||||
|
|
|
|||
|
|
@ -29,12 +29,152 @@ type Mutation {
|
|||
importHosts(hosts: [CreateHost!]!):[ImportHostResponse!]
|
||||
|
||||
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
|
||||
####################################################################
|
||||
|
||||
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 {
|
||||
"""
|
||||
Name of the host group
|
||||
|
|
|
|||
|
|
@ -113,5 +113,15 @@ type Query {
|
|||
exclude_groups_pattern: Regex allowing to exclude all matching hostgroups from group permissions
|
||||
"""
|
||||
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]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@ import {
|
|||
MutationCreateHostArgs,
|
||||
MutationImportHostGroupsArgs,
|
||||
MutationImportHostsArgs,
|
||||
MutationImportTemplateGroupsArgs,
|
||||
MutationImportTemplatesArgs,
|
||||
MutationDeleteTemplatesArgs,
|
||||
MutationDeleteTemplateGroupsArgs,
|
||||
MutationImportUserRightsArgs,
|
||||
Permission, QueryAllDevicesArgs,
|
||||
QueryAllHostGroupsArgs,
|
||||
|
|
@ -13,12 +17,15 @@ import {
|
|||
QueryExportHostValueHistoryArgs,
|
||||
QueryExportUserRightsArgs,
|
||||
QueryHasPermissionsArgs,
|
||||
QueryTemplatesArgs,
|
||||
QueryUserPermissionsArgs,
|
||||
Resolvers,
|
||||
StorageItemType,
|
||||
} from "../schema/generated/graphql.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 {logger} from "../logging/logger.js";
|
||||
import {ParsedArgs, ZabbixRequest} from "../datasources/zabbix-request.js";
|
||||
|
|
@ -39,6 +46,14 @@ import {
|
|||
ZabbixImportUserRolesRequest,
|
||||
ZabbixQueryUserRolesRequest
|
||||
} 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 {GraphQLInterfaceType, GraphQLList} from "graphql/type/index.js";
|
||||
import {isDevice} from "./resolver_helpers.js";
|
||||
|
|
@ -129,6 +144,37 @@ export function createResolvers(): Resolvers {
|
|||
userGroups: groups,
|
||||
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: {
|
||||
|
|
@ -172,6 +218,30 @@ export function createResolvers(): Resolvers {
|
|||
userRoles: userRolesImport,
|
||||
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)
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export class ParsedArgs {
|
|||
constructor(params?: any) {
|
||||
if (Array.isArray(params)) {
|
||||
this.zabbix_params = params.map(arg => this.parseArgObject(arg))
|
||||
} else if (params instanceof Object) {
|
||||
} else {
|
||||
this.zabbix_params = this.parseArgObject(params)
|
||||
}
|
||||
}
|
||||
|
|
@ -52,9 +52,12 @@ export class ParsedArgs {
|
|||
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
|
||||
if (args) {
|
||||
if (args && typeof args === 'object' && args.constructor === Object) {
|
||||
if ("name_pattern" in args && typeof args["name_pattern"] == "string") {
|
||||
if (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
|
||||
if (Array.isArray(args?.zabbix_params)) {
|
||||
params = args?.zabbix_params.map(paramsObj => {
|
||||
if (paramsObj !== null && typeof paramsObj === 'object' && paramsObj.constructor === Object) {
|
||||
return {...this.requestBodyTemplate.params, ...paramsObj}
|
||||
}
|
||||
return paramsObj;
|
||||
})
|
||||
} else {
|
||||
params = {...this.requestBodyTemplate.params, ...zabbixParams ?? this.createZabbixParams(args)}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
107
src/execution/template_deleter.ts
Normal file
107
src/execution/template_deleter.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
220
src/execution/template_importer.ts
Normal file
220
src/execution/template_importer.ts
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -81,6 +81,94 @@ export interface CreateHostResponse {
|
|||
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
|
||||
* besides monitoring information.
|
||||
|
|
@ -232,6 +320,14 @@ export interface ImportHostResponse {
|
|||
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 {
|
||||
__typename?: 'ImportUserRightResult';
|
||||
errors?: Maybe<Array<ApiError>>;
|
||||
|
|
@ -268,6 +364,18 @@ export interface Mutation {
|
|||
__typename?: 'Mutation';
|
||||
/** Authentication: By zbx_session - cookie or zabbix-auth-token - header */
|
||||
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
|
||||
* 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
|
||||
*/
|
||||
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>;
|
||||
}
|
||||
|
||||
|
|
@ -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 {
|
||||
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 {
|
||||
dryRun?: Scalars['Boolean']['input'];
|
||||
input: UserRightsInput;
|
||||
|
|
@ -359,6 +508,8 @@ export interface Query {
|
|||
* Authentication: By zbx_session - cookie or zabbix-auth-token - header
|
||||
*/
|
||||
allHosts?: Maybe<Array<Maybe<Host>>>;
|
||||
/** Get template groups. */
|
||||
allTemplateGroups?: Maybe<Array<Maybe<HostGroup>>>;
|
||||
/** Get api (build) version */
|
||||
apiVersion: Scalars['String']['output'];
|
||||
/**
|
||||
|
|
@ -401,6 +552,8 @@ export interface Query {
|
|||
* operation. Returns true on success
|
||||
*/
|
||||
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
|
||||
* the objectNames - list
|
||||
|
|
@ -439,6 +592,11 @@ export interface QueryAllHostsArgs {
|
|||
}
|
||||
|
||||
|
||||
export interface QueryAllTemplateGroupsArgs {
|
||||
name_pattern?: InputMaybe<Scalars['String']['input']>;
|
||||
}
|
||||
|
||||
|
||||
export interface QueryExportHostValueHistoryArgs {
|
||||
host_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 {
|
||||
objectNames?: InputMaybe<Array<Scalars['String']['input']>>;
|
||||
}
|
||||
|
|
@ -749,7 +913,16 @@ export type ResolversTypes = {
|
|||
CreateHostGroup: CreateHostGroup;
|
||||
CreateHostGroupResponse: ResolverTypeWrapper<CreateHostGroupResponse>;
|
||||
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']>;
|
||||
DeleteResponse: ResolverTypeWrapper<DeleteResponse>;
|
||||
Device: ResolverTypeWrapper<ResolversInterfaceTypes<ResolversTypes>['Device']>;
|
||||
DeviceCommunicationType: DeviceCommunicationType;
|
||||
DeviceConfig: ResolverTypeWrapper<DeviceConfig>;
|
||||
|
|
@ -769,6 +942,7 @@ export type ResolversTypes = {
|
|||
HostGroup: ResolverTypeWrapper<HostGroup>;
|
||||
ID: ResolverTypeWrapper<Scalars['ID']['output']>;
|
||||
ImportHostResponse: ResolverTypeWrapper<ImportHostResponse>;
|
||||
ImportTemplateResponse: ResolverTypeWrapper<ImportTemplateResponse>;
|
||||
ImportUserRightResult: ResolverTypeWrapper<ImportUserRightResult>;
|
||||
ImportUserRightsResult: ResolverTypeWrapper<ImportUserRightsResult>;
|
||||
Int: ResolverTypeWrapper<Scalars['Int']['output']>;
|
||||
|
|
@ -814,7 +988,16 @@ export type ResolversParentTypes = {
|
|||
CreateHostGroup: CreateHostGroup;
|
||||
CreateHostGroupResponse: CreateHostGroupResponse;
|
||||
CreateHostResponse: CreateHostResponse;
|
||||
CreateItemPreprocessing: CreateItemPreprocessing;
|
||||
CreateLinkedTemplate: CreateLinkedTemplate;
|
||||
CreateMasterItem: CreateMasterItem;
|
||||
CreateTag: CreateTag;
|
||||
CreateTemplate: CreateTemplate;
|
||||
CreateTemplateGroup: CreateTemplateGroup;
|
||||
CreateTemplateGroupResponse: CreateTemplateGroupResponse;
|
||||
CreateTemplateItem: CreateTemplateItem;
|
||||
DateTime: Scalars['DateTime']['output'];
|
||||
DeleteResponse: DeleteResponse;
|
||||
Device: ResolversInterfaceTypes<ResolversParentTypes>['Device'];
|
||||
DeviceConfig: DeviceConfig;
|
||||
DeviceState: ResolversInterfaceTypes<ResolversParentTypes>['DeviceState'];
|
||||
|
|
@ -832,6 +1015,7 @@ export type ResolversParentTypes = {
|
|||
HostGroup: HostGroup;
|
||||
ID: Scalars['ID']['output'];
|
||||
ImportHostResponse: ImportHostResponse;
|
||||
ImportTemplateResponse: ImportTemplateResponse;
|
||||
ImportUserRightResult: ImportUserRightResult;
|
||||
ImportUserRightsResult: ImportUserRightsResult;
|
||||
Int: Scalars['Int']['output'];
|
||||
|
|
@ -890,10 +1074,25 @@ export type CreateHostResponseResolvers<ContextType = any, ParentType extends Re
|
|||
__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> {
|
||||
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']> = {
|
||||
__resolveType: TypeResolveFn<'GenericDevice', 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>;
|
||||
};
|
||||
|
||||
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']> = {
|
||||
errors?: Resolver<Maybe<Array<ResolversTypes['ApiError']>>, 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']> = {
|
||||
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'>>;
|
||||
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'>>;
|
||||
};
|
||||
|
||||
|
|
@ -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'>>;
|
||||
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'>>;
|
||||
allTemplateGroups?: Resolver<Maybe<Array<Maybe<ResolversTypes['HostGroup']>>>, ParentType, ContextType, Partial<QueryAllTemplateGroupsArgs>>;
|
||||
apiVersion?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
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'>>;
|
||||
|
|
@ -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'>>;
|
||||
login?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType, RequireFields<QueryLoginArgs, 'password' | 'username'>>;
|
||||
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>>;
|
||||
zabbixVersion?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
};
|
||||
|
|
@ -1193,7 +1406,9 @@ export type Resolvers<ContextType = any> = {
|
|||
ApiError?: ApiErrorResolvers<ContextType>;
|
||||
CreateHostGroupResponse?: CreateHostGroupResponseResolvers<ContextType>;
|
||||
CreateHostResponse?: CreateHostResponseResolvers<ContextType>;
|
||||
CreateTemplateGroupResponse?: CreateTemplateGroupResponseResolvers<ContextType>;
|
||||
DateTime?: GraphQLScalarType;
|
||||
DeleteResponse?: DeleteResponseResolvers<ContextType>;
|
||||
Device?: DeviceResolvers<ContextType>;
|
||||
DeviceCommunicationType?: DeviceCommunicationTypeResolvers;
|
||||
DeviceConfig?: DeviceConfigResolvers<ContextType>;
|
||||
|
|
@ -1211,6 +1426,7 @@ export type Resolvers<ContextType = any> = {
|
|||
Host?: HostResolvers<ContextType>;
|
||||
HostGroup?: HostGroupResolvers<ContextType>;
|
||||
ImportHostResponse?: ImportHostResponseResolvers<ContextType>;
|
||||
ImportTemplateResponse?: ImportTemplateResponseResolvers<ContextType>;
|
||||
ImportUserRightResult?: ImportUserRightResultResolvers<ContextType>;
|
||||
ImportUserRightsResult?: ImportUserRightsResultResolvers<ContextType>;
|
||||
Inventory?: InventoryResolvers<ContextType>;
|
||||
|
|
|
|||
162
src/test/template_deleter.test.ts
Normal file
162
src/test/template_deleter.test.ts
Normal 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]
|
||||
})
|
||||
}));
|
||||
});
|
||||
});
|
||||
176
src/test/template_importer.test.ts
Normal file
176
src/test/template_importer.test.ts
Normal 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.");
|
||||
});
|
||||
});
|
||||
229
src/test/template_integration.test.ts
Normal file
229
src/test/template_integration.test.ts
Normal 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");
|
||||
});
|
||||
});
|
||||
112
src/test/template_query.test.ts
Normal file
112
src/test/template_query.test.ts
Normal 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"
|
||||
}
|
||||
})
|
||||
})
|
||||
}));
|
||||
});
|
||||
});
|
||||
670
src/testdata/templates/zbx_default_templates_vcr.yaml
vendored
Normal file
670
src/testdata/templates/zbx_default_templates_vcr.yaml
vendored
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue