zabbix-graphql-api/src/datasources/zabbix-history.ts
Andreas Hilbig fb5e9cbe81 feat: improve Zabbix multi-version compatibility and introduce local development environment
This update enhances compatibility across multiple Zabbix versions and introduces tools for easier local development and testing.

Key improvements and verified version support:
- Verified Zabbix version support: 6.2, 6.4, 7.0, and 7.4.
- Version-specific feature handling:
  - `history.push` is enabled only for Zabbix 7.0+; older versions skip it with a clear error or notice.
  - Conditional JSON-RPC authentication: the `auth` field is automatically added to the request body for versions older than 6.4.
  - Implemented static Zabbix version caching in the datasource to minimize redundant API calls.
- Query optimization refinements:
  - Added mapping for implied fields (e.g., `state` -> `items`, `deviceType` -> `tags`).
  - Automatically prune unnecessary Zabbix parameters (like `selectItems` or `selectTags`) when not requested.
- Local development environment:
  - Added a new `zabbix-local` Docker Compose profile that includes PostgreSQL, Zabbix Server, and Zabbix Web.
  - Supports testing different versions by passing the `ZABBIX_VERSION` environment variable (e.g., 6.2, 6.4, 7.0, 7.4).
  - Provided a sample environment file at `samples/zabbix-local.env`.
- Documentation and Roadmap:
  - Updated README with a comprehensive version compatibility matrix and local environment instructions.
  - Created a new guide: `docs/howtos/local_development.md`.
  - Updated maintenance guides and added "Local Development Environment" as an achieved milestone in the roadmap.
- Test suite enhancements:
  - Improved Smoketest and RegressionTest executors with more reliable resource cleanup and error reporting.
  - Made tests version-aware to prevent failures on older Zabbix instances.

BREAKING CHANGE: Dropped Zabbix 6.0 specific workarounds; the minimum supported version is now 6.2.
2026-02-04 14:01:53 +01:00

114 lines
4.1 KiB
TypeScript

import {ApiError, SortOrder, StorageItemType} from "../schema/generated/graphql.js";
import {isZabbixErrorResult, ParsedArgs, ZabbixErrorResult, ZabbixParams, ZabbixRequest, ZabbixResult} from "./zabbix-request.js";
import {ZabbixAPI} from "./zabbix-api.js";
import {GraphQLError} from "graphql";
export interface ZabbixValue {
itemid?: string,
key?: string,
host?: string,
value: string,
clock: number,
ns: number
}
export interface ZabbixExportValue extends ZabbixValue, ZabbixResult {
itemid?: string
}
export class ZabbixHistoryGetParams extends ParsedArgs {
time_from_ms: number | undefined
time_till_ms: number | undefined
constructor(public itemids: number[] | number | string | string[],
public output: string[] = ["value", "itemid", "clock", "ns"],
public limit: number | null = Array.isArray(itemids) ? itemids.length : 1,
public history: StorageItemType | string = StorageItemType.Text,
time_from?: Date,
time_until?: Date,
public sortfield: string[] = ["clock", "ns"],
public sortorder: SortOrder | null = SortOrder.Desc,
) {
super();
this.time_from_ms = time_from ? Math.floor(new Date(time_from).getTime() / 1000) : undefined
this.time_till_ms = time_until ? Math.floor(new Date(time_until).getTime() / 1000) : undefined
}
}
export class ZabbixQueryHistoryRequest extends ZabbixRequest<ZabbixExportValue[], ZabbixHistoryGetParams> {
constructor(authToken?: string | null, cookie?: string | null) {
super("history.get", authToken, cookie);
}
createZabbixParams(args?: ZabbixHistoryGetParams): ZabbixParams {
return {
itemids: args?.itemids,
output: args?.output,
limit: args?.limit,
history: args?.history?.valueOf(),
sortfield: args?.sortfield,
sortorder: args?.sortorder == SortOrder.Asc ? "ASC" : "DESC",
time_from: args?.time_from_ms,
time_till: args?.time_till_ms,
}
}
}
export interface ZabbixHistoryPushInput {
timestamp: string
value: any,
}
export interface ZabbixHistoryPushResult {
response: string,
data: { itemid: string, error?: string[] | ApiError }[],
error?: ApiError | string[]
}
export class ZabbixHistoryPushParams extends ParsedArgs {
constructor(public values: ZabbixHistoryPushInput[], public itemid?: string,
public key?: string,
public host?: string,) {
super();
}
}
export class ZabbixHistoryPushRequest extends ZabbixRequest<ZabbixHistoryPushResult, ZabbixHistoryPushParams> {
constructor(authToken?: string | null, cookie?: string) {
super("history.push", authToken, cookie);
}
async prepare(zabbixAPI: ZabbixAPI, args?: ZabbixHistoryPushParams): Promise<ZabbixHistoryPushResult | ZabbixErrorResult | undefined> {
if (!args) return undefined;
const version = await zabbixAPI.getVersion();
if (version < "7.0.0") {
throw new GraphQLError(`history.push is only supported in Zabbix 7.0.0 and newer. Current version is ${version}. For older versions, please use Zabbix trapper items and zabbix_sender protocol.`);
}
if (!args.itemid && (!args.key || !args.host)) {
throw new GraphQLError("if itemid is empty both key and host must be filled");
}
return super.prepare(zabbixAPI, args);
}
createZabbixParams(args?: ZabbixHistoryPushParams): ZabbixParams {
if (!args) return [];
return args.values.map(v => {
const date = new Date(v.timestamp);
const result: any = {
value: typeof v.value === 'string' ? v.value : JSON.stringify(v.value),
clock: Math.floor(date.getTime() / 1000),
ns: (date.getTime() % 1000) * 1000000
};
if (args.itemid) {
result.itemid = args.itemid;
} else {
result.host = args.host;
result.key = args.key;
}
return result as ZabbixValue;
});
}
}