diff --git a/db/knex_migrations/2024-03-24-0000-add-datadog-monitor-type.js b/db/knex_migrations/2024-03-24-0000-add-datadog-monitor-type.js
new file mode 100644
index 00000000..5f8e8742
--- /dev/null
+++ b/db/knex_migrations/2024-03-24-0000-add-datadog-monitor-type.js
@@ -0,0 +1,21 @@
+exports.up = function (knex) {
+ // Add new columns monitor.datadog*
+ return knex.schema
+ .alterTable("monitor", function (table) {
+ table.string("datadog_site", 255);
+ table.string("datadog_api_key", 255);
+ table.string("datadog_app_key", 255);
+ table.string("datadog_monitor_id", 255);
+ });
+};
+
+exports.down = function (knex) {
+ // Drop column monitor.datadog*
+ return knex.schema
+ .alterTable("monitor", function (table) {
+ table.dropColumn("datadog_site");
+ table.dropColumn("datadog_api_key");
+ table.dropColumn("datadog_app_key");
+ table.dropColumn("datadog_monitor_id");
+ });
+};
diff --git a/package-lock.json b/package-lock.json
index 7578c191..0ebb6345 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,7 @@
"version": "2.0.0-dev",
"license": "MIT",
"dependencies": {
+ "@datadog/datadog-api-client": "^1.23.0",
"@grpc/grpc-js": "~1.7.3",
"@louislam/ping": "~0.4.4-mod.1",
"@louislam/sqlite3": "15.1.6",
@@ -100,7 +101,7 @@
"bootstrap": "5.1.3",
"chart.js": "~4.2.1",
"chartjs-adapter-dayjs-4": "~1.0.4",
- "concurrently": "^7.1.0",
+ "concurrently": "^7.6.0",
"core-js": "~3.26.1",
"cronstrue": "~2.24.0",
"cross-env": "~7.0.3",
@@ -1917,6 +1918,26 @@
"postcss-selector-parser": "^6.0.13"
}
},
+ "node_modules/@datadog/datadog-api-client": {
+ "version": "1.23.0",
+ "resolved": "https://registry.npmjs.org/@datadog/datadog-api-client/-/datadog-api-client-1.23.0.tgz",
+ "integrity": "sha512-hwt/WIrm63Y1JWQXj5DHihgiYdaJkJ41KJTJDUtRA85B9QpoDA+P/j28xC75IIfoajcEeAe1n9BtRrRZ7Z2x3A==",
+ "dependencies": {
+ "@types/buffer-from": "^1.1.0",
+ "@types/node": "*",
+ "@types/pako": "^1.0.3",
+ "buffer-from": "^1.1.2",
+ "cross-fetch": "^3.1.5",
+ "es6-promise": "^4.2.8",
+ "form-data": "^4.0.0",
+ "loglevel": "^1.8.1",
+ "pako": "^2.0.4",
+ "url-parse": "^1.4.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/@es-joy/jsdoccomment": {
"version": "0.40.1",
"resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.40.1.tgz",
@@ -4042,6 +4063,14 @@
"@popperjs/core": "^2.9.2"
}
},
+ "node_modules/@types/buffer-from": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@types/buffer-from/-/buffer-from-1.1.3.tgz",
+ "integrity": "sha512-2lq4YC9uLUMGHkl2IDtX4tCXSo2+hwMpOJcY1qiIk1kybc31rIlPyM1HCVJhkPFIo75a/pOVxqyvwuf5TpCG/w==",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/connect": {
"version": "3.4.38",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
@@ -4189,6 +4218,11 @@
"integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
"dev": true
},
+ "node_modules/@types/pako": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/@types/pako/-/pako-1.0.7.tgz",
+ "integrity": "sha512-YBtzT2ztNF6R/9+UXj2wTGFnC9NklAnASt3sC0h2m1bbH7G6FyBIkt4AN8ThZpNfxUo1b2iMVO0UawiJymEt8A=="
+ },
"node_modules/@types/qs": {
"version": "6.9.11",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz",
@@ -6131,6 +6165,14 @@
"yarn": ">=1"
}
},
+ "node_modules/cross-fetch": {
+ "version": "3.1.8",
+ "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz",
+ "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==",
+ "dependencies": {
+ "node-fetch": "^2.6.12"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -6880,6 +6922,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/es6-promise": {
+ "version": "4.2.8",
+ "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
+ "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
+ },
"node_modules/esbuild": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz",
@@ -9415,6 +9462,18 @@
"integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==",
"dev": true
},
+ "node_modules/loglevel": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.1.tgz",
+ "integrity": "sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==",
+ "engines": {
+ "node": ">= 0.6.0"
+ },
+ "funding": {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/loglevel"
+ }
+ },
"node_modules/long": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
@@ -10666,6 +10725,11 @@
"resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
"integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
},
+ "node_modules/pako": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
+ "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="
+ },
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
diff --git a/package.json b/package.json
index 894079cf..53b442a5 100644
--- a/package.json
+++ b/package.json
@@ -74,6 +74,7 @@
"start-server-node14-win": "private\\node14\\node.exe server/server.js"
},
"dependencies": {
+ "@datadog/datadog-api-client": "^1.23.0",
"@grpc/grpc-js": "~1.7.3",
"@louislam/ping": "~0.4.4-mod.1",
"@louislam/sqlite3": "15.1.6",
@@ -165,7 +166,7 @@
"bootstrap": "5.1.3",
"chart.js": "~4.2.1",
"chartjs-adapter-dayjs-4": "~1.0.4",
- "concurrently": "^7.1.0",
+ "concurrently": "^7.6.0",
"core-js": "~3.26.1",
"cronstrue": "~2.24.0",
"cross-env": "~7.0.3",
diff --git a/server/model/monitor.js b/server/model/monitor.js
index 1667b83a..eda615c6 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -161,6 +161,9 @@ class Monitor extends BeanModel {
kafkaProducerMessage: this.kafkaProducerMessage,
screenshot,
remote_browser: this.remote_browser,
+ datadog_site: this.datadog_site,
+ datadog_monitor_id: this.datadog_monitor_id,
+
};
if (includeSensitiveData) {
@@ -190,6 +193,8 @@ class Monitor extends BeanModel {
tlsCert: this.tlsCert,
tlsKey: this.tlsKey,
kafkaProducerSaslOptions: JSON.parse(this.kafkaProducerSaslOptions),
+ datadog_api_key: this.datadog_api_key,
+ datadog_app_key: this.datadog_app_key,
};
}
diff --git a/server/monitor-types/datadog.js b/server/monitor-types/datadog.js
new file mode 100644
index 00000000..54488981
--- /dev/null
+++ b/server/monitor-types/datadog.js
@@ -0,0 +1,58 @@
+const { MonitorType } = require("./monitor-type");
+const { UP, log, DOWN, PENDING } = require("../../src/util");
+const { client, v1 } = require("@datadog/datadog-api-client");
+const { json } = require("express");
+
+/**
+ * A DataDog class extends the MonitorType.
+ * It will query DataDog api to get datadog monitor state and sync the monitor status.
+ */
+class DataDog extends MonitorType {
+
+ name = "datadog";
+ /**
+ * @param {Object} monitor - The monitor object associated with the check.
+ * @param {Object} heartbeat - The heartbeat object to update.
+ * @throws Will throw an error if the API call found any.
+ */
+ async check(monitor, heartbeat) {
+ try {
+ const configurationOpts = {
+ authMethods: {
+ apiKeyAuth: monitor.datadog_api_key,
+ appKeyAuth: monitor.datadog_app_key
+ },
+ };
+ const configuration = client.createConfiguration(configurationOpts);
+ configuration.setServerVariables({
+ site: monitor.datadog_site
+ });
+ const apiInstance = new v1.MonitorsApi(configuration)
+ let params = {
+ monitorId: monitor.datadog_monitor_id,
+ };
+ let status = await apiInstance.getMonitor(params).then((data) => {
+ log.debug("datadog",'DataDog API called successfully');
+ let state = data["overallState"]
+ if (state === "OK") {
+ log.debug("datadog","UP")
+ return UP;
+ } else if (state === "No Data") {
+ log.debug("datadog","PENDING")
+ return PENDING;
+ } else {
+ log.debug("datadog","DOWN")
+ return DOWN;
+ }
+ })
+ heartbeat.status = status
+ } catch (err) {
+ // trigger log function somewhere to display a notification or alert to the user (but how?)
+ throw new Error(`Error checking datadog monitor: ${err}`);
+ }
+ }
+}
+
+module.exports = {
+ DataDog,
+};
diff --git a/server/uptime-kuma-server.js b/server/uptime-kuma-server.js
index bcf497b5..188ab1f2 100644
--- a/server/uptime-kuma-server.js
+++ b/server/uptime-kuma-server.js
@@ -113,6 +113,7 @@ class UptimeKumaServer {
UptimeKumaServer.monitorTypeList["tailscale-ping"] = new TailscalePing();
UptimeKumaServer.monitorTypeList["dns"] = new DnsMonitorType();
UptimeKumaServer.monitorTypeList["mqtt"] = new MqttMonitorType();
+ UptimeKumaServer.monitorTypeList["datadog"] = new DataDog();
// Allow all CORS origins (polling) in development
let cors = undefined;
@@ -516,3 +517,4 @@ const { RealBrowserMonitorType } = require("./monitor-types/real-browser-monitor
const { TailscalePing } = require("./monitor-types/tailscale-ping");
const { DnsMonitorType } = require("./monitor-types/dns");
const { MqttMonitorType } = require("./monitor-types/mqtt");
+const { DataDog } = require("./monitor-types/datadog")
diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index e9f3ac83..d98e8299 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -85,6 +85,9 @@
+
@@ -245,6 +248,32 @@
+
+
+
+
+