diff --git a/db/patch-user-username-case-insensitive.sql b/db/patch-user-username-case-insensitive.sql new file mode 100644 index 000000000..90b7f1cb2 --- /dev/null +++ b/db/patch-user-username-case-insensitive.sql @@ -0,0 +1,47 @@ +CREATE TABLE [temp_user]( + [id] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + [username] VARCHAR(255) NOT NULL UNIQUE COLLATE NOCASE, + [password] VARCHAR(255), + [active] BOOLEAN NOT NULL DEFAULT 1, + [timezone] VARCHAR(150), + twofa_secret VARCHAR(64), + twofa_status BOOLEAN default 0 NOT NULL, + twofa_last_token VARCHAR(6) +); + +INSERT INTO [temp_user] SELECT +[id], +[username], +[password], +[active], +[timezone], +twofa_secret, +twofa_status, +twofa_last_token + FROM user; + +DROP TABLE user; + +CREATE TABLE [user]( + [id] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + [username] VARCHAR(255) NOT NULL UNIQUE COLLATE NOCASE, + [password] VARCHAR(255), + [active] BOOLEAN NOT NULL DEFAULT 1, + [timezone] VARCHAR(150), + twofa_secret VARCHAR(64), + twofa_status BOOLEAN default 0 NOT NULL, + twofa_last_token VARCHAR(6) +); + +INSERT INTO [user] SELECT +[id], +[username], +[password], +[active], +[timezone], +twofa_secret, +twofa_status, +twofa_last_token + FROM [temp_user]; + +DROP TABLE [temp_user]; diff --git a/server/auth.js b/server/auth.js index 3ce1a6041..b4eeee41f 100644 --- a/server/auth.js +++ b/server/auth.js @@ -15,7 +15,7 @@ exports.login = async function (username, password) { return null; } - let user = await R.findOne("user", " username = ? AND active = 1 ", [ + let user = await R.findOne("user", " username = ? AND active = 1", [ username, ]); diff --git a/server/database.js b/server/database.js index ff41f8351..e4aa98cf8 100644 --- a/server/database.js +++ b/server/database.js @@ -66,6 +66,7 @@ class Database { "patch-add-radius-monitor.sql": true, "patch-monitor-add-resend-interval.sql": true, "patch-maintenance-table2.sql": true, + "patch-user-username-case-insensitive.sql": { parents: [ "patch-2fa-invalidate-used-token.sql", "patch-2fa.sql" ] } "patch-http-monitor-add-body-encoding.sql": true, }; diff --git a/server/model/monitor.js b/server/model/monitor.js index a0a692dc2..16713702e 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -561,7 +561,7 @@ class Monitor extends BeanModel { log.debug("monitor:", `gRPC response: ${JSON.stringify(response)}`); let responseData = response.data; if (responseData.length > 50) { - responseData = response.substring(0, 47) + "..."; + responseData = responseData.toString().substring(0, 47) + "..."; } if (response.code !== 1) { bean.status = DOWN; @@ -1103,7 +1103,7 @@ class Monitor extends BeanModel { // Prevent if the msg is undefined, notifications such as Discord cannot send out. const heartbeatJSON = bean.toJSON(); if (!heartbeatJSON["msg"]) { - heartbeatJSON["msg"] = ""; + heartbeatJSON["msg"] = "N/A"; } await Notification.send(JSON.parse(notification.config), msg, await monitor.toJSON(false), heartbeatJSON); diff --git a/server/notification-providers/discord.js b/server/notification-providers/discord.js index ac5c8ae84..b699228c9 100644 --- a/server/notification-providers/discord.js +++ b/server/notification-providers/discord.js @@ -91,7 +91,7 @@ class Discord extends NotificationProvider { }, { name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL", - value: monitorJSON["type"] === "push" ? "Heartbeat" : address.startsWith("http") ? "[Visit Service](" + address + ")" : address, + value: monitorJSON["type"] === "push" ? "Heartbeat" : address, }, { name: "Time (UTC)", diff --git a/server/notification-providers/kook.js b/server/notification-providers/kook.js new file mode 100644 index 000000000..b37b75ab1 --- /dev/null +++ b/server/notification-providers/kook.js @@ -0,0 +1,31 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); + +class Kook extends NotificationProvider { + + name = "Kook"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully."; + let url = "https://www.kookapp.cn/api/v3/message/create"; + let data = { + target_id: notification.kookGuildID, + content: msg, + }; + let config = { + headers: { + "Authorization": "Bot " + notification.kookBotToken, + "Content-Type": "application/json", + }, + }; + try { + await axios.post(url, data, config); + return okMsg; + + } catch (error) { + this.throwGeneralAxiosError(error); + } + } +} + +module.exports = Kook; diff --git a/server/notification-providers/zoho-cliq.js b/server/notification-providers/zoho-cliq.js new file mode 100644 index 000000000..749647d06 --- /dev/null +++ b/server/notification-providers/zoho-cliq.js @@ -0,0 +1,116 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); +const { DOWN, UP } = require("../../src/util"); + +class ZohoCliq extends NotificationProvider { + + name = "ZohoCliq"; + + /** + * Generate the message to send + * @param {const} status The status constant + * @param {string} monitorName Name of monitor + * @returns {string} + */ + _statusMessageFactory = (status, monitorName) => { + if (status === DOWN) { + return `🔴 Application [${monitorName}] went down\n`; + } else if (status === UP) { + return `✅ Application [${monitorName}] is back online\n`; + } + return "Notification\n"; + }; + + /** + * Send the notification + * @param {string} webhookUrl URL to send the request to + * @param {Array} payload Payload generated by _notificationPayloadFactory + */ + _sendNotification = async (webhookUrl, payload) => { + await axios.post(webhookUrl, { text: payload.join("\n") }); + }; + + /** + * Generate payload for notification + * @param {const} status The status of the monitor + * @param {string} monitorMessage Message to send + * @param {string} monitorName Name of monitor affected + * @param {string} monitorUrl URL of monitor affected + * @returns {Array} + */ + _notificationPayloadFactory = ({ + status, + monitorMessage, + monitorName, + monitorUrl, + }) => { + const payload = []; + payload.push("### Uptime Kuma\n"); + payload.push(this._statusMessageFactory(status, monitorName)); + payload.push(`*Description:* ${monitorMessage}`); + + if (monitorName) { + payload.push(`*Monitor:* ${monitorName}`); + } + + if (monitorUrl && monitorUrl !== "https://") { + payload.push(`*URL:* [${monitorUrl}](${monitorUrl})`); + } + + return payload; + }; + + /** + * Send a general notification + * @param {string} webhookUrl URL to send request to + * @param {string} msg Message to send + * @returns {Promise} + */ + _handleGeneralNotification = (webhookUrl, msg) => { + const payload = this._notificationPayloadFactory({ + monitorMessage: msg + }); + + return this._sendNotification(webhookUrl, payload); + }; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully."; + + try { + if (heartbeatJSON == null) { + await this._handleGeneralNotification(notification.webhookUrl, msg); + return okMsg; + } + + let url; + switch (monitorJSON["type"]) { + case "http": + case "keywork": + url = monitorJSON["url"]; + break; + case "docker": + url = monitorJSON["docker_host"]; + break; + default: + url = monitorJSON["hostname"]; + break; + } + + const payload = this._notificationPayloadFactory({ + monitorMessage: heartbeatJSON.msg, + monitorName: monitorJSON.name, + monitorUrl: url, + status: heartbeatJSON.status + }); + + await this._sendNotification(notification.webhookUrl, payload); + return okMsg; + + } catch (error) { + this.throwGeneralAxiosError(error); + } + } +} + +module.exports = ZohoCliq; diff --git a/server/notification.js b/server/notification.js index 9069601b4..1aad704c0 100644 --- a/server/notification.js +++ b/server/notification.js @@ -14,6 +14,7 @@ const GoogleChat = require("./notification-providers/google-chat"); const Gorush = require("./notification-providers/gorush"); const Gotify = require("./notification-providers/gotify"); const HomeAssistant = require("./notification-providers/home-assistant"); +const Kook = require("./notification-providers/kook"); const Line = require("./notification-providers/line"); const LineNotify = require("./notification-providers/linenotify"); const LunaSea = require("./notification-providers/lunasea"); @@ -44,6 +45,7 @@ const WeCom = require("./notification-providers/wecom"); const GoAlert = require("./notification-providers/goalert"); const SMSManager = require("./notification-providers/smsmanager"); const ServerChan = require("./notification-providers/serverchan"); +const ZohoCliq = require("./notification-providers/zoho-cliq"); class Notification { @@ -70,6 +72,7 @@ class Notification { new Gorush(), new Gotify(), new HomeAssistant(), + new Kook(), new Line(), new LineNotify(), new LunaSea(), @@ -100,6 +103,7 @@ class Notification { new Webhook(), new WeCom(), new GoAlert(), + new ZohoCliq() ]; for (let item of list) { diff --git a/server/server.js b/server/server.js index e482aed75..5276ba92c 100644 --- a/server/server.js +++ b/server/server.js @@ -714,6 +714,7 @@ let needSetup = false; bean.authDomain = monitor.authDomain; bean.grpcUrl = monitor.grpcUrl; bean.grpcProtobuf = monitor.grpcProtobuf; + bean.grpcServiceName = monitor.grpcServiceName; bean.grpcMethod = monitor.grpcMethod; bean.grpcBody = monitor.grpcBody; bean.grpcMetadata = monitor.grpcMetadata; diff --git a/server/util-server.js b/server/util-server.js index 0bf69133b..2ce6de3ee 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -778,22 +778,31 @@ module.exports.grpcQuery = async (options) => { cb); }, false, false); return new Promise((resolve, _) => { - return grpcService[`${grpcMethod}`](JSON.parse(grpcBody), function (err, response) { - const responseData = JSON.stringify(response); - if (err) { - return resolve({ - code: err.code, - errorMessage: err.details, - data: "" - }); - } else { - log.debug("monitor:", `gRPC response: ${response}`); - return resolve({ - code: 1, - errorMessage: "", - data: responseData - }); - } - }); + try { + return grpcService[`${grpcMethod}`](JSON.parse(grpcBody), function (err, response) { + const responseData = JSON.stringify(response); + if (err) { + return resolve({ + code: err.code, + errorMessage: err.details, + data: "" + }); + } else { + log.debug("monitor:", `gRPC response: ${JSON.stringify(response)}`); + return resolve({ + code: 1, + errorMessage: "", + data: responseData + }); + } + }); + } catch (err) { + return resolve({ + code: -1, + errorMessage: `Error ${err}. Please review your gRPC configuration option. The service name must not include package name value, and the method name must follow camelCase format`, + data: "" + }); + } + }); }; diff --git a/src/components/notifications/Kook.vue b/src/components/notifications/Kook.vue new file mode 100644 index 000000000..7027b5e13 --- /dev/null +++ b/src/components/notifications/Kook.vue @@ -0,0 +1,36 @@ + + + diff --git a/src/components/notifications/ZohoCliq.vue b/src/components/notifications/ZohoCliq.vue new file mode 100644 index 000000000..9a9cd7360 --- /dev/null +++ b/src/components/notifications/ZohoCliq.vue @@ -0,0 +1,18 @@ + diff --git a/src/components/notifications/index.js b/src/components/notifications/index.js index 0c220b717..810cdf03e 100644 --- a/src/components/notifications/index.js +++ b/src/components/notifications/index.js @@ -12,6 +12,7 @@ import GoogleChat from "./GoogleChat.vue"; import Gorush from "./Gorush.vue"; import Gotify from "./Gotify.vue"; import HomeAssistant from "./HomeAssistant.vue"; +import Kook from "./Kook.vue"; import Line from "./Line.vue"; import LineNotify from "./LineNotify.vue"; import LunaSea from "./LunaSea.vue"; @@ -42,6 +43,7 @@ import Telegram from "./Telegram.vue"; import Webhook from "./Webhook.vue"; import WeCom from "./WeCom.vue"; import GoAlert from "./GoAlert.vue"; +import ZohoCliq from "./ZohoCliq.vue"; /** * Manage all notification form. @@ -63,6 +65,7 @@ const NotificationFormList = { "gorush": Gorush, "gotify": Gotify, "HomeAssistant": HomeAssistant, + "Kook": Kook, "line": Line, "LineNotify": LineNotify, "lunasea": LunaSea, @@ -93,6 +96,7 @@ const NotificationFormList = { "WeCom": WeCom, "GoAlert": GoAlert, "ServerChan": ServerChan, + "ZohoCliq": ZohoCliq }; export default NotificationFormList; diff --git a/src/languages/el-GR.js b/src/languages/el-GR.js index c520a6079..9b7c4cfb3 100644 --- a/src/languages/el-GR.js +++ b/src/languages/el-GR.js @@ -194,6 +194,7 @@ export default { here: "εδώ", Required: "Απαιτείται", telegram: "Telegram", + "ZohoCliq": "ZohoCliq", "Bot Token": "Διακριτικό Bot", wayToGetTelegramToken: "Μπορείτε να πάρετε ένα διακριτικό από {0}.", "Chat ID": "Chat ID", @@ -224,6 +225,7 @@ export default { teams: "Microsoft Teams", "Webhook URL": "Webhook URL", wayToGetTeamsURL: "Μπορείτε να μάθετε πώς να δημιουργείτε μια διεύθυνση URL webhook {0}.", + wayToGetZohoCliqURL: "Μπορείτε να μάθετε πώς να δημιουργείτε μια διεύθυνση URL webhook {0}.", signal: "Signal", Number: "Αριθμός", Recipients: "Αποδέκτες", diff --git a/src/languages/en.js b/src/languages/en.js index fa914db66..e298f6437 100644 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -209,6 +209,7 @@ export default { here: "here", Required: "Required", telegram: "Telegram", + "ZohoCliq": "ZohoCliq", "Bot Token": "Bot Token", wayToGetTelegramToken: "You can get a token from {0}.", "Chat ID": "Chat ID", @@ -241,6 +242,7 @@ export default { teams: "Microsoft Teams", "Webhook URL": "Webhook URL", wayToGetTeamsURL: "You can learn how to create a webhook URL {0}.", + wayToGetZohoCliqURL: "You can learn how to create a webhook URL {0}.", signal: "Signal", Number: "Number", Recipients: "Recipients", @@ -270,6 +272,10 @@ export default { apprise: "Apprise (Support 50+ Notification services)", GoogleChat: "Google Chat (Google Workspace only)", pushbullet: "Pushbullet", + Kook: "Kook", + wayToGetKookBotToken: "Create application and get your bot token at {0}", + wayToGetKookGuildID: "Switch on 'Developer Mode' in Kook setting, and right click the guild to get its ID", + "Guild ID": "Guild ID", line: "Line Messenger", mattermost: "Mattermost", "User Key": "User Key", diff --git a/src/languages/eu.js b/src/languages/eu.js index c99f1eb70..a491c8728 100644 --- a/src/languages/eu.js +++ b/src/languages/eu.js @@ -191,6 +191,7 @@ export default { here: "Hemen", Required: "Beharrezkoa", telegram: "Telegram", + "ZohoCliq": "ZohoCliq", "Bot Token": "Bot Tokena", wayToGetTelegramToken: "You can get a token from {0}.", "Chat ID": "Txat IDa", @@ -221,6 +222,7 @@ export default { teams: "Microsoft Teams", "Webhook URL": "Webhook URL", wayToGetTeamsURL: "You can learn how to create a webhook URL {0}.", + wayToGetZohoCliqURL: "You can learn how to create a webhook URL {0}.", signal: "Signal", Number: "Zenbakia", Recipients: "Recipients", diff --git a/src/languages/zh-CN.js b/src/languages/zh-CN.js index ff11c7e9b..5878758c9 100644 --- a/src/languages/zh-CN.js +++ b/src/languages/zh-CN.js @@ -250,6 +250,10 @@ export default { apprise: "Apprise (支持 50+ 种通知服务)", GoogleChat: "Google Chat(仅 Google Workspace)", pushbullet: "Pushbullet", + Kook: "Kook", + wayToGetKookBotToken: "在 {0} 创建应用并获取机器人 Token", + wayToGetKookGuildID: "在Kook设置中打开 ‘开发者模式’,然后右键频道可获取其 ID", + "Guild ID": "频道 ID", line: "Line Messenger", mattermost: "Mattermost", "User Key": "User Key",