From daddab36a8dbe5e80efcc8795a2ccf78467d112f Mon Sep 17 00:00:00 2001 From: Brandon Hubbard Date: Thu, 15 Jan 2026 05:23:47 +0000 Subject: [PATCH] Add support for Clickup --- server/notification-providers/clickup.js | 59 ++++++++++++++++++++++++ server/notification.js | 2 + src/components/NotificationDialog.vue | 1 + src/components/notifications/Clickup.vue | 57 +++++++++++++++++++++++ src/components/notifications/index.js | 2 + src/lang/en.json | 4 ++ 6 files changed, 125 insertions(+) create mode 100644 server/notification-providers/clickup.js create mode 100644 src/components/notifications/Clickup.vue diff --git a/server/notification-providers/clickup.js b/server/notification-providers/clickup.js new file mode 100644 index 000000000..1f46b45cf --- /dev/null +++ b/server/notification-providers/clickup.js @@ -0,0 +1,59 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); +const { DOWN, UP } = require("../../src/util"); + +class Clickup extends NotificationProvider { + name = "clickup"; + + /** + * @inheritdoc + */ + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + const okMsg = "Sent Successfully."; + + try { + let config = this.getAxiosConfigWithProxy({ + headers: { + "Authorization": notification.clickupToken, + "Content-Type": "application/json", + }, + }); + + // Build the message + let title = "Uptime Kuma Alert"; + if (monitorJSON && heartbeatJSON) { + if (heartbeatJSON["status"] === DOWN) { + title = `❌ ${monitorJSON["name"]} went down`; + } else if (heartbeatJSON["status"] === UP) { + title = `✅ ${monitorJSON["name"]} is back online`; + } + } + + // Build message content + let content = msg; + if (heartbeatJSON) { + content += `\n\n**Time (${heartbeatJSON["timezone"]}):** ${heartbeatJSON["localDateTime"]}`; + } + + let address = this.extractAddress(monitorJSON); + if (address && !notification.clickupDisableUrl) { + content += `\n**Address:** ${address}`; + } + + // Construct the payload for Clickup Chat API + const data = { + text: `${title}\n${content}`, + }; + + // Send to the specified channel + const url = `https://api.clickup.com/api/v2/channel/${notification.clickupChannelId}/chat/message`; + + await axios.post(url, data, config); + return okMsg; + } catch (error) { + this.throwGeneralAxiosError(error); + } + } +} + +module.exports = Clickup; diff --git a/server/notification.js b/server/notification.js index de429388c..14bfc0280 100644 --- a/server/notification.js +++ b/server/notification.js @@ -7,6 +7,7 @@ const Apprise = require("./notification-providers/apprise"); const Bale = require("./notification-providers/bale"); const Bark = require("./notification-providers/bark"); const Bitrix24 = require("./notification-providers/bitrix24"); +const Clickup = require("./notification-providers/clickup"); const ClickSendSMS = require("./notification-providers/clicksendsms"); const CallMeBot = require("./notification-providers/call-me-bot"); const SMSC = require("./notification-providers/smsc"); @@ -108,6 +109,7 @@ class Notification { new Bale(), new Bark(), new Bitrix24(), + new Clickup(), new ClickSendSMS(), new CallMeBot(), new SMSC(), diff --git a/src/components/NotificationDialog.vue b/src/components/NotificationDialog.vue index 6d915255a..9a3537677 100644 --- a/src/components/NotificationDialog.vue +++ b/src/components/NotificationDialog.vue @@ -214,6 +214,7 @@ export default { let chatPlatforms = { bale: "Bale", Bitrix24: "Bitrix24", + clickup: "Clickup", discord: "Discord", GoogleChat: "Google Chat (Google Workspace)", gorush: "Gorush", diff --git a/src/components/notifications/Clickup.vue b/src/components/notifications/Clickup.vue new file mode 100644 index 000000000..ad4b07152 --- /dev/null +++ b/src/components/notifications/Clickup.vue @@ -0,0 +1,57 @@ + + + diff --git a/src/components/notifications/index.js b/src/components/notifications/index.js index 1a8f6dba3..96e5db74b 100644 --- a/src/components/notifications/index.js +++ b/src/components/notifications/index.js @@ -5,6 +5,7 @@ import Apprise from "./Apprise.vue"; import Bale from "./Bale.vue"; import Bark from "./Bark.vue"; import Bitrix24 from "./Bitrix24.vue"; +import Clickup from "./Clickup.vue"; import Notifery from "./Notifery.vue"; import ClickSendSMS from "./ClickSendSMS.vue"; import CallMeBot from "./CallMeBot.vue"; @@ -95,6 +96,7 @@ const NotificationFormList = { bale: Bale, Bark: Bark, Bitrix24: Bitrix24, + clickup: Clickup, clicksendsms: ClickSendSMS, CallMeBot: CallMeBot, smsc: SMSC, diff --git a/src/lang/en.json b/src/lang/en.json index ea1ea35a8..0b330d0df 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -1028,6 +1028,10 @@ "Bitrix24 Webhook URL": "Bitrix24 Webhook URL", "wayToGetBitrix24Webhook": "You can create a webhook by following the steps at {0}", "bitrix24SupportUserID": "Enter your user ID in Bitrix24. You can find out the ID from the link by going to the user's profile.", + "Clickup API Token": "Clickup API Token", + "Clickup API Token Description": "You can create a personal access token in Clickup by going to Settings -> Apps & Integrations -> API -> Generate token", + "Clickup Channel ID": "Clickup Channel ID", + "Clickup Channel ID Description": "You can find the channel ID from the channel URL or by using the Clickup API", "Saved.": "Saved.", "authUserInactiveOrDeleted": "The user is inactive or deleted.", "authInvalidToken": "Invalid Token.",