diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 58392a8..ad6f159 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -4,17 +4,12 @@
-
+
-
-
+
-
-
-
-
-
+
@@ -120,7 +115,7 @@
-
+
@@ -353,7 +348,15 @@
1769525714791
-
+
+
+ 1769531308340
+
+
+
+ 1769531308340
+
+
@@ -393,7 +396,8 @@
-
+
+
diff --git a/README.md b/README.md
index 01d2740..71b50a7 100644
--- a/README.md
+++ b/README.md
@@ -58,6 +58,8 @@ The API is configured via environment variables. Create a `.env` file or set the
| `ZABBIX_EDGE_DEVICE_BASE_GROUP` | Base host group for devices | `Roadwork` |
| `ZABBIX_PERMISSION_TEMPLATE_GROUP_NAME_PREFIX` | Prefix for template groups used as permissions | `Permissions` |
| `SCHEMA_PATH` | Path to the directory containing `.graphql` schema files | `./schema/` |
+| `HOST_GROUP_FILTER_DEFAULT` | Default search pattern for `allHostGroups` query | |
+| `HOST_TYPE_FILTER_DEFAULT` | Default value for `tag_hostType` filter in `allHosts` and `allDevices` queries | |
### Starting the API
@@ -187,6 +189,54 @@ This allows for fine-grained access control in your frontend or external applica
For a complete example of how to import these permission groups, see the [Permissions Template Groups Import Sample](docs/sample_import_permissions_template_groups_mutation.graphql).
+## Host Classification & Filtering
+
+The API leverages Zabbix tags to classify hosts and devices, enabling efficient filtering and multi-tenancy support.
+
+### The `hostType` Tag
+
+The `hostType` tag is used to categorize hosts and templates. This allows the API to provide default filters for specific application domains or device categories.
+
+#### How to set the Host Type in Zabbix:
+
+To classify a host or a template, simply add a tag in the Zabbix UI or via the API:
+* **Tag Name**: `hostType`
+* **Tag Value**: A string representing the category (e.g., `Roadwork/Devices`, `SmartCity/Sensors`).
+
+This tag can be defined:
+1. **Directly on the Host**: Specific to that individual device.
+2. **On a Template**: All hosts linked to this template will inherit the classification.
+
+### Default Filtering with `HOST_TYPE_FILTER_DEFAULT`
+
+By configuring the `HOST_TYPE_FILTER_DEFAULT` environment variable, you can set a global default for the `allHosts` and `allDevices` queries.
+
+* If `HOST_TYPE_FILTER_DEFAULT=Roadwork/Devices` is set, a query like `allHosts { host }` will only return hosts that have the `hostType` tag set to `Roadwork/Devices`.
+* This default can always be overridden in the GraphQL query by explicitly passing the `tag_hostType` argument.
+
+### Search Filtering with `HOST_GROUP_FILTER_DEFAULT`
+
+The `HOST_GROUP_FILTER_DEFAULT` variable provides a default search pattern for the `allHostGroups` query. This is particularly useful for restricting the visible host group hierarchy to a specific subtree by default.
+
+#### Overriding the Default Filter
+
+The default filter can be overridden by explicitly providing the `search_name` argument in the `allHostGroups` query. When `search_name` is present, the environment variable is ignored.
+
+#### Using Wildcards
+
+The `search_name` parameter supports the `*` wildcard (enabled via the Zabbix API's `searchWildcardsEnabled` feature). This allows you to search for all subgroups within a specific path.
+
+**Example**: To find all subgroups of `Roadwork/Devices/`, use the following query:
+
+```graphql
+query {
+ allHostGroups(search_name: "Roadwork/Devices/*") {
+ groupid
+ name
+ }
+}
+```
+
## Sample Application: Virtual Control Room (VCR)
The **Virtual Control Room (VCR)** is a professional cockpit and control center application designed for monitoring and managing large-scale deployments of IoT and Edge devices, such as traffic management systems, roadwork safety equipment, and environmental sensors.
@@ -214,6 +264,8 @@ ZABBIX_AUTH_TOKEN=your-super-admin-token-here
ZABBIX_EDGE_DEVICE_BASE_GROUP=Roadwork
API_VERSION=1.0.0
SCHEMA_PATH=./schema/
+HOST_GROUP_FILTER_DEFAULT=Roadwork/Devices/*
+HOST_TYPE_FILTER_DEFAULT=Roadwork/Devices
# Schema Extensions (No-Code)
ADDITIONAL_SCHEMAS=./schema/extensions/display_devices.graphql,./schema/extensions/location_tracker_devices.graphql,./schema/extensions/location_tracker_commons.graphql
diff --git a/src/api/resolvers.ts b/src/api/resolvers.ts
index 025a5b8..e55c45a 100644
--- a/src/api/resolvers.ts
+++ b/src/api/resolvers.ts
@@ -97,7 +97,9 @@ export function createResolvers(): Resolvers {
zabbixAuthToken,
cookie, dataSources
}: any) => {
- args.tag_hostType ??= [ZABBIX_EDGE_DEVICE_BASE_GROUP];
+ if (Config.HOST_TYPE_FILTER_DEFAULT) {
+ args.tag_hostType ??= [Config.HOST_TYPE_FILTER_DEFAULT];
+ }
return await new ZabbixQueryHostsRequestWithItemsAndInventory(zabbixAuthToken, cookie)
.executeRequestThrowError(
dataSources.zabbixAPI, new ParsedArgs(args)
@@ -107,8 +109,9 @@ export function createResolvers(): Resolvers {
zabbixAuthToken,
cookie, dataSources
}: any) => {
- args.tag_hostType ??= [ZABBIX_EDGE_DEVICE_BASE_GROUP];
-
+ if (Config.HOST_TYPE_FILTER_DEFAULT) {
+ args.tag_hostType ??= [Config.HOST_TYPE_FILTER_DEFAULT];
+ }
return await new ZabbixQueryDevices(zabbixAuthToken, cookie)
.executeRequestThrowError(
dataSources.zabbixAPI, new ZabbixQueryDevicesArgs(args)
@@ -118,8 +121,8 @@ export function createResolvers(): Resolvers {
zabbixAuthToken,
cookie
}: any) => {
- if (!args.search_name) {
- args.search_name = ZABBIX_EDGE_DEVICE_BASE_GROUP + "/*"
+ if (!args.search_name && Config.HOST_GROUP_FILTER_DEFAULT) {
+ args.search_name = Config.HOST_GROUP_FILTER_DEFAULT
}
return await new ZabbixQueryHostgroupsRequest(zabbixAuthToken, cookie).executeRequestThrowError(
zabbixAPI, new ZabbixQueryHostgroupsParams(args)
diff --git a/src/common_utils.ts b/src/common_utils.ts
index 58b75e1..937350c 100644
--- a/src/common_utils.ts
+++ b/src/common_utils.ts
@@ -14,4 +14,6 @@ static readonly DRY_RUN = process.env.DRY_RUN
static readonly ZABBIX_ROADWORK_BASE_GROUP = process.env.ZABBIX_ROADWORK_BASE_GROUP
static readonly ZABBIX_PERMISSION_TEMPLATE_GROUP_NAME_PREFIX = process.env.ZABBIX_PERMISSION_TEMPLATE_GROUP_NAME_PREFIX || "Permissions"
static readonly LOG_LEVELS = process.env.LOG_LEVELS
+ static readonly HOST_TYPE_FILTER_DEFAULT = process.env.HOST_TYPE_FILTER_DEFAULT;
+ static readonly HOST_GROUP_FILTER_DEFAULT = process.env.HOST_GROUP_FILTER_DEFAULT;
}
\ No newline at end of file
diff --git a/src/test/host_query.test.ts b/src/test/host_query.test.ts
index 98e8dd2..9b256d7 100644
--- a/src/test/host_query.test.ts
+++ b/src/test/host_query.test.ts
@@ -2,6 +2,7 @@
import {createResolvers} from "../api/resolvers.js";
import {zabbixAPI, ZABBIX_EDGE_DEVICE_BASE_GROUP} from "../datasources/zabbix-api.js";
import {QueryAllHostsArgs, QueryAllDevicesArgs, QueryAllHostGroupsArgs} from "../schema/generated/graphql.js";
+import {Config} from "../common_utils.js";
// Mocking ZabbixAPI
jest.mock("../datasources/zabbix-api.js", () => ({
@@ -14,6 +15,14 @@ jest.mock("../datasources/zabbix-api.js", () => ({
ZABBIX_EDGE_DEVICE_BASE_GROUP: "Roadwork"
}));
+// Mocking Config
+jest.mock("../common_utils.js", () => ({
+ Config: {
+ HOST_TYPE_FILTER_DEFAULT: "Roadwork",
+ HOST_GROUP_FILTER_DEFAULT: "Roadwork/%"
+ }
+}));
+
describe("Host and HostGroup Resolvers", () => {
let resolvers: any;
@@ -94,6 +103,27 @@ describe("Host and HostGroup Resolvers", () => {
}));
});
+ test("allHostGroups query - uses default search pattern", async () => {
+ const mockGroups = [{ groupid: "10", name: "Roadwork/Group 1" }];
+ (zabbixAPI.post as jest.Mock).mockResolvedValueOnce(mockGroups);
+
+ const args: QueryAllHostGroupsArgs = {};
+ const context = {
+ zabbixAuthToken: "test-token"
+ };
+
+ const result = await resolvers.Query.allHostGroups(null, args, context);
+
+ expect(result).toEqual(mockGroups);
+ expect(zabbixAPI.post).toHaveBeenCalledWith("hostgroup.get", expect.objectContaining({
+ body: expect.objectContaining({
+ params: expect.objectContaining({
+ search: { name: ["Roadwork/%"] }
+ })
+ })
+ }));
+ });
+
test("locations query", async () => {
const mockLocations = [{ name: "Loc 1", latitude: 1.0, longitude: 2.0 }];
(zabbixAPI.getLocations as jest.Mock).mockResolvedValueOnce(mockLocations);