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 @@ + + + +