Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 'zabbix trigger' monitor #3972

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions db/knex_init_db.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ async function createTables() {
table.integer("resend_interval").notNullable().defaultTo(0);
table.integer("packet_size").notNullable().defaultTo(56);
table.string("game", 255);
table.text("zabbix_instance_url");
table.text("zabbix_auth_token");
table.integer("zabbix_trigger_id");
Comment on lines +127 to +129
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is added below in the migration

Suggested change
table.text("zabbix_instance_url");
table.text("zabbix_auth_token");
table.integer("zabbix_trigger_id");

});

// heartbeat
Expand Down
19 changes: 19 additions & 0 deletions db/knex_migrations/2023-11-01-2149-patch-add-zabbix.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
exports.up = function (knex) {
// Create Zabbix Columns
return knex.schema
.alterTable("monitor", function (table) {
table.text("zabbix_instance_url");
table.text("zabbix_auth_token");
table.integer("zabbix_trigger_id");
});

};

exports.down = function (knex) {
return knex.schema
.alterTable("monitor", function (table) {
table.dropColumn("zabbix_instance_url");
table.dropColumn("zabbix_auth_token");
table.dropColumn("zabbix_trigger_id");
});
};
3 changes: 3 additions & 0 deletions server/model/monitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ class Monitor extends BeanModel {
kafkaProducerAllowAutoTopicCreation: this.getKafkaProducerAllowAutoTopicCreation(),
kafkaProducerMessage: this.kafkaProducerMessage,
screenshot,
zabbixInstanceUrl: this.zabbixInstanceUrl,
zabbixTriggerId: this.zabbixTriggerId,
remote_browser: this.remote_browser,
};

Expand Down Expand Up @@ -184,6 +186,7 @@ class Monitor extends BeanModel {
tlsCert: this.tlsCert,
tlsKey: this.tlsKey,
kafkaProducerSaslOptions: JSON.parse(this.kafkaProducerSaslOptions),
zabbixAuthToken: this.zabbixAuthToken,
};
}

Expand Down
64 changes: 64 additions & 0 deletions server/monitor-types/zabbix-trigger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
const { MonitorType } = require("./monitor-type");
const { UP, DOWN } = require("../../src/util");
const axios = require("axios");

class ZabbixTriggerMonitorType extends MonitorType {

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change

name = "zabbix-trigger";

/**
* @inheritdoc
*/
async check(monitor, heartbeat, _server) {

// Make Request
Comment on lines +13 to +14
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: lets link to the here docs instead

Suggested change
// Make Request
// see https://www.zabbix.com/documentation/current/en/manual/api/reference/trigger/get

const options = {
method: "post",
url: monitor.zabbixInstanceUrl,
data: {
"jsonrpc": "2.0",
"method": "trigger.get",
"params": {
"triggerids": monitor.zabbixTriggerId,
"output": "extend",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that we only care about a few outputs here, would this be better?

"output": [
    "triggerid",
    "value",
    "description",
    "comments",
]

"selectFunctions": "extend",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like selectFunctions is only adding extra infos about functions, which we don't use.
Am I reading this wrong or can we remove this?

},
// Will be deprecated, but Bearer Auth is currently not working
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are you meaning when you say "not working"?

Their docs state that Bearer Auth is the preferred option..
https://www.zabbix.com/documentation/current/en/manual/api#by-authorization-header

"auth": monitor.zabbixAuthToken,
"id": 1
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a bit unclear about what the id field does. They state that it is

an arbitrary identifier of the request

=> should we set it to a Random id on each request?

},
headers: {
"Content-Type": "application/json-rpc",
// In the future Authentication will use Bearer Token
"Authorization": `Bearer ${monitor.zabbixAuthToken}`,
}
};

// Send Request & Convert Data
let res = await axios(options);
let data = res.data;
Comment on lines +15 to +39
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Our codebase is mostly using the axios.post syntax.
Lets not change this here ^^

Suggested change
const options = {
method: "post",
url: monitor.zabbixInstanceUrl,
data: {
"jsonrpc": "2.0",
"method": "trigger.get",
"params": {
"triggerids": monitor.zabbixTriggerId,
"output": "extend",
"selectFunctions": "extend",
},
// Will be deprecated, but Bearer Auth is currently not working
"auth": monitor.zabbixAuthToken,
"id": 1
},
headers: {
"Content-Type": "application/json-rpc",
// In the future Authentication will use Bearer Token
"Authorization": `Bearer ${monitor.zabbixAuthToken}`,
}
};
// Send Request & Convert Data
let res = await axios(options);
let data = res.data;
const options = {
headers: {
"Content-Type": "application/json-rpc",
// In the future Authentication will use Bearer Token
"Authorization": `Bearer ${monitor.zabbixAuthToken}`,
}
};
const postData = {
"jsonrpc": "2.0",
"method": "trigger.get",
"params": {
"triggerids": monitor.zabbixTriggerId,
"output": "extend",
"selectFunctions": "extend",
},
// Will be deprecated, but Bearer Auth is currently not working
"auth": monitor.zabbixAuthToken,
"id": 1
};
const { data } = await axios.post(monitor.zabbixInstanceUrl, postData, options);


// convert data to object
if (typeof data === "string") {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs don't mention that this can be a string.
=> I think this check can be removed (I have not tried to set up zabbix => idk if the json-decoding is already done at this point)

data = JSON.parse(data);
}
let trigger = data.result[0];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let trigger = data.result[0];
// because we only allow one triggerId, at most one result is returned.
let trigger = data.result[0];

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this line misses that the result may also not be a monitor, or am I misunderstanding this monitor?


/*
Zabbix Trigger Value Mapping
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could not find a reference to this in their docs.
Are there more states that might be interesting?

The value also might be empty (at least it is in one of their examples). Do we have to pay special attention to this case?

Value 0 - ok
value 1 - Firing
*/
if ( trigger.value === "0" ) {
heartbeat.status = UP;
heartbeat.msg = `OK - ${ trigger.description }`;
} else {
heartbeat.status = DOWN;
heartbeat.msg = trigger.comments;
}
}
}

module.exports = {
ZabbixTriggerMonitorType,
};
3 changes: 3 additions & 0 deletions server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,9 @@ let needSetup = false;
bean.kafkaProducerAllowAutoTopicCreation =
monitor.kafkaProducerAllowAutoTopicCreation;
bean.gamedigGivenPortOnly = monitor.gamedigGivenPortOnly;
bean.zabbixInstanceUrl = monitor.zabbixInstanceUrl;
bean.zabbixAuthToken = monitor.zabbixAuthToken;
bean.zabbixTriggerId = monitor.zabbixTriggerId;
bean.remote_browser = monitor.remote_browser;

bean.validate();
Expand Down
2 changes: 2 additions & 0 deletions server/uptime-kuma-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ class UptimeKumaServer {
UptimeKumaServer.monitorTypeList["real-browser"] = new RealBrowserMonitorType();
UptimeKumaServer.monitorTypeList["tailscale-ping"] = new TailscalePing();
UptimeKumaServer.monitorTypeList["dns"] = new DnsMonitorType();
UptimeKumaServer.monitorTypeList["zabbix-trigger"] = new ZabbixTriggerMonitorType();

this.io = new Server(this.httpServer);
}
Expand Down Expand Up @@ -436,3 +437,4 @@ module.exports = {
const { RealBrowserMonitorType } = require("./monitor-types/real-browser-monitor-type");
const { TailscalePing } = require("./monitor-types/tailscale-ping");
const { DnsMonitorType } = require("./monitor-types/dns");
const { ZabbixTriggerMonitorType } = require("./monitor-types/zabbix-trigger");
6 changes: 5 additions & 1 deletion src/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -869,5 +869,9 @@
"useRemoteBrowser": "Use a Remote Browser",
"deleteRemoteBrowserMessage": "Are you sure want to delete this Remote Browser for all monitors?",
"GrafanaOncallUrl": "Grafana Oncall URL",
"Browser Screenshot": "Browser Screenshot"
"Browser Screenshot": "Browser Screenshot",
"GrafanaOncallUrl": "Grafana Oncall URL",
"zabbixInstanceUrl": "Zabbix Instance URL",
"zabbixAuthToken": "Zabbix Authentication Token",
"zabbixTriggerId": "Zabbix Trigger ID"
}
20 changes: 20 additions & 0 deletions src/pages/EditMonitor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@
<option value="redis">
Redis
</option>
<option value="zabbix-trigger">
Zabbix Trigger
</option>
<option v-if="!$root.info.isContainer" value="tailscale-ping">
Tailscale Ping
</option>
Expand Down Expand Up @@ -385,6 +388,23 @@
</div>
</template>

<template v-if="monitor.type === 'zabbix-trigger'">
<div class="my-3">
<label for="zabbix_instance_url" class="form-label">{{ $t("zabbixInstanceUrl") }}</label>
<input id="zabbix_instance_url" v-model="monitor.zabbixInstanceUrl" type="text" class="form-control" required />
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think here a placeholder what the url we expect is might be helpfull.

Suggested change
<input id="zabbix_instance_url" v-model="monitor.zabbixInstanceUrl" type="text" class="form-control" required />
<input id="zabbix_instance_url" v-model="monitor.zabbixInstanceUrl" type="text" class="form-control" placeholder="https://example.com/zabbix/api_jsonrpc.php" required />

</div>

<div class="my-3">
<label for="zabbix_trigger_id" class="form-label">{{ $t("zabbixTriggerId") }}</label>
<input id="zabbix_trigger_id" v-model="monitor.zabbixTriggerId" type="text" class="form-control" required />
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have never used zabbix.
Is it nessesary to add a helptext how to find the triggerid?

</div>

<div class="my-3">
<label for="zabbix_auth_token" class="form-label">{{ $t("zabbixAuthToken") }}</label>
<input id="zabbix_auth_token" v-model="monitor.zabbixAuthToken" type="password" class="form-control" required />
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use HiddenInput for such fields instead to communicate to our users that this is a sensitive field ^^

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have never used zabbix.
Is it nessesary to add a helptext how to find the auth token?

</div>
</template>

<!-- SQL Server / PostgreSQL / MySQL / Redis / MongoDB -->
<template v-if="monitor.type === 'sqlserver' || monitor.type === 'postgres' || monitor.type === 'mysql' || monitor.type === 'redis' || monitor.type === 'mongodb'">
<div class="my-3">
Expand Down