refactor!: Restructure grapqhl-schema to better align with clean code and project structure principles

This commit is contained in:
Andreas Hilbig 2026-01-06 15:58:38 +01:00
parent 47640ff13e
commit a89c3eeea7
21 changed files with 648 additions and 1847 deletions

View file

@ -2,16 +2,16 @@ import type {CodegenConfig} from '@graphql-codegen/cli';
const config: CodegenConfig = { const config: CodegenConfig = {
overwrite: true, overwrite: true,
schema: './schema.graphql', schema: 'src/schema/*.graphql',
generates: { generates: {
"src/generated/graphql.ts": { "src/schema/generated/graphql.ts": {
plugins: ["typescript", "typescript-resolvers"], plugins: ["typescript", "typescript-resolvers"],
config: { config: {
enumValues: { enumValues: {
DeviceCommunicationType: "../model/model_enum_values.js#DeviceCommunicationType", DeviceCommunicationType: "../../model/model_enum_values.js#DeviceCommunicationType",
StorageItemType: "../model/model_enum_values.js#StorageItemType", StorageItemType: "../../model/model_enum_values.js#StorageItemType",
DeviceStatus: "../model/model_enum_values.js#DeviceStatus", DeviceStatus: "../../model/model_enum_values.js#DeviceStatus",
Permission: "../model/model_enum_values.js#Permission", Permission: "../../model/model_enum_values.js#Permission",
}, },
declarationKind: 'interface' declarationKind: 'interface'
} }

View file

@ -1,624 +0,0 @@
# Scalars resolved by package "graphql-scalars"
scalar DateTime
scalar Time
scalar JSONObject
# Schema definitions go here
type Query {
"Get api (build) version"
apiVersion: String!
"Get zabbix version"
zabbixVersion: String
"""
Login to zabbix - provided for debugging and testing purpose. The result of the login operation is
authentication token returned may be passed as
header 'zabbix-auth-token' for authenticating future API requests.
As an alternative to the cookie 'zbx_session' may be set which is automatically set after login to
the cockpit - this is the standard way to authenticate api calls initiated by the cockpit frontend
because the frontend is always embedded into the Zabbix portal which is only accessible after logging in and
obtainind the zbx_session - cookie.
"""
login(username: String!, password: String!): String
"""
Logout from zabbix - provided for debugging and testing purpose. This invalidates the token received by the login
operation. Returns true on success
"""
logout: Boolean
"""
Get all hosts + corresponding items. If with_items==true only hosts with attached items are delivered
name_pattern: If provided this will perform a LIKE "%…%" search on the name attribute within the database.
Authentication: By zbx_session - cookie or zabbix-auth-token - header
"""
allHosts(name_pattern: String = "", filter_host: String = null, hostids: Int,
groupids:[Int!] = null, with_items: Boolean = false, tag_deviceType:[String]=[], tag_hostType:[String!]): [Host]
"""
Get all host groups.
If with_hosts==true only groups with attached hosts are delivered.
Authentication: By zbx_session - cookie or zabbix-auth-token - header
"""
allHostGroups(search_name: String, with_hosts: Boolean = true): [HostGroup]
"""
Get all locations used by hosts.
distinct_by_name=true means that the result is filtered for distinct names (default)
name_pattern: If provided this will perform a Regex search on the name attribute within the database.
Authentication: By zbx_session - cookie or zabbix-auth-token - header
"""
locations(name_pattern: String = "", distinct_by_name: Boolean = true, templateids:[String] = null): [Location]
"""
Export history from Zabbix items
Authentication: By zbx_session - cookie or zabbix-auth-token - header
"""
exportHostValueHistory(
"(Optional) list of hostnames to be included in the result"
host_filter: [String!],
"(Optional) list of item keys to be included in the result"
itemKey_filter: [String!],
"""
(Optional) timestamp of earliest value"""
time_from: DateTime,
"""(Optional) timestamp of last value """
time_until: DateTime,
"""Results are sorted by timestamps - ascending or descending order may be specified
using this parameter"""
sortOrder: SortOrder=desc,
"""
Maximum number of records to be delivered. Hint: This might be useful, because the
current version of Zabbix delivers a 500 - error in case of requesting too much data
"""
limit: Int
"""
As values are stored in different data structures depending on their type
the type information must be specified in advance, although
each value (also if number) is converted into a string afterwards
"""
type: StorageItemType = FLOAT
):HistoryExportResponse
"""
Return all user permissions. If objectNames is provided return only the permissions related to the objects within
the objectNames - list
"""
userPermissions(objectNames: [String!]): [UserPermission!]
"""
Return true if and only if the current user (identified by token / cookie)
has all requested permissions (minimum - if READ is requested and the user has READ_WRITE
the response will be true)
"""
hasPermissions(permissions: [PermissionRequest!]!): Boolean
"""
name_pattern: If provided this will perform a LIKE "%…%" search on the name attribute within the database.
exclude_groups_pattern: Regex allowing to exclude all matching hostgroups from group permissions
"""
exportUserRights(name_pattern: String = "" exclude_hostgroups_pattern: String = ""): UserRights
}
type UserRights {
userGroups: [UserGroup!]
userRoles: [UserRole!]
}
type UserRole {
roleid: Int!
name: String
type: Int
readonly: Int
rules: UserRoleRules
}
type UserRoleRules {
ui: [UserRoleRule!]
ui_default_access: Int
modules:[UserRoleModule!]
modules_default_access: Int
api_access: Int
api_mode: Int
api: [String!]
actions: [UserRoleRule!]
actions_default_access: Int
}
type UserRoleRule {
name: String
status: Int
}
type UserRoleModule {
moduleid: String
status: Int
id: String
relative_path: String
}
type UserGroup {
usrgrpid: Int!
name: String!
gui_access: Int
users_status: Int
hostgroup_rights: [ZabbixGroupRight!]
templategroup_rights: [ZabbixGroupRight!]
}
type ZabbixGroupRight {
id: Int!
uuid: String
name: String
permission: Permission
}
########################################################
# User permissions
########################################################
input PermissionRequest {
permission: Permission!,
"""
objectName maps to name / path suffix of the template group representing the permission in Zabbix:
Permissions/{objectName}
"""
objectName: String!
}
"""
READ, READ_WRITE or DENY:
describes the rights related to an objectName
There is no EXECUTE or anything else in Zabbix,
i.e. objectName - Tree has to be designed accordingly in order to represent the perform actions.
E.g.
Let's assume a button called "button1", used in application "app1", having a label which shows "do something". Instead of model the action "do something" the idea is
to model the effect of this action - do something will result in a status change. Let's further assume that the button will only
be displayed if the user is allowed to see the current status.
Permissions/app1/button1/status
The following PermissionRequests would be used by the frontend:
1. Should the button (and its status) be displayed at all?
button1.displayed = hasPermissions(
{
objectName: "app1/button1/status"
permission: READ
})
2. Should the user be able to press the button (enabled)?
button1.displayed = hasPermissions(
{
objectName: "app1/button1/status"
permission: READ_WRITE
})
Usage Example for this pattern: Activation/Deactivation of a control program - the button
shows the possible action. If the program is active it shows "Deactivate". If the program is inactive it shows "Activate".
From this label the user learns something about the current state - therefore the status - read - permission is needed in order
to display the button at all. The status write permission is needed in order to enable the button (i.e. allow the user to press it).
in order to model the permissions to press / see a button "button1" belonging to application "app1"
the following template group could be modelled in Zabbix
"""
enum Permission {
"""
DENY superseeds anything else - i.e. if in Zabbix there is a DENY and READ at the same time the result will be DENY
"""
DENY
"""
READ superseeds READ_WRITE - i.e. if in Zabbix there is a READ_WRITE and READ at the same time the resulting permission
level will be READ
"""
READ
"""
READ_WRITE implies the READ permission. Do not set both READ and READ_WRITE at the same time if you want to achieve
READ + WRITE permission, because in this case READ will superseed the READ_WRITE and the resulting permission level will be READ
"""
READ_WRITE
}
type UserPermission {
permission: Permission!,
"""
objectName maps to name / path suffix of the template group representing the permission in Zabbix:
Permissions/{objectName}
"""
objectName: String!
}
########################################################
# Values
########################################################
enum StorageItemType {
FLOAT
INT
TEXT
}
type HistoryExportResponse {
result: [JSONObject!]
error: ApiError
}
############################################################################
type Mutation {
"""
Authentication: By zbx_session - cookie or zabbix-auth-token - header
"""
createHost(host: String!, hostgroupids:[Int!]!, templateids: [Int!]!,
location: LocationInput): ZabbixCreateResponse
"""
(Mass) Import zabbix groups
and assign them to the corresponding hosts by groupid or groupName.
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
"""
importHostGroups(hostGroups: [CreateHostGroup!]!):[CreateHostGroupResponse!]
"""
(Mass) Import hosts and assign them to host groups by groupid or groupName.
Return value: If no error occurs a hostid will be returned for each created host,
otherwise the return object will contain an error message.
Authentication: By zbx_session - cookie or zabbix-auth-token - header
"""
importHosts(hosts: [CreateHost!]!):[CreateHostResponse!]
importUserRights(input: UserRightsInput!, dryRun: Boolean! = true): ImportUserRightsResult
}
input UserRightsInput {
userRoles: [UserRoleInput!]
userGroups: [UserGroupInput!]
}
input UserRoleInput {
name: String
type: Int
readonly: Int
rules: UserRoleRulesInput
}
input UserRoleRulesInput {
ui: [UserRoleRuleInput!]
ui_default_access: Int
modules:[UserRoleModuleInput!]
modules_default_access: Int
api_access: Int
api_mode: Int
api: [String!]
actions: [UserRoleRuleInput!]
actions_default_access: Int
}
input UserRoleRuleInput {
name: String
status: Int
}
input UserRoleModuleInput {
moduleid: String
status: Int
id: String
}
input UserGroupInput {
name: String!
gui_access: Int
users_status: Int
hostgroup_rights: [ZabbixGroupRightInput!]
templategroup_rights: [ZabbixGroupRightInput!]
}
input ZabbixGroupRightInput {
uuid: String
"""
name may optionally be specified for documentation purpose,
but the master for setting the user right is the uuid.
If a uuid is found and the corresponding group
has a deviating name this will be documented within a message
with errorcode = 0 (OK) but the permission will be set (
the reason is that names for groups may deviate between several
instances of the control center although the semantic is the same -
while the semantic is identified by uuid.
"""
name: String
permission: Permission
}
type ImportUserRightsResult {
userRoles: [ImportUserRightResult!]
userGroups: [ImportUserRightResult!]
}
type ImportUserRightResult {
id: String
name: String
message: String
errors: [ApiError!]
}
"""
Hint: WGS84[dd.ddddd] coordinates are used
"""
interface GpsPosition {
latitude: Float
longitude: Float
}
#########################################
type HostGroup {
groupid: ID!
name: String
}
interface Host {
hostid: ID!
"""
The host field contains the "hostname" in Zabbix
"""
host: String!
deviceType: String
hostgroups: [HostGroup!]
name: String
tags: JSONObject
}
type ZabbixItem {
itemid: Int!
name: String!
key_: String!
hostid: Int
lastclock: Int
lastvalue: String
value_type: Int!
attributeName: String
deviceType: String
topicType:String
status: DeviceStatus
type: DeviceCommunicationType
hosts: [Host]
}
type ZabbixHost implements Host {
hostid: ID!
host: String!
deviceType: String
hostgroups: [HostGroup!]
name: String
tags: JSONObject
items: [ZabbixItem!]
inventory: Inventory
parentTemplates: [Template!]
}
"""
(IoT / Edge - ) Devices are hosts having a state containing the "output" / the business data which is exposed
besides monitoring information.
"""
interface Device implements Host {
hostid: ID!
"""
Per convention a uuid is used as hostname to identify devices if they do not have a unique hostname
"""
host: String!
deviceType: String
hostgroups: [HostGroup!]
name: String
tags: JSONObject
state: DeviceState
}
type OperationalDeviceData {
temperature: Float
voltage: Float
signalstrength: Float
location: Location
timestamp: DateTime
error: [ErrorPayload!]
}
interface DeviceState {
operational: OperationalDeviceData
}
# Generic IoT devices with "generic" current state - mapping all "values"
type GenericDeviceState implements DeviceState {
operational: OperationalDeviceData
current: JSONObject
}
"""
Device represents generic IoT / Edge - devices providing their state as generic "state.current" - JSON Object
"""
type GenericDevice implements Host & Device {
hostid: ID!
"""
Per convention a uuid is used as hostname to identify devices if they do not have a unique hostname
"""
host: String!
deviceType: String
hostgroups: [HostGroup!]
name: String
tags: JSONObject
state: GenericDeviceState
}
type ErrorPayload {
code: Int!
message: String
additionalInfo: JSONObject
}
enum SensorValueType {
NUMERIC # 0 - numeric float;
CHARACTER # 1 - character;
LOG # 2 - log;
NUMERIC_UNSIGNED # 3 - numeric unsigned;
TEXT # 4 - text;
}
type ZabbixCreateResponse {
hostids: [Int]
itemids: [Int]
error: ApiError
}
input LocationInput {
name: String
location_lat: String
location_lon: String
}
type Template {
templateid: String!
name: String
}
type Inventory {
location: Location
}
type Location implements GpsPosition {
name: String
latitude: Float
longitude: Float
}
enum DeviceStatus {
ENABLED
DISABLED
}
enum DeviceCommunicationType {
ZABBIX_AGENT
ZABBIX_AGENT_ACTIVE
ZABBIX_TRAP
ZABBIX_INTERNAL_ITEM
SIMPLE_CHECK
DEPENDANT_ITEM
SIMULATOR_CALCULATED
SIMULATOR_JAVASCRIPT
HTTP_AGENT
IPMI_AGENT
JMX_AGENT
SNMP_AGENT
SNMP_TRAP
DATABASE_MONITOR
}
type Item {
itemid: Int
hostid: Int
name: String!
key_: String
attributeName: String
deviceType: String
topicType:String
status: DeviceStatus
type: DeviceCommunicationType
hosts: [Host]
}
####################################################################
# Input types used for importXXX - and storeXXX - Mutations
####################################################################
input CreateHostGroup {
"""
Name of the host group
"""
groupName: String!
"""
Internally used unique id
(will be assigned by Zabbix if empty)
"""
uuid: String
}
type CreateHostGroupResponse {
groupName: String!
groupid: Int
message: String
error: ApiError
}
type HostTypeMeta {
deviceType: String
deviceTypeDescription: String
}
input CreateHost {
deviceKey: String!
"""
Optional display name of the device (must be unique if provided - default is to set display name to deviceKey)
"""
name: String
deviceType: String!
"""
groupNames is used to assign the created object
to a host group. It is mandatory but
can also be blank. This is usefull in case of
passing a groupid instead which is
the zabbix internal key for storing the group.
If a groupid is provided the passed groupName is ignored
"""
groupNames: [String!]!
"""
Optionally the internal groupids can be passed - in this case the
groupName is ignored
"""
groupids: [Int]
location: LocationInput
}
type CreateHostResponse {
deviceKey: String!
hostid: String
message: String
error: ApiError
}
interface Error {
code: Int
message: String
data: JSONObject
}
type ApiError implements Error {
code: Int
message: String
data: JSONObject
path: String
args: JSONObject
}
############################################################################
# General purpose types + enums
############################################################################
enum SortOrder {
"Deliver values in ascending order"
asc
"Deliver values in descending order"
desc
}

View file

@ -1,5 +1,8 @@
import {isObjectType} from "graphql"; import {isObjectType} from "graphql";
import {logger} from "../logging/logger.js"; import {logger} from "../logging/logger.js";
import {Device, Host} from "../schema/generated/graphql";
export const isDevice = (value: Host): value is Device => !!(value as Device).deviceType;
/* /*
As a default all . - seperators within a key shall be replaced by a Capital letter of the following word As a default all . - seperators within a key shall be replaced by a Capital letter of the following word

View file

@ -12,8 +12,8 @@ import {
QueryHasPermissionsArgs, QueryHasPermissionsArgs,
QueryUserPermissionsArgs, QueryUserPermissionsArgs,
Resolvers, Resolvers,
StorageItemType, Host, QueryExportHostValueHistoryArgs, StorageItemType, Host, QueryExportHostValueHistoryArgs, Device,
} from "../generated/graphql.js"; } from "../schema/generated/graphql.js";
import {HostImporter} from "../execution/host_importer"; import {HostImporter} from "../execution/host_importer";
import {HostValueExporter} from "../execution/host_exporter"; import {HostValueExporter} from "../execution/host_exporter";
@ -34,6 +34,7 @@ import {
} from "../datasources/zabbix-userroles.js"; } from "../datasources/zabbix-userroles.js";
import {ZABBIX_EDGE_DEVICE_BASE_GROUP, zabbixAPI} from "../datasources/zabbix-api"; import {ZABBIX_EDGE_DEVICE_BASE_GROUP, zabbixAPI} from "../datasources/zabbix-api";
import {GraphQLInterfaceType, GraphQLList} from "graphql/type"; import {GraphQLInterfaceType, GraphQLList} from "graphql/type";
import {isDevice} from "./resolver_helpers";
export function createResolvers(): Resolvers { export function createResolvers(): Resolvers {
@ -160,23 +161,20 @@ export function createResolvers(): Resolvers {
// @ts-ignore // @ts-ignore
__resolveType: function (host: Host, _context, info ): string { __resolveType: function (host: Host, _context, info ): string {
const deviceType = host.deviceType ?? ""; if (!isDevice(host)) {
logger.info(`checking host ${host.name} for deviceType - no device type found, returning as ZabbixHost`);
if (deviceType) { return "ZabbixHost";
logger.info(`checking host ${host.name} for deviceType - found ${deviceType}`);
let interfaceType: GraphQLInterfaceType = (info.returnType instanceof GraphQLList ?
info.returnType.ofType : info.returnType) as GraphQLInterfaceType
if (info.schema.getImplementations(interfaceType).objects.some((impl: { name: string; }) => impl.name === deviceType)) {
return deviceType;
}
return "GenericDevice"
} }
const deviceType = host.deviceType!;
logger.info(`checking host ${host.name} for deviceType - no device type found, returning as ZabbixHost`); logger.info(`checking host ${host.name} for deviceType - found ${deviceType}`);
return "ZabbixHost"; // Return "generic" device host as a default if no templates are assigned let interfaceType: GraphQLInterfaceType = (info.returnType instanceof GraphQLList ?
info.returnType.ofType : info.returnType) as GraphQLInterfaceType
if (info.schema.getImplementations(interfaceType).objects.some((impl: { name: string; }) => impl.name === deviceType)) {
return deviceType;
}
return "GenericDevice"
} }
}, },
Inventory: { Inventory: {
@ -239,13 +237,6 @@ export function createResolvers(): Resolvers {
DISABLED: DeviceStatus.DISABLED DISABLED: DeviceStatus.DISABLED
}, },
SensorValueType: {
NUMERIC: 0,
CHARACTER: 1,
LOG: 2,
NUMERIC_UNSIGNED: 3,
TEXT: 4
},
StorageItemType: { StorageItemType: {
TEXT: StorageItemType.Text, TEXT: StorageItemType.Text,
FLOAT: StorageItemType.Float, FLOAT: StorageItemType.Float,

View file

@ -1,5 +1,5 @@
import {ZabbixAPI} from "./zabbix-api.js"; import {ZabbixAPI} from "./zabbix-api.js";
import {ApiError, SortOrder, StorageItemType} from "../generated/graphql.js"; import {ApiError, SortOrder, StorageItemType} from "../schema/generated/graphql.js";
import {ZabbixCreateOrUpdateStorageItemRequest} from "./zabbix-items.js"; import {ZabbixCreateOrUpdateStorageItemRequest} from "./zabbix-items.js";
import {ZabbixForceCacheReloadRequest} from "./zabbix-script.js"; import {ZabbixForceCacheReloadRequest} from "./zabbix-script.js";
import {logger} from "../logging/logger.js"; import {logger} from "../logging/logger.js";

View file

@ -1,5 +1,5 @@
import {isZabbixErrorResult, ParsedArgs, ZabbixParams, ZabbixRequest} from "./zabbix-request.js"; import {isZabbixErrorResult, ParsedArgs, ZabbixParams, ZabbixRequest} from "./zabbix-request.js";
import {Permission} from "../generated/graphql.js"; import {Permission} from "../schema/generated/graphql.js";
import { import {
FIND_ZABBIX_EDGE_DEVICE_BASE_GROUP_PREFIX, FIND_ZABBIX_EDGE_DEVICE_BASE_GROUP_PREFIX,
ZABBIX_EDGE_DEVICE_BASE_GROUP, ZABBIX_EDGE_DEVICE_BASE_GROUP,

View file

@ -1,4 +1,4 @@
import {Host, ZabbixHost} from "../generated/graphql.js"; import {CreateHostResponse, Host, ZabbixHost} from "../schema/generated/graphql.js";
import {ZabbixAPI} from "./zabbix-api.js"; import {ZabbixAPI} from "./zabbix-api.js";
import { import {
isZabbixErrorResult, isZabbixErrorResult,
@ -8,8 +8,7 @@ import {
ZabbixRequest, ZabbixRequest,
ZabbixResult ZabbixResult
} from "./zabbix-request.js"; } from "./zabbix-request.js";
import {QueryZabbixItemResponse} from "./zabbix-items.js"; import {ZabbixHistoryGetParams, ZabbixQueryHistoryRequest} from "./zabbix-history.js";
import {ZabbixExportValue, ZabbixHistoryGetParams, ZabbixQueryHistoryRequest} from "./zabbix-history.js";
export class ZabbixQueryHostsGenericRequest<T extends ZabbixResult> extends ZabbixRequest<T> { export class ZabbixQueryHostsGenericRequest<T extends ZabbixResult> extends ZabbixRequest<T> {
@ -244,9 +243,7 @@ class ZabbixCreateHostParams implements ZabbixParams {
} }
export class ZabbixCreateHostRequest extends ZabbixRequest<{ export class ZabbixCreateHostRequest extends ZabbixRequest<CreateHostResponse> {
hostids: number[]
}> {
constructor(authToken?: string | null, cookie?: string) { constructor(authToken?: string | null, cookie?: string) {
super("host.create", authToken, cookie); super("host.create", authToken, cookie);
} }

View file

@ -1,4 +1,5 @@
import {ParsedArgs, ZabbixParams, ZabbixRequest, ZabbixResult, ZabbixValueType} from "./zabbix-request.js"; import {ParsedArgs, ZabbixParams, ZabbixRequest, ZabbixResult, ZabbixValueType} from "./zabbix-request.js";
import {ZabbixItem} from "../schema/generated/graphql";
export class ZabbixQueryItemsMetaRequest extends ZabbixRequest<any> { export class ZabbixQueryItemsMetaRequest extends ZabbixRequest<any> {
createZabbixParams(args?: ParsedArgs) { createZabbixParams(args?: ParsedArgs) {
@ -13,27 +14,8 @@ export class ZabbixQueryItemsMetaRequest extends ZabbixRequest<any> {
} }
} }
export type QueryZabbixItemResponse = {
value_type: string;
itemid: string,
name: string,
status?: string,
key_?: string,
lastvalue: string | null
lastclock: string | null
tags?: {
tag: string,
value: string
}[]
hosts?: {
hostid: number,
host: string,
templateid?: number,
name: string
}[]
}
export class ZabbixQueryItemsRequest extends ZabbixRequest<QueryZabbixItemResponse[]> { export class ZabbixQueryItemsRequest extends ZabbixRequest<ZabbixItem[]> {
constructor(authToken?: string | null, cookie?: string) { constructor(authToken?: string | null, cookie?: string) {
super("item.get", authToken, cookie); super("item.get", authToken, cookie);
} }
@ -66,7 +48,7 @@ export class ZabbixQueryItemsRequest extends ZabbixRequest<QueryZabbixItemRespon
} }
export class ZabbixQueryItemsByIdRequest extends ZabbixRequest<QueryZabbixItemResponse[]> { export class ZabbixQueryItemsByIdRequest extends ZabbixRequest<ZabbixItem[]> {
constructor(authToken?: string | null, cookie?: string) { constructor(authToken?: string | null, cookie?: string) {
super("item.get.itembyid", authToken, cookie); super("item.get.itembyid", authToken, cookie);
} }

View file

@ -1,5 +1,5 @@
import {ParsedArgs, ZabbixParams, ZabbixRequest} from "./zabbix-request.js"; import {ParsedArgs, ZabbixParams, ZabbixRequest} from "./zabbix-request.js";
import {UserRoleModule} from "../generated/graphql.js"; import {UserRoleModule} from "../schema/generated/graphql.js";
export class ZabbixQueryModulesRequest extends ZabbixRequest<UserRoleModule[]> { export class ZabbixQueryModulesRequest extends ZabbixRequest<UserRoleModule[]> {
constructor(authToken?: string | null, cookie?: string | null) { constructor(authToken?: string | null, cookie?: string | null) {

View file

@ -1,4 +1,4 @@
import {ApiError, InputMaybe, QueryHasPermissionsArgs, UserPermission} from "../generated/graphql.js"; import {ApiError, InputMaybe, QueryHasPermissionsArgs, UserPermission} from "../schema/generated/graphql.js";
import {ZabbixAPI} from "./zabbix-api.js"; import {ZabbixAPI} from "./zabbix-api.js";
import {ApiErrorCode, Permission, PermissionNumber} from "../model/model_enum_values.js"; import {ApiErrorCode, Permission, PermissionNumber} from "../model/model_enum_values.js";
import {logger} from "../logging/logger.js"; import {logger} from "../logging/logger.js";

View file

@ -15,7 +15,7 @@ import {
UserGroupInput, UserGroupInput,
ZabbixGroupRight, ZabbixGroupRight,
ZabbixGroupRightInput ZabbixGroupRightInput
} from "../generated/graphql.js"; } from "../schema/generated/graphql.js";
import {ZabbixAPI} from "./zabbix-api.js"; import {ZabbixAPI} from "./zabbix-api.js";
import {ZabbixQueryTemplateGroupRequest, ZabbixQueryTemplateGroupResponse} from "./zabbix-templates.js"; import {ZabbixQueryTemplateGroupRequest, ZabbixQueryTemplateGroupResponse} from "./zabbix-templates.js";
import {ZabbixQueryHostgroupsRequest, ZabbixQueryHostgroupsResult} from "./zabbix-hostgroups.js"; import {ZabbixQueryHostgroupsRequest, ZabbixQueryHostgroupsResult} from "./zabbix-hostgroups.js";

View file

@ -8,7 +8,7 @@ import {
ZabbixRequest, ZabbixRequest,
ZabbixResult ZabbixResult
} from "./zabbix-request.js"; } from "./zabbix-request.js";
import {ApiError, ImportUserRightResult, UserRole, UserRoleInput, UserRoleModule} from "../generated/graphql.js"; import {ApiError, ImportUserRightResult, UserRole, UserRoleInput, UserRoleModule} from "../schema/generated/graphql.js";
import {ZabbixAPI} from "./zabbix-api.js"; import {ZabbixAPI} from "./zabbix-api.js";
import {ZabbixQueryModulesRequest} from "./zabbix-module.js"; import {ZabbixQueryModulesRequest} from "./zabbix-module.js";
import {ApiErrorCode} from "../model/model_enum_values.js"; import {ApiErrorCode} from "../model/model_enum_values.js";

View file

@ -1,11 +1,11 @@
import { import {
ApiError, ApiError,
HistoryExportResponse, QueryExportHostValueHistoryArgs, GenericResponse, QueryExportHostValueHistoryArgs,
StorageItemType StorageItemType, ZabbixItem
} from "../generated/graphql.js"; } from "../schema/generated/graphql.js";
import {ApiErrorCode, ApiErrorMessage} from "../model/model_enum_values.js"; import {ApiErrorCode, ApiErrorMessage} from "../model/model_enum_values.js";
import {QueryZabbixItemResponse, ZabbixQueryItemsRequest} from "../datasources/zabbix-items.js"; import {ZabbixQueryItemsRequest} from "../datasources/zabbix-items.js";
import {isZabbixErrorResult, ParsedArgs, ZabbixErrorResult} from "../datasources/zabbix-request.js"; import {isZabbixErrorResult, ParsedArgs, ZabbixErrorResult} from "../datasources/zabbix-request.js";
import {ZabbixHistoryGetParams, ZabbixQueryHistoryRequest} from "../datasources/zabbix-history.js"; import {ZabbixHistoryGetParams, ZabbixQueryHistoryRequest} from "../datasources/zabbix-history.js";
import {zabbixAPI} from "../datasources/zabbix-api"; import {zabbixAPI} from "../datasources/zabbix-api";
@ -20,7 +20,7 @@ type ItemMapResponse = {
} }
export class HostValueExporter { export class HostValueExporter {
static async exportHistory(args: QueryExportHostValueHistoryArgs, zabbixAuthToken?: string, cookie?: string): Promise<HistoryExportResponse> { static async exportHistory(args: QueryExportHostValueHistoryArgs, zabbixAuthToken?: string, cookie?: string): Promise<GenericResponse> {
let itemMapResponse: ItemMapResponse = await HostValueExporter.queryItemsForFilterArgs(args, zabbixAuthToken, cookie); let itemMapResponse: ItemMapResponse = await HostValueExporter.queryItemsForFilterArgs(args, zabbixAuthToken, cookie);
if (itemMapResponse.error || !itemMapResponse.items) { if (itemMapResponse.error || !itemMapResponse.items) {
return { return {
@ -77,7 +77,7 @@ export class HostValueExporter {
let hostFilter = args.host_filter let hostFilter = args.host_filter
let itemKeyFilter = args.itemKey_filter let itemKeyFilter = args.itemKey_filter
let items: QueryZabbixItemResponse[] | ZabbixErrorResult = await new ZabbixQueryItemsRequest(zabbixAuthToken, cookie) let items: ZabbixItem[] | ZabbixErrorResult = await new ZabbixQueryItemsRequest(zabbixAuthToken, cookie)
.executeRequestReturnError(zabbixAPI, new ParsedArgs( .executeRequestReturnError(zabbixAPI, new ParsedArgs(
{ {
filter: { filter: {

View file

@ -1,9 +1,10 @@
import { import {
CreateHost, CreateHost,
CreateHostResponse, CreateHostGroup,
CreateHostGroupResponse, CreateHostGroupResponse,
InputMaybe,CreateHostGroup ImportHostResponse,
} from "../generated/graphql.js"; InputMaybe
} from "../schema/generated/graphql.js";
import {logger} from "../logging/logger.js"; import {logger} from "../logging/logger.js";
import {ZabbixQueryTemplatesRequest} from "../datasources/zabbix-templates.js"; import {ZabbixQueryTemplatesRequest} from "../datasources/zabbix-templates.js";
import {isZabbixErrorResult, ParsedArgs, ZabbixErrorResult} from "../datasources/zabbix-request.js"; import {isZabbixErrorResult, ParsedArgs, ZabbixErrorResult} from "../datasources/zabbix-request.js";
@ -89,7 +90,7 @@ export class HostImporter {
if (!hosts) { if (!hosts) {
return null return null
} }
let result: CreateHostResponse[] = [] let result: ImportHostResponse[] = []
for (let device of hosts) { for (let device of hosts) {
let groupids = device.groupids let groupids = device.groupids
if (!groupids) { if (!groupids) {

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,110 @@
########################################################
# Request/response
########################################################
type GenericResponse {
result: [JSONObject!]
error: ApiError
}
type ApiError implements Error {
code: Int
message: String
data: JSONObject
path: String
args: JSONObject
}
interface Error {
code: Int
message: String
data: JSONObject
}
########################################################
# User permissions
########################################################
input PermissionRequest {
permission: Permission!,
"""
objectName maps to name / path suffix of the template group representing the permission in Zabbix:
Permissions/{objectName}
"""
objectName: String!
}
"""
READ, READ_WRITE or DENY:
describes the rights related to an objectName
There is no EXECUTE or anything else in Zabbix,
i.e. objectName - Tree has to be designed accordingly in order to represent the perform actions.
E.g.
Let's assume a button called "button1", used in application "app1", having a label which shows "do something". Instead of model the action "do something" the idea is
to model the effect of this action - do something will result in a status change. Let's further assume that the button will only
be displayed if the user is allowed to see the current status.
Permissions/app1/button1/status
The following PermissionRequests would be used by the frontend:
1. Should the button (and its status) be displayed at all?
button1.displayed = hasPermissions(
{
objectName: "app1/button1/status"
permission: READ
})
2. Should the user be able to press the button (enabled)?
button1.displayed = hasPermissions(
{
objectName: "app1/button1/status"
permission: READ_WRITE
})
Usage Example for this pattern: Activation/Deactivation of a control program - the button
shows the possible action. If the program is active it shows "Deactivate". If the program is inactive it shows "Activate".
From this label the user learns something about the current state - therefore the status - read - permission is needed in order
to display the button at all. The status write permission is needed in order to enable the button (i.e. allow the user to press it).
in order to model the permissions to press / see a button "button1" belonging to application "app1"
the following template group could be modelled in Zabbix
"""
enum Permission {
"""
DENY superseeds anything else - i.e. if in Zabbix there is a DENY and READ at the same time the result will be DENY
"""
DENY
"""
READ superseeds READ_WRITE - i.e. if in Zabbix there is a READ_WRITE and READ at the same time the resulting permission
level will be READ
"""
READ
"""
READ_WRITE implies the READ permission. Do not set both READ and READ_WRITE at the same time if you want to achieve
READ + WRITE permission, because in this case READ will superseed the READ_WRITE and the resulting permission level will be READ
"""
READ_WRITE
}
type UserPermission {
permission: Permission!,
"""
objectName maps to name / path suffix of the template group representing the permission in Zabbix:
Permissions/{objectName}
"""
objectName: String!
}
############################################################################
# General purpose types + enums
############################################################################
enum SortOrder {
"Deliver values in ascending order"
asc
"Deliver values in descending order"
desc
}

View file

@ -1,6 +1,6 @@
""" """
Represents a message containing information about a specific device and its associated data value. Represents a message containing information about a specific device and its associated data value.
The class is designed to be extended by other classes that define more structured or specialized types The interface is designed to be extended by other types that define more structured or specialized types
of device value messages. of device value messages.
""" """
interface DeviceValueMessage { interface DeviceValueMessage {

View file

@ -0,0 +1,58 @@
"""
(IoT / Edge - ) Devices are hosts having a state containing the "output" / the business data which is exposed
besides monitoring information.
"""
interface Device implements Host {
hostid: ID!
"""
Per convention a uuid is used as hostname to identify devices if they do not have a unique hostname
"""
host: String!
deviceType: String
hostgroups: [HostGroup!]
name: String
tags: JSONObject
state: DeviceState
}
type OperationalDeviceData {
temperature: Float
voltage: Float
signalstrength: Float
location: Location
timestamp: DateTime
error: [ErrorPayload!]
}
type ErrorPayload {
code: Int!
message: String
additionalInfo: JSONObject
}
interface DeviceState {
operational: OperationalDeviceData
}
# Generic IoT devices with "generic" current state - mapping all "values"
type GenericDeviceState implements DeviceState {
operational: OperationalDeviceData
current: JSONObject
}
"""
Device represents generic IoT / Edge - devices providing their state as generic "state.current" - JSON Object
"""
type GenericDevice implements Host & Device {
hostid: ID!
"""
Per convention a uuid is used as hostname to identify devices if they do not have a unique hostname
"""
host: String!
deviceType: String
hostgroups: [HostGroup!]
name: String
tags: JSONObject
state: GenericDeviceState
}

View file

@ -0,0 +1,171 @@
type Mutation {
"""
Authentication: By zbx_session - cookie or zabbix-auth-token - header
"""
createHost(host: String!, hostgroupids:[Int!]!, templateids: [Int!]!,
location: LocationInput): CreateHostResponse
"""
(Mass) Import zabbix groups
and assign them to the corresponding hosts by groupid or groupName.
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
"""
importHostGroups(hostGroups: [CreateHostGroup!]!):[CreateHostGroupResponse!]
"""
(Mass) Import hosts and assign them to host groups by groupid or groupName.
Return value: If no error occurs a hostid will be returned for each created host,
otherwise the return object will contain an error message.
Authentication: By zbx_session - cookie or zabbix-auth-token - header
"""
importHosts(hosts: [CreateHost!]!):[ImportHostResponse!]
importUserRights(input: UserRightsInput!, dryRun: Boolean! = true): ImportUserRightsResult
}
####################################################################
# Input types used for importXXX - and storeXXX - Mutations
####################################################################
input CreateHostGroup {
"""
Name of the host group
"""
groupName: String!
"""
Internally used unique id
(will be assigned by Zabbix if empty)
"""
uuid: String
}
type ImportHostResponse {
deviceKey: String!
hostid: String
message: String
error: ApiError
}
type CreateHostGroupResponse {
groupName: String!
groupid: Int
message: String
error: ApiError
}
input CreateHost {
deviceKey: String!
"""
Optional display name of the device (must be unique if provided - default is to set display name to deviceKey)
"""
name: String
deviceType: String!
"""
groupNames is used to assign the created object
to a host group. It is mandatory but
can also be blank. This is usefull in case of
passing a groupid instead which is
the zabbix internal key for storing the group.
If a groupid is provided the passed groupName is ignored
"""
groupNames: [String!]!
"""
Optionally the internal groupids can be passed - in this case the
groupName is ignored
"""
groupids: [Int]
location: LocationInput
}
type CreateHostResponse {
hostids: [Int]
itemids: [Int]
error: ApiError
}
input LocationInput {
name: String
location_lat: String
location_lon: String
}
#######################################
# Permission related input
#######################################
input UserRightsInput {
userRoles: [UserRoleInput!]
userGroups: [UserGroupInput!]
}
input UserRoleInput {
name: String
type: Int
readonly: Int
rules: UserRoleRulesInput
}
input UserRoleRulesInput {
ui: [UserRoleRuleInput!]
ui_default_access: Int
modules:[UserRoleModuleInput!]
modules_default_access: Int
api_access: Int
api_mode: Int
api: [String!]
actions: [UserRoleRuleInput!]
actions_default_access: Int
}
input UserRoleRuleInput {
name: String
status: Int
}
input UserRoleModuleInput {
moduleid: String
status: Int
id: String
}
input UserGroupInput {
name: String!
gui_access: Int
users_status: Int
hostgroup_rights: [ZabbixGroupRightInput!]
templategroup_rights: [ZabbixGroupRightInput!]
}
input ZabbixGroupRightInput {
uuid: String
"""
name may optionally be specified for documentation purpose,
but the master for setting the user right is the uuid.
If a uuid is found and the corresponding group
has a deviating name this will be documented within a message
with errorcode = 0 (OK) but the permission will be set (
the reason is that names for groups may deviate between several
instances of the control center although the semantic is the same -
while the semantic is identified by uuid.
"""
name: String
permission: Permission
}
type ImportUserRightsResult {
userRoles: [ImportUserRightResult!]
userGroups: [ImportUserRightResult!]
}
type ImportUserRightResult {
id: String
name: String
message: String
errors: [ApiError!]
}

107
src/schema/queries.graphql Normal file
View file

@ -0,0 +1,107 @@
# Scalars resolved by package "graphql-scalars"
scalar DateTime
scalar Time
scalar JSONObject
type Query {
"Get api (build) version"
apiVersion: String!
"Get zabbix version"
zabbixVersion: String
"""
Login to zabbix - provided for debugging and testing purpose. The result of the login operation is
authentication token returned may be passed as
header 'zabbix-auth-token' for authenticating future API requests.
As an alternative to the cookie 'zbx_session' may be set which is automatically set after login to
the cockpit - this is the standard way to authenticate api calls initiated by the cockpit frontend
because the frontend is always embedded into the Zabbix portal which is only accessible after logging in and
obtainind the zbx_session - cookie.
"""
login(username: String!, password: String!): String
"""
Logout from zabbix - provided for debugging and testing purpose. This invalidates the token received by the login
operation. Returns true on success
"""
logout: Boolean
"""
Get all hosts + corresponding items. If with_items==true only hosts with attached items are delivered
name_pattern: If provided this will perform a LIKE "%…%" search on the name attribute within the database.
Authentication: By zbx_session - cookie or zabbix-auth-token - header
"""
allHosts(name_pattern: String = "", filter_host: String = null, hostids: Int,
groupids:[Int!] = null, with_items: Boolean = false, tag_deviceType:[String]=[], tag_hostType:[String!]): [Host]
"""
Get all host groups.
If with_hosts==true only groups with attached hosts are delivered.
Authentication: By zbx_session - cookie or zabbix-auth-token - header
"""
allHostGroups(search_name: String, with_hosts: Boolean = true): [HostGroup]
"""
Get all locations used by hosts.
distinct_by_name=true means that the result is filtered for distinct names (default)
name_pattern: If provided this will perform a Regex search on the name attribute within the database.
Authentication: By zbx_session - cookie or zabbix-auth-token - header
"""
locations(name_pattern: String = "", distinct_by_name: Boolean = true, templateids:[String] = null): [Location]
"""
Export history from Zabbix items
Authentication: By zbx_session - cookie or zabbix-auth-token - header
"""
exportHostValueHistory(
"(Optional) list of hostnames to be included in the result"
host_filter: [String!],
"(Optional) list of item keys to be included in the result"
itemKey_filter: [String!],
"""
(Optional) timestamp of earliest value"""
time_from: DateTime,
"""(Optional) timestamp of last value """
time_until: DateTime,
"""Results are sorted by timestamps - ascending or descending order may be specified
using this parameter"""
sortOrder: SortOrder=desc,
"""
Maximum number of records to be delivered. Hint: This might be useful, because the
current version of Zabbix delivers a 500 - error in case of requesting too much data
"""
limit: Int
"""
As values are stored in different data structures depending on their type
the type information must be specified in advance, although
each value (also if number) is converted into a string afterwards
"""
type: StorageItemType = FLOAT
):GenericResponse
"""
Return all user permissions. If objectNames is provided return only the permissions related to the objects within
the objectNames - list
"""
userPermissions(objectNames: [String!]): [UserPermission!]
"""
Return true if and only if the current user (identified by token / cookie)
has all requested permissions (minimum - if READ is requested and the user has READ_WRITE
the response will be true)
"""
hasPermissions(permissions: [PermissionRequest!]!): Boolean
"""
name_pattern: If provided this will perform a LIKE "%…%" search on the name attribute within the database.
exclude_groups_pattern: Regex allowing to exclude all matching hostgroups from group permissions
"""
exportUserRights(name_pattern: String = "" exclude_hostgroups_pattern: String = ""): UserRights
}

155
src/schema/zabbix.graphql Normal file
View file

@ -0,0 +1,155 @@
###################################
# Hosts, items + groups, templates
###################################
type HostGroup {
groupid: ID!
name: String
}
interface Host {
hostid: ID!
"""
The host field contains the "hostname" in Zabbix
"""
host: String!
hostgroups: [HostGroup!]
name: String
tags: JSONObject
}
type ZabbixItem {
itemid: Int!
name: String!
key_: String!
hostid: Int
lastclock: Int
lastvalue: String
value_type: Int!
attributeName: String
status: DeviceStatus
type: DeviceCommunicationType
hosts: [Host!]
}
enum DeviceCommunicationType {
ZABBIX_AGENT
ZABBIX_AGENT_ACTIVE
ZABBIX_TRAP
ZABBIX_INTERNAL_ITEM
SIMPLE_CHECK
DEPENDANT_ITEM
SIMULATOR_CALCULATED
SIMULATOR_JAVASCRIPT
HTTP_AGENT
IPMI_AGENT
JMX_AGENT
SNMP_AGENT
SNMP_TRAP
DATABASE_MONITOR
}
type ZabbixHost implements Host {
hostid: ID!
host: String!
hostgroups: [HostGroup!]
name: String
tags: JSONObject
items: [ZabbixItem!]
inventory: Inventory
parentTemplates: [Template!]
}
type Template {
templateid: String!
name: String
}
type Inventory {
location: Location
}
"""
Hint: WGS84[dd.ddddd] coordinates are used
"""
interface GpsPosition {
latitude: Float
longitude: Float
}
type Location implements GpsPosition {
name: String
latitude: Float
longitude: Float
}
enum DeviceStatus {
ENABLED
DISABLED
}
########################################################
# History / Values
########################################################
enum StorageItemType {
FLOAT
INT
TEXT
}
############################
# Permissions
############################
type UserRights {
userGroups: [UserGroup!]
userRoles: [UserRole!]
}
type UserRole {
roleid: Int!
name: String
type: Int
readonly: Int
rules: UserRoleRules
}
type UserRoleRules {
ui: [UserRoleRule!]
ui_default_access: Int
modules:[UserRoleModule!]
modules_default_access: Int
api_access: Int
api_mode: Int
api: [String!]
actions: [UserRoleRule!]
actions_default_access: Int
}
type UserRoleRule {
name: String
status: Int
}
type UserRoleModule {
moduleid: String
status: Int
id: String
relative_path: String
}
type UserGroup {
usrgrpid: Int!
name: String!
gui_access: Int
users_status: Int
hostgroup_rights: [ZabbixGroupRight!]
templategroup_rights: [ZabbixGroupRight!]
}
type ZabbixGroupRight {
id: Int!
uuid: String
name: String
permission: Permission
}