From d01fa9bcfcec87c14adcf82f5097a83129967ae9 Mon Sep 17 00:00:00 2001 From: Ponkhy <github@myon.lu> Date: Tue, 7 Sep 2021 10:25:25 +0200 Subject: [PATCH 1/5] Auto login after setup --- src/pages/Setup.vue | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/pages/Setup.vue b/src/pages/Setup.vue index dbff86837..9cb7c5ecb 100644 --- a/src/pages/Setup.vue +++ b/src/pages/Setup.vue @@ -85,7 +85,12 @@ export default { this.$root.toastRes(res) if (res.ok) { - this.$router.push("/") + this.processing = true; + + this.$root.login(this.username, this.password, (res) => { + this.processing = false; + this.$router.push("/") + }) } }) }, From da74391c3e6c315d2f317f7f34224a50a4431520 Mon Sep 17 00:00:00 2001 From: LouisLam <louislam@users.noreply.github.com> Date: Tue, 7 Sep 2021 22:42:46 +0800 Subject: [PATCH 2/5] convert notifications into modules --- .eslintrc.js | 3 +- server/notification-providers/apprise.js | 26 + server/notification-providers/discord.js | 105 +++ server/notification-providers/gotify.js | 28 + server/notification-providers/line.js | 60 ++ server/notification-providers/lunasea.js | 48 ++ server/notification-providers/mattermost.js | 123 +++ .../notification-provider.js | 36 + server/notification-providers/octopush.js | 40 + server/notification-providers/pushbullet.js | 50 ++ server/notification-providers/pushover.js | 49 ++ server/notification-providers/pushy.js | 30 + server/notification-providers/rocket-chat.js | 46 ++ server/notification-providers/signal.js | 27 + server/notification-providers/slack.js | 70 ++ server/notification-providers/smtp.js | 43 ++ server/notification-providers/telegram.js | 27 + server/notification-providers/webhook.js | 44 ++ server/notification.js | 714 ++---------------- server/server.js | 3 + 20 files changed, 918 insertions(+), 654 deletions(-) create mode 100644 server/notification-providers/apprise.js create mode 100644 server/notification-providers/discord.js create mode 100644 server/notification-providers/gotify.js create mode 100644 server/notification-providers/line.js create mode 100644 server/notification-providers/lunasea.js create mode 100644 server/notification-providers/mattermost.js create mode 100644 server/notification-providers/notification-provider.js create mode 100644 server/notification-providers/octopush.js create mode 100644 server/notification-providers/pushbullet.js create mode 100644 server/notification-providers/pushover.js create mode 100644 server/notification-providers/pushy.js create mode 100644 server/notification-providers/rocket-chat.js create mode 100644 server/notification-providers/signal.js create mode 100644 server/notification-providers/slack.js create mode 100644 server/notification-providers/smtp.js create mode 100644 server/notification-providers/telegram.js create mode 100644 server/notification-providers/webhook.js diff --git a/.eslintrc.js b/.eslintrc.js index fe63d4a4e..6704a85b2 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -17,7 +17,8 @@ module.exports = { }, rules: { "camelcase": ["warn", { - "properties": "never" + "properties": "never", + "ignoreImports": true }], // override/add rules settings here, such as: // 'vue/no-unused-vars': 'error' diff --git a/server/notification-providers/apprise.js b/server/notification-providers/apprise.js new file mode 100644 index 000000000..fdcd8d61b --- /dev/null +++ b/server/notification-providers/apprise.js @@ -0,0 +1,26 @@ +const NotificationProvider = require("./notification-provider"); +const child_process = require("child_process"); + +class Apprise extends NotificationProvider { + + name = "apprise"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let s = child_process.spawnSync("apprise", [ "-vv", "-b", msg, notification.appriseURL]) + + let output = (s.stdout) ? s.stdout.toString() : "ERROR: maybe apprise not found"; + + if (output) { + + if (! output.includes("ERROR")) { + return "Sent Successfully"; + } + + throw new Error(output) + } else { + return "No output from apprise"; + } + } +} + +module.exports = Apprise; diff --git a/server/notification-providers/discord.js b/server/notification-providers/discord.js new file mode 100644 index 000000000..d6ee0afe1 --- /dev/null +++ b/server/notification-providers/discord.js @@ -0,0 +1,105 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); +const { DOWN, UP } = require("../../src/util"); + +class Discord extends NotificationProvider { + + name = "discord"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully. "; + + try { + const discordDisplayName = notification.discordUsername || "Uptime Kuma"; + + // If heartbeatJSON is null, assume we're testing. + if (heartbeatJSON == null) { + let discordtestdata = { + username: discordDisplayName, + content: msg, + } + await axios.post(notification.discordWebhookUrl, discordtestdata) + return okMsg; + } + + let url; + + if (monitorJSON["type"] === "port") { + url = monitorJSON["hostname"]; + if (monitorJSON["port"]) { + url += ":" + monitorJSON["port"]; + } + + } else { + url = monitorJSON["url"]; + } + + // If heartbeatJSON is not null, we go into the normal alerting loop. + if (heartbeatJSON["status"] == DOWN) { + let discorddowndata = { + username: discordDisplayName, + embeds: [{ + title: "❌ Your service " + monitorJSON["name"] + " went down. ❌", + color: 16711680, + timestamp: heartbeatJSON["time"], + fields: [ + { + name: "Service Name", + value: monitorJSON["name"], + }, + { + name: "Service URL", + value: url, + }, + { + name: "Time (UTC)", + value: heartbeatJSON["time"], + }, + { + name: "Error", + value: heartbeatJSON["msg"], + }, + ], + }], + } + await axios.post(notification.discordWebhookUrl, discorddowndata) + return okMsg; + + } else if (heartbeatJSON["status"] == UP) { + let discordupdata = { + username: discordDisplayName, + embeds: [{ + title: "✅ Your service " + monitorJSON["name"] + " is up! ✅", + color: 65280, + timestamp: heartbeatJSON["time"], + fields: [ + { + name: "Service Name", + value: monitorJSON["name"], + }, + { + name: "Service URL", + value: url.startsWith("http") ? "[Visit Service](" + url + ")" : url, + }, + { + name: "Time (UTC)", + value: heartbeatJSON["time"], + }, + { + name: "Ping", + value: heartbeatJSON["ping"] + "ms", + }, + ], + }], + } + await axios.post(notification.discordWebhookUrl, discordupdata) + return okMsg; + } + } catch (error) { + this.throwGeneralAxiosError(error) + } + } + +} + +module.exports = Discord; diff --git a/server/notification-providers/gotify.js b/server/notification-providers/gotify.js new file mode 100644 index 000000000..9d2d55aa5 --- /dev/null +++ b/server/notification-providers/gotify.js @@ -0,0 +1,28 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); + +class Gotify extends NotificationProvider { + + name = "gotify"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully. "; + try { + if (notification.gotifyserverurl && notification.gotifyserverurl.endsWith("/")) { + notification.gotifyserverurl = notification.gotifyserverurl.slice(0, -1); + } + await axios.post(`${notification.gotifyserverurl}/message?token=${notification.gotifyapplicationToken}`, { + "message": msg, + "priority": notification.gotifyPriority || 8, + "title": "Uptime-Kuma", + }) + + return okMsg; + + } catch (error) { + this.throwGeneralAxiosError(error); + } + } +} + +module.exports = Gotify; diff --git a/server/notification-providers/line.js b/server/notification-providers/line.js new file mode 100644 index 000000000..830969034 --- /dev/null +++ b/server/notification-providers/line.js @@ -0,0 +1,60 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); +const { DOWN, UP } = require("../../src/util"); + +class Line extends NotificationProvider { + + name = "line"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully. "; + try { + let lineAPIUrl = "https://api.line.me/v2/bot/message/push"; + let config = { + headers: { + "Content-Type": "application/json", + "Authorization": "Bearer " + notification.lineChannelAccessToken + } + }; + if (heartbeatJSON == null) { + let testMessage = { + "to": notification.lineUserID, + "messages": [ + { + "type": "text", + "text": "Test Successful!" + } + ] + } + await axios.post(lineAPIUrl, testMessage, config) + } else if (heartbeatJSON["status"] == DOWN) { + let downMessage = { + "to": notification.lineUserID, + "messages": [ + { + "type": "text", + "text": "UptimeKuma Alert: [🔴 Down]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"] + } + ] + } + await axios.post(lineAPIUrl, downMessage, config) + } else if (heartbeatJSON["status"] == UP) { + let upMessage = { + "to": notification.lineUserID, + "messages": [ + { + "type": "text", + "text": "UptimeKuma Alert: [✅ Up]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"] + } + ] + } + await axios.post(lineAPIUrl, upMessage, config) + } + return okMsg; + } catch (error) { + this.throwGeneralAxiosError(error) + } + } +} + +module.exports = Line; diff --git a/server/notification-providers/lunasea.js b/server/notification-providers/lunasea.js new file mode 100644 index 000000000..fb6cd2368 --- /dev/null +++ b/server/notification-providers/lunasea.js @@ -0,0 +1,48 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); +const { DOWN, UP } = require("../../src/util"); + +class LunaSea extends NotificationProvider { + + name = "lunasea"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully. "; + let lunaseadevice = "https://notify.lunasea.app/v1/custom/device/" + notification.lunaseaDevice + + try { + if (heartbeatJSON == null) { + let testdata = { + "title": "Uptime Kuma Alert", + "body": "Testing Successful.", + } + await axios.post(lunaseadevice, testdata) + return okMsg; + } + + if (heartbeatJSON["status"] == DOWN) { + let downdata = { + "title": "UptimeKuma Alert: " + monitorJSON["name"], + "body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], + } + await axios.post(lunaseadevice, downdata) + return okMsg; + } + + if (heartbeatJSON["status"] == UP) { + let updata = { + "title": "UptimeKuma Alert: " + monitorJSON["name"], + "body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], + } + await axios.post(lunaseadevice, updata) + return okMsg; + } + + } catch (error) { + this.throwGeneralAxiosError(error) + } + + } +} + +module.exports = LunaSea; diff --git a/server/notification-providers/mattermost.js b/server/notification-providers/mattermost.js new file mode 100644 index 000000000..97779435a --- /dev/null +++ b/server/notification-providers/mattermost.js @@ -0,0 +1,123 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); +const { DOWN, UP } = require("../../src/util"); + +class Mattermost extends NotificationProvider { + + name = "mattermost"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully. "; + try { + const mattermostUserName = notification.mattermostusername || "Uptime Kuma"; + // If heartbeatJSON is null, assume we're testing. + if (heartbeatJSON == null) { + let mattermostTestData = { + username: mattermostUserName, + text: msg, + } + await axios.post(notification.mattermostWebhookUrl, mattermostTestData) + return okMsg; + } + + const mattermostChannel = notification.mattermostchannel; + const mattermostIconEmoji = notification.mattermosticonemo; + const mattermostIconUrl = notification.mattermosticonurl; + + if (heartbeatJSON["status"] == DOWN) { + let mattermostdowndata = { + username: mattermostUserName, + text: "Uptime Kuma Alert", + channel: mattermostChannel, + icon_emoji: mattermostIconEmoji, + icon_url: mattermostIconUrl, + attachments: [ + { + fallback: + "Your " + + monitorJSON["name"] + + " service went down.", + color: "#FF0000", + title: + "❌ " + + monitorJSON["name"] + + " service went down. ❌", + title_link: monitorJSON["url"], + fields: [ + { + short: true, + title: "Service Name", + value: monitorJSON["name"], + }, + { + short: true, + title: "Time (UTC)", + value: heartbeatJSON["time"], + }, + { + short: false, + title: "Error", + value: heartbeatJSON["msg"], + }, + ], + }, + ], + }; + await axios.post( + notification.mattermostWebhookUrl, + mattermostdowndata + ); + return okMsg; + } else if (heartbeatJSON["status"] == UP) { + let mattermostupdata = { + username: mattermostUserName, + text: "Uptime Kuma Alert", + channel: mattermostChannel, + icon_emoji: mattermostIconEmoji, + icon_url: mattermostIconUrl, + attachments: [ + { + fallback: + "Your " + + monitorJSON["name"] + + " service went up!", + color: "#32CD32", + title: + "✅ " + + monitorJSON["name"] + + " service went up! ✅", + title_link: monitorJSON["url"], + fields: [ + { + short: true, + title: "Service Name", + value: monitorJSON["name"], + }, + { + short: true, + title: "Time (UTC)", + value: heartbeatJSON["time"], + }, + { + short: false, + title: "Ping", + value: heartbeatJSON["ping"] + "ms", + }, + ], + }, + ], + }; + await axios.post( + notification.mattermostWebhookUrl, + mattermostupdata + ); + return okMsg; + } + } catch (error) { + this.throwGeneralAxiosError(error); + } + + } +} + +module.exports = Mattermost; diff --git a/server/notification-providers/notification-provider.js b/server/notification-providers/notification-provider.js new file mode 100644 index 000000000..61c6242d7 --- /dev/null +++ b/server/notification-providers/notification-provider.js @@ -0,0 +1,36 @@ +class NotificationProvider { + + /** + * Notification Provider Name + * @type string + */ + name = undefined; + + /** + * @param notification : BeanModel + * @param msg : string General Message + * @param monitorJSON : object Monitor details (For Up/Down only) + * @param heartbeatJSON : object Heartbeat details (For Up/Down only) + * @returns {Promise<string>} Return Successful Message + * Throw Error with fail msg + */ + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + throw new Error("Have to override Notification.send(...)"); + } + + throwGeneralAxiosError(error) { + let msg = "Error: " + error + " "; + + if (error.response && error.response.data) { + if (typeof error.response.data === "string") { + msg += error.response.data; + } else { + msg += JSON.stringify(error.response.data) + } + } + + throw new Error(msg) + } +} + +module.exports = NotificationProvider; diff --git a/server/notification-providers/octopush.js b/server/notification-providers/octopush.js new file mode 100644 index 000000000..40273f9b8 --- /dev/null +++ b/server/notification-providers/octopush.js @@ -0,0 +1,40 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); + +class Octopush extends NotificationProvider { + + name = "octopush"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully. "; + + try { + let config = { + headers: { + "api-key": notification.octopushAPIKey, + "api-login": notification.octopushLogin, + "cache-control": "no-cache" + } + }; + let data = { + "recipients": [ + { + "phone_number": notification.octopushPhoneNumber + } + ], + //octopush not supporting non ascii char + "text": msg.replace(/[^\x00-\x7F]/g, ""), + "type": notification.octopushSMSType, + "purpose": "alert", + "sender": notification.octopushSenderName + }; + + await axios.post("https://api.octopush.com/v1/public/sms-campaign/send", data, config) + return okMsg; + } catch (error) { + this.throwGeneralAxiosError(error); + } + } +} + +module.exports = Octopush; diff --git a/server/notification-providers/pushbullet.js b/server/notification-providers/pushbullet.js new file mode 100644 index 000000000..0ed6f0fdf --- /dev/null +++ b/server/notification-providers/pushbullet.js @@ -0,0 +1,50 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); + +const { DOWN, UP } = require("../../src/util"); + +class Pushbullet extends NotificationProvider { + + name = "pushbullet"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully. "; + + try { + let pushbulletUrl = "https://api.pushbullet.com/v2/pushes"; + let config = { + headers: { + "Access-Token": notification.pushbulletAccessToken, + "Content-Type": "application/json" + } + }; + if (heartbeatJSON == null) { + let testdata = { + "type": "note", + "title": "Uptime Kuma Alert", + "body": "Testing Successful.", + } + await axios.post(pushbulletUrl, testdata, config) + } else if (heartbeatJSON["status"] == DOWN) { + let downdata = { + "type": "note", + "title": "UptimeKuma Alert: " + monitorJSON["name"], + "body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], + } + await axios.post(pushbulletUrl, downdata, config) + } else if (heartbeatJSON["status"] == UP) { + let updata = { + "type": "note", + "title": "UptimeKuma Alert: " + monitorJSON["name"], + "body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], + } + await axios.post(pushbulletUrl, updata, config) + } + return okMsg; + } catch (error) { + this.throwGeneralAxiosError(error) + } + } +} + +module.exports = Pushbullet; diff --git a/server/notification-providers/pushover.js b/server/notification-providers/pushover.js new file mode 100644 index 000000000..2133ca1cf --- /dev/null +++ b/server/notification-providers/pushover.js @@ -0,0 +1,49 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); + +class Pushover extends NotificationProvider { + + name = "pushover"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully. "; + let pushoverlink = "https://api.pushover.net/1/messages.json" + + try { + if (heartbeatJSON == null) { + let data = { + "message": "<b>Uptime Kuma Pushover testing successful.</b>", + "user": notification.pushoveruserkey, + "token": notification.pushoverapptoken, + "sound": notification.pushoversounds, + "priority": notification.pushoverpriority, + "title": notification.pushovertitle, + "retry": "30", + "expire": "3600", + "html": 1, + } + await axios.post(pushoverlink, data) + return okMsg; + } + + let data = { + "message": "<b>Uptime Kuma Alert</b>\n\n<b>Message</b>:" + msg + "\n<b>Time (UTC)</b>:" + heartbeatJSON["time"], + "user": notification.pushoveruserkey, + "token": notification.pushoverapptoken, + "sound": notification.pushoversounds, + "priority": notification.pushoverpriority, + "title": notification.pushovertitle, + "retry": "30", + "expire": "3600", + "html": 1, + } + await axios.post(pushoverlink, data) + return okMsg; + } catch (error) { + this.throwGeneralAxiosError(error) + } + + } +} + +module.exports = Pushover; diff --git a/server/notification-providers/pushy.js b/server/notification-providers/pushy.js new file mode 100644 index 000000000..431cf8c37 --- /dev/null +++ b/server/notification-providers/pushy.js @@ -0,0 +1,30 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); + +class Pushy extends NotificationProvider { + + name = "pushy"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully. "; + + try { + await axios.post(`https://api.pushy.me/push?api_key=${notification.pushyAPIKey}`, { + "to": notification.pushyToken, + "data": { + "message": "Uptime-Kuma" + }, + "notification": { + "body": msg, + "badge": 1, + "sound": "ping.aiff" + } + }) + return okMsg; + } catch (error) { + this.throwGeneralAxiosError(error) + } + } +} + +module.exports = Pushy; diff --git a/server/notification-providers/rocket-chat.js b/server/notification-providers/rocket-chat.js new file mode 100644 index 000000000..149189650 --- /dev/null +++ b/server/notification-providers/rocket-chat.js @@ -0,0 +1,46 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); + +class RocketChat extends NotificationProvider { + + name = "rocket.chat"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully. "; + try { + if (heartbeatJSON == null) { + let data = { + "text": "Uptime Kuma Rocket.chat testing successful.", + "channel": notification.rocketchannel, + "username": notification.rocketusername, + "icon_emoji": notification.rocketiconemo, + } + await axios.post(notification.rocketwebhookURL, data) + return okMsg; + } + + const time = heartbeatJSON["time"]; + let data = { + "text": "Uptime Kuma Alert", + "channel": notification.rocketchannel, + "username": notification.rocketusername, + "icon_emoji": notification.rocketiconemo, + "attachments": [ + { + "title": "Uptime Kuma Alert *Time (UTC)*\n" + time, + "title_link": notification.rocketbutton, + "text": "*Message*\n" + msg, + "color": "#32cd32" + } + ] + } + await axios.post(notification.rocketwebhookURL, data) + return okMsg; + } catch (error) { + this.throwGeneralAxiosError(error) + } + + } +} + +module.exports = RocketChat; diff --git a/server/notification-providers/signal.js b/server/notification-providers/signal.js new file mode 100644 index 000000000..ba5f87f9c --- /dev/null +++ b/server/notification-providers/signal.js @@ -0,0 +1,27 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); + +class Signal extends NotificationProvider { + + name = "signal"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully. "; + + try { + let data = { + "message": msg, + "number": notification.signalNumber, + "recipients": notification.signalRecipients.replace(/\s/g, "").split(","), + }; + let config = {}; + + await axios.post(notification.signalURL, data, config) + return okMsg; + } catch (error) { + this.throwGeneralAxiosError(error) + } + } +} + +module.exports = Signal; diff --git a/server/notification-providers/slack.js b/server/notification-providers/slack.js new file mode 100644 index 000000000..661df5a0f --- /dev/null +++ b/server/notification-providers/slack.js @@ -0,0 +1,70 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); + +class Slack extends NotificationProvider { + + name = "slack"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully. "; + try { + if (heartbeatJSON == null) { + let data = { + "text": "Uptime Kuma Slack testing successful.", + "channel": notification.slackchannel, + "username": notification.slackusername, + "icon_emoji": notification.slackiconemo, + } + await axios.post(notification.slackwebhookURL, data) + return okMsg; + } + + const time = heartbeatJSON["time"]; + let data = { + "text": "Uptime Kuma Alert", + "channel": notification.slackchannel, + "username": notification.slackusername, + "icon_emoji": notification.slackiconemo, + "blocks": [{ + "type": "header", + "text": { + "type": "plain_text", + "text": "Uptime Kuma Alert", + }, + }, + { + "type": "section", + "fields": [{ + "type": "mrkdwn", + "text": "*Message*\n" + msg, + }, + { + "type": "mrkdwn", + "text": "*Time (UTC)*\n" + time, + }], + }, + { + "type": "actions", + "elements": [ + { + "type": "button", + "text": { + "type": "plain_text", + "text": "Visit Uptime Kuma", + }, + "value": "Uptime-Kuma", + "url": notification.slackbutton || "https://github.com/louislam/uptime-kuma", + }, + ], + }], + } + await axios.post(notification.slackwebhookURL, data) + return okMsg; + } catch (error) { + this.throwGeneralAxiosError(error) + } + + } +} + +module.exports = Slack; diff --git a/server/notification-providers/smtp.js b/server/notification-providers/smtp.js new file mode 100644 index 000000000..4914c0748 --- /dev/null +++ b/server/notification-providers/smtp.js @@ -0,0 +1,43 @@ +const nodemailer = require("nodemailer"); +const NotificationProvider = require("./notification-provider"); + +class SMTP extends NotificationProvider { + + name = "smtp"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + + const config = { + host: notification.smtpHost, + port: notification.smtpPort, + secure: notification.smtpSecure, + }; + + // Should fix the issue in https://github.com/louislam/uptime-kuma/issues/26#issuecomment-896373904 + if (notification.smtpUsername || notification.smtpPassword) { + config.auth = { + user: notification.smtpUsername, + pass: notification.smtpPassword, + }; + } + + let transporter = nodemailer.createTransport(config); + + let bodyTextContent = msg; + if (heartbeatJSON) { + bodyTextContent = `${msg}\nTime (UTC): ${heartbeatJSON["time"]}`; + } + + // send mail with defined transport object + await transporter.sendMail({ + from: `"Uptime Kuma" <${notification.smtpFrom}>`, + to: notification.smtpTo, + subject: msg, + text: bodyTextContent, + }); + + return "Sent Successfully."; + } +} + +module.exports = SMTP; diff --git a/server/notification-providers/telegram.js b/server/notification-providers/telegram.js new file mode 100644 index 000000000..f88dcf5de --- /dev/null +++ b/server/notification-providers/telegram.js @@ -0,0 +1,27 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); + +class Telegram extends NotificationProvider { + + name = "telegram"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully. "; + + try { + await axios.get(`https://api.telegram.org/bot${notification.telegramBotToken}/sendMessage`, { + params: { + chat_id: notification.telegramChatID, + text: msg, + }, + }) + return okMsg; + + } catch (error) { + let msg = (error.response.data.description) ? error.response.data.description : "Error without description" + throw new Error(msg) + } + } +} + +module.exports = Telegram; diff --git a/server/notification-providers/webhook.js b/server/notification-providers/webhook.js new file mode 100644 index 000000000..197e9f9f3 --- /dev/null +++ b/server/notification-providers/webhook.js @@ -0,0 +1,44 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); +const FormData = require("form-data"); + +class Webhook extends NotificationProvider { + + name = "webhook"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully. "; + + try { + let data = { + heartbeat: heartbeatJSON, + monitor: monitorJSON, + msg, + }; + let finalData; + let config = {}; + + if (notification.webhookContentType === "form-data") { + finalData = new FormData(); + finalData.append("data", JSON.stringify(data)); + + config = { + headers: finalData.getHeaders(), + } + + } else { + finalData = data; + } + + await axios.post(notification.webhookURL, finalData, config) + return okMsg; + + } catch (error) { + this.throwGeneralAxiosError(error) + } + + } + +} + +module.exports = Webhook; diff --git a/server/notification.js b/server/notification.js index f78401d9b..5d2ffb030 100644 --- a/server/notification.js +++ b/server/notification.js @@ -1,602 +1,75 @@ -const axios = require("axios"); const { R } = require("redbean-node"); -const FormData = require("form-data"); -const nodemailer = require("nodemailer"); -const child_process = require("child_process"); - -const { UP, DOWN } = require("../src/util"); +const Apprise = require("./notification-providers/apprise"); +const Discord = require("./notification-providers/discord"); +const Gotify = require("./notification-providers/gotify"); +const Line = require("./notification-providers/line"); +const LunaSea = require("./notification-providers/lunasea"); +const Mattermost = require("./notification-providers/mattermost"); +const Octopush = require("./notification-providers/octopush"); +const Pushbullet = require("./notification-providers/pushbullet"); +const Pushover = require("./notification-providers/pushover"); +const Pushy = require("./notification-providers/pushy"); +const RocketChat = require("./notification-providers/rocket-chat"); +const Signal = require("./notification-providers/signal"); +const Slack = require("./notification-providers/slack"); +const SMTP = require("./notification-providers/smtp"); +const Telegram = require("./notification-providers/telegram"); +const Webhook = require("./notification-providers/webhook"); class Notification { + providerList = {}; + + static init() { + console.log("Prepare Notification Providers"); + + this.providerList = {}; + + const list = [ + new Apprise(), + new Discord(), + new Gotify(), + new Line(), + new LunaSea(), + new Mattermost(), + new Octopush(), + new Pushbullet(), + new Pushover(), + new Pushy(), + new RocketChat(), + new Signal(), + new Slack(), + new SMTP(), + new Telegram(), + new Webhook(), + ]; + + for (let item of list) { + if (! item.name) { + throw new Error("Notification provider without name"); + } + + if (this.providerList[item.name]) { + throw new Error("Duplicate notification provider name"); + } + this.providerList[item.name] = item; + } + } + /** * - * @param notification - * @param msg - * @param monitorJSON - * @param heartbeatJSON + * @param notification : BeanModel + * @param msg : string General Message + * @param monitorJSON : object Monitor details (For Up/Down only) + * @param heartbeatJSON : object Heartbeat details (For Up/Down only) * @returns {Promise<string>} Successful msg * Throw Error with fail msg */ static async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { - let okMsg = "Sent Successfully. "; - - if (notification.type === "telegram") { - try { - await axios.get(`https://api.telegram.org/bot${notification.telegramBotToken}/sendMessage`, { - params: { - chat_id: notification.telegramChatID, - text: msg, - }, - }) - return okMsg; - - } catch (error) { - let msg = (error.response.data.description) ? error.response.data.description : "Error without description" - throw new Error(msg) - } - - } else if (notification.type === "gotify") { - try { - if (notification.gotifyserverurl && notification.gotifyserverurl.endsWith("/")) { - notification.gotifyserverurl = notification.gotifyserverurl.slice(0, -1); - } - await axios.post(`${notification.gotifyserverurl}/message?token=${notification.gotifyapplicationToken}`, { - "message": msg, - "priority": notification.gotifyPriority || 8, - "title": "Uptime-Kuma", - }) - - return okMsg; - - } catch (error) { - throwGeneralAxiosError(error) - } - - } else if (notification.type === "webhook") { - try { - let data = { - heartbeat: heartbeatJSON, - monitor: monitorJSON, - msg, - }; - let finalData; - let config = {}; - - if (notification.webhookContentType === "form-data") { - finalData = new FormData(); - finalData.append("data", JSON.stringify(data)); - - config = { - headers: finalData.getHeaders(), - } - - } else { - finalData = data; - } - - await axios.post(notification.webhookURL, finalData, config) - return okMsg; - - } catch (error) { - throwGeneralAxiosError(error) - } - - } else if (notification.type === "smtp") { - return await Notification.smtp(notification, msg, heartbeatJSON) - - } else if (notification.type === "discord") { - try { - const discordDisplayName = notification.discordUsername || "Uptime Kuma"; - - // If heartbeatJSON is null, assume we're testing. - if (heartbeatJSON == null) { - let discordtestdata = { - username: discordDisplayName, - content: msg, - } - await axios.post(notification.discordWebhookUrl, discordtestdata) - return okMsg; - } - - let url; - - if (monitorJSON["type"] === "port") { - url = monitorJSON["hostname"]; - if (monitorJSON["port"]) { - url += ":" + monitorJSON["port"]; - } - - } else { - url = monitorJSON["url"]; - } - - // If heartbeatJSON is not null, we go into the normal alerting loop. - if (heartbeatJSON["status"] == DOWN) { - let discorddowndata = { - username: discordDisplayName, - embeds: [{ - title: "❌ Your service " + monitorJSON["name"] + " went down. ❌", - color: 16711680, - timestamp: heartbeatJSON["time"], - fields: [ - { - name: "Service Name", - value: monitorJSON["name"], - }, - { - name: "Service URL", - value: url, - }, - { - name: "Time (UTC)", - value: heartbeatJSON["time"], - }, - { - name: "Error", - value: heartbeatJSON["msg"], - }, - ], - }], - } - await axios.post(notification.discordWebhookUrl, discorddowndata) - return okMsg; - - } else if (heartbeatJSON["status"] == UP) { - let discordupdata = { - username: discordDisplayName, - embeds: [{ - title: "✅ Your service " + monitorJSON["name"] + " is up! ✅", - color: 65280, - timestamp: heartbeatJSON["time"], - fields: [ - { - name: "Service Name", - value: monitorJSON["name"], - }, - { - name: "Service URL", - value: url.startsWith("http") ? "[Visit Service](" + url + ")" : url, - }, - { - name: "Time (UTC)", - value: heartbeatJSON["time"], - }, - { - name: "Ping", - value: heartbeatJSON["ping"] + "ms", - }, - ], - }], - } - await axios.post(notification.discordWebhookUrl, discordupdata) - return okMsg; - } - } catch (error) { - throwGeneralAxiosError(error) - } - - } else if (notification.type === "signal") { - try { - let data = { - "message": msg, - "number": notification.signalNumber, - "recipients": notification.signalRecipients.replace(/\s/g, "").split(","), - }; - let config = {}; - - await axios.post(notification.signalURL, data, config) - return okMsg; - } catch (error) { - throwGeneralAxiosError(error) - } - - } else if (notification.type === "pushy") { - try { - await axios.post(`https://api.pushy.me/push?api_key=${notification.pushyAPIKey}`, { - "to": notification.pushyToken, - "data": { - "message": "Uptime-Kuma" - }, - "notification": { - "body": msg, - "badge": 1, - "sound": "ping.aiff" - } - }) - return true; - } catch (error) { - console.log(error) - return false; - } - } else if (notification.type === "octopush") { - try { - let config = { - headers: { - "api-key": notification.octopushAPIKey, - "api-login": notification.octopushLogin, - "cache-control": "no-cache" - } - }; - let data = { - "recipients": [ - { - "phone_number": notification.octopushPhoneNumber - } - ], - //octopush not supporting non ascii char - "text": msg.replace(/[^\x00-\x7F]/g, ""), - "type": notification.octopushSMSType, - "purpose": "alert", - "sender": notification.octopushSenderName - }; - - await axios.post("https://api.octopush.com/v1/public/sms-campaign/send", data, config) - return true; - } catch (error) { - console.log(error) - return false; - } - } else if (notification.type === "slack") { - try { - if (heartbeatJSON == null) { - let data = { - "text": "Uptime Kuma Slack testing successful.", - "channel": notification.slackchannel, - "username": notification.slackusername, - "icon_emoji": notification.slackiconemo, - } - await axios.post(notification.slackwebhookURL, data) - return okMsg; - } - - const time = heartbeatJSON["time"]; - let data = { - "text": "Uptime Kuma Alert", - "channel": notification.slackchannel, - "username": notification.slackusername, - "icon_emoji": notification.slackiconemo, - "blocks": [{ - "type": "header", - "text": { - "type": "plain_text", - "text": "Uptime Kuma Alert", - }, - }, - { - "type": "section", - "fields": [{ - "type": "mrkdwn", - "text": "*Message*\n" + msg, - }, - { - "type": "mrkdwn", - "text": "*Time (UTC)*\n" + time, - }], - }, - { - "type": "actions", - "elements": [ - { - "type": "button", - "text": { - "type": "plain_text", - "text": "Visit Uptime Kuma", - }, - "value": "Uptime-Kuma", - "url": notification.slackbutton || "https://github.com/louislam/uptime-kuma", - }, - ], - }], - } - await axios.post(notification.slackwebhookURL, data) - return okMsg; - } catch (error) { - throwGeneralAxiosError(error) - } - - } else if (notification.type === "rocket.chat") { - try { - if (heartbeatJSON == null) { - let data = { - "text": "Uptime Kuma Rocket.chat testing successful.", - "channel": notification.rocketchannel, - "username": notification.rocketusername, - "icon_emoji": notification.rocketiconemo, - } - await axios.post(notification.rocketwebhookURL, data) - return okMsg; - } - - const time = heartbeatJSON["time"]; - let data = { - "text": "Uptime Kuma Alert", - "channel": notification.rocketchannel, - "username": notification.rocketusername, - "icon_emoji": notification.rocketiconemo, - "attachments": [ - { - "title": "Uptime Kuma Alert *Time (UTC)*\n" + time, - "title_link": notification.rocketbutton, - "text": "*Message*\n" + msg, - "color": "#32cd32" - } - ] - } - await axios.post(notification.rocketwebhookURL, data) - return okMsg; - } catch (error) { - throwGeneralAxiosError(error) - } - - } else if (notification.type === "mattermost") { - try { - const mattermostUserName = notification.mattermostusername || "Uptime Kuma"; - // If heartbeatJSON is null, assume we're testing. - if (heartbeatJSON == null) { - let mattermostTestData = { - username: mattermostUserName, - text: msg, - } - await axios.post(notification.mattermostWebhookUrl, mattermostTestData) - return okMsg; - } - - const mattermostChannel = notification.mattermostchannel; - const mattermostIconEmoji = notification.mattermosticonemo; - const mattermostIconUrl = notification.mattermosticonurl; - - if (heartbeatJSON["status"] == DOWN) { - let mattermostdowndata = { - username: mattermostUserName, - text: "Uptime Kuma Alert", - channel: mattermostChannel, - icon_emoji: mattermostIconEmoji, - icon_url: mattermostIconUrl, - attachments: [ - { - fallback: - "Your " + - monitorJSON["name"] + - " service went down.", - color: "#FF0000", - title: - "❌ " + - monitorJSON["name"] + - " service went down. ❌", - title_link: monitorJSON["url"], - fields: [ - { - short: true, - title: "Service Name", - value: monitorJSON["name"], - }, - { - short: true, - title: "Time (UTC)", - value: heartbeatJSON["time"], - }, - { - short: false, - title: "Error", - value: heartbeatJSON["msg"], - }, - ], - }, - ], - }; - await axios.post( - notification.mattermostWebhookUrl, - mattermostdowndata - ); - return okMsg; - } else if (heartbeatJSON["status"] == UP) { - let mattermostupdata = { - username: mattermostUserName, - text: "Uptime Kuma Alert", - channel: mattermostChannel, - icon_emoji: mattermostIconEmoji, - icon_url: mattermostIconUrl, - attachments: [ - { - fallback: - "Your " + - monitorJSON["name"] + - " service went up!", - color: "#32CD32", - title: - "✅ " + - monitorJSON["name"] + - " service went up! ✅", - title_link: monitorJSON["url"], - fields: [ - { - short: true, - title: "Service Name", - value: monitorJSON["name"], - }, - { - short: true, - title: "Time (UTC)", - value: heartbeatJSON["time"], - }, - { - short: false, - title: "Ping", - value: heartbeatJSON["ping"] + "ms", - }, - ], - }, - ], - }; - await axios.post( - notification.mattermostWebhookUrl, - mattermostupdata - ); - return okMsg; - } - } catch (error) { - throwGeneralAxiosError(error); - } - - } else if (notification.type === "pushover") { - let pushoverlink = "https://api.pushover.net/1/messages.json" - try { - if (heartbeatJSON == null) { - let data = { - "message": "<b>Uptime Kuma Pushover testing successful.</b>", - "user": notification.pushoveruserkey, - "token": notification.pushoverapptoken, - "sound": notification.pushoversounds, - "priority": notification.pushoverpriority, - "title": notification.pushovertitle, - "retry": "30", - "expire": "3600", - "html": 1, - } - await axios.post(pushoverlink, data) - return okMsg; - } - - let data = { - "message": "<b>Uptime Kuma Alert</b>\n\n<b>Message</b>:" + msg + "\n<b>Time (UTC)</b>:" + heartbeatJSON["time"], - "user": notification.pushoveruserkey, - "token": notification.pushoverapptoken, - "sound": notification.pushoversounds, - "priority": notification.pushoverpriority, - "title": notification.pushovertitle, - "retry": "30", - "expire": "3600", - "html": 1, - } - await axios.post(pushoverlink, data) - return okMsg; - } catch (error) { - throwGeneralAxiosError(error) - } - - } else if (notification.type === "apprise") { - - return Notification.apprise(notification, msg) - - } else if (notification.type === "lunasea") { - let lunaseadevice = "https://notify.lunasea.app/v1/custom/device/" + notification.lunaseaDevice - - try { - if (heartbeatJSON == null) { - let testdata = { - "title": "Uptime Kuma Alert", - "body": "Testing Successful.", - } - await axios.post(lunaseadevice, testdata) - return okMsg; - } - - if (heartbeatJSON["status"] == DOWN) { - let downdata = { - "title": "UptimeKuma Alert: " + monitorJSON["name"], - "body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], - } - await axios.post(lunaseadevice, downdata) - return okMsg; - } - - if (heartbeatJSON["status"] == UP) { - let updata = { - "title": "UptimeKuma Alert: " + monitorJSON["name"], - "body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], - } - await axios.post(lunaseadevice, updata) - return okMsg; - } - - } catch (error) { - throwGeneralAxiosError(error) - } - - } else if (notification.type === "pushbullet") { - try { - let pushbulletUrl = "https://api.pushbullet.com/v2/pushes"; - let config = { - headers: { - "Access-Token": notification.pushbulletAccessToken, - "Content-Type": "application/json" - } - }; - if (heartbeatJSON == null) { - let testdata = { - "type": "note", - "title": "Uptime Kuma Alert", - "body": "Testing Successful.", - } - await axios.post(pushbulletUrl, testdata, config) - } else if (heartbeatJSON["status"] == DOWN) { - let downdata = { - "type": "note", - "title": "UptimeKuma Alert: " + monitorJSON["name"], - "body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], - } - await axios.post(pushbulletUrl, downdata, config) - } else if (heartbeatJSON["status"] == UP) { - let updata = { - "type": "note", - "title": "UptimeKuma Alert: " + monitorJSON["name"], - "body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], - } - await axios.post(pushbulletUrl, updata, config) - } - return okMsg; - } catch (error) { - throwGeneralAxiosError(error) - } - } else if (notification.type === "line") { - try { - let lineAPIUrl = "https://api.line.me/v2/bot/message/push"; - let config = { - headers: { - "Content-Type": "application/json", - "Authorization": "Bearer " + notification.lineChannelAccessToken - } - }; - if (heartbeatJSON == null) { - let testMessage = { - "to": notification.lineUserID, - "messages": [ - { - "type": "text", - "text": "Test Successful!" - } - ] - } - await axios.post(lineAPIUrl, testMessage, config) - } else if (heartbeatJSON["status"] == DOWN) { - let downMessage = { - "to": notification.lineUserID, - "messages": [ - { - "type": "text", - "text": "UptimeKuma Alert: [🔴 Down]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"] - } - ] - } - await axios.post(lineAPIUrl, downMessage, config) - } else if (heartbeatJSON["status"] == UP) { - let upMessage = { - "to": notification.lineUserID, - "messages": [ - { - "type": "text", - "text": "UptimeKuma Alert: [✅ Up]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"] - } - ] - } - await axios.post(lineAPIUrl, upMessage, config) - } - return okMsg; - } catch (error) { - throwGeneralAxiosError(error) - } + if (this.providerList[notification.type]) { + return this.providerList[notification.type].send(notification, msg, monitorJSON, heartbeatJSON); } else { - throw new Error("Notification type is not supported") + throw new Error("Notification type is not supported"); } } @@ -636,57 +109,6 @@ class Notification { await R.trash(bean) } - static async smtp(notification, msg, heartbeatJSON = null) { - - const config = { - host: notification.smtpHost, - port: notification.smtpPort, - secure: notification.smtpSecure, - }; - - // Should fix the issue in https://github.com/louislam/uptime-kuma/issues/26#issuecomment-896373904 - if (notification.smtpUsername || notification.smtpPassword) { - config.auth = { - user: notification.smtpUsername, - pass: notification.smtpPassword, - }; - } - - let transporter = nodemailer.createTransport(config); - - let bodyTextContent = msg; - if(heartbeatJSON) { - bodyTextContent = `${msg}\nTime (UTC): ${heartbeatJSON["time"]}`; - } - - // send mail with defined transport object - await transporter.sendMail({ - from: `"Uptime Kuma" <${notification.smtpFrom}>`, - to: notification.smtpTo, - subject: msg, - text: bodyTextContent, - }); - - return "Sent Successfully."; - } - - static async apprise(notification, msg) { - let s = child_process.spawnSync("apprise", [ "-vv", "-b", msg, notification.appriseURL]) - - let output = (s.stdout) ? s.stdout.toString() : "ERROR: maybe apprise not found"; - - if (output) { - - if (! output.includes("ERROR")) { - return "Sent Successfully"; - } - - throw new Error(output) - } else { - return "" - } - } - static checkApprise() { let commandExistsSync = require("command-exists").sync; let exists = commandExistsSync("apprise"); @@ -695,20 +117,6 @@ class Notification { } -function throwGeneralAxiosError(error) { - let msg = "Error: " + error + " "; - - if (error.response && error.response.data) { - if (typeof error.response.data === "string") { - msg += error.response.data; - } else { - msg += JSON.stringify(error.response.data) - } - } - - throw new Error(msg) -} - module.exports = { Notification, } diff --git a/server/server.js b/server/server.js index 7d9295354..29e0857a0 100644 --- a/server/server.js +++ b/server/server.js @@ -27,8 +27,11 @@ debug("Importing Monitor"); const Monitor = require("./model/monitor"); debug("Importing Settings"); const { getSettings, setSettings, setting, initJWTSecret } = require("./util-server"); + debug("Importing Notification"); const { Notification } = require("./notification"); +Notification.init(); + debug("Importing Database"); const Database = require("./database"); From d164b6ccce012180a9dcc493c6eb0ead8157e1f4 Mon Sep 17 00:00:00 2001 From: LouisLam <louislam@users.noreply.github.com> Date: Tue, 7 Sep 2021 23:06:49 +0800 Subject: [PATCH 3/5] prevent Chrome ask for saving password for notification settings (change to one-time-code to solve it) --- src/components/HiddenInput.vue | 38 +++++---------------------- src/components/NotificationDialog.vue | 20 +++++++------- 2 files changed, 17 insertions(+), 41 deletions(-) diff --git a/src/components/HiddenInput.vue b/src/components/HiddenInput.vue index 7ec9f2e49..2b588af19 100644 --- a/src/components/HiddenInput.vue +++ b/src/components/HiddenInput.vue @@ -1,11 +1,7 @@ <template> <div class="input-group mb-3"> - <!-- - Hack - Disable Chrome save password - readonly + onfocus - https://stackoverflow.com/questions/41217019/how-to-prevent-a-browser-from-storing-passwords - --> <input + ref="input" v-model="model" :type="visibility" class="form-control" @@ -13,8 +9,7 @@ :maxlength="maxlength" :autocomplete="autocomplete" :required="required" - :readonly="isReadOnly" - @focus="removeReadOnly" + :readonly="readonly" > <a v-if="visibility == 'password'" class="btn btn-outline-primary" @click="showInput()"> @@ -42,20 +37,20 @@ export default { default: 255 }, autocomplete: { - type: Boolean, + type: String, + default: undefined, }, required: { type: Boolean }, readonly: { - type: Boolean, - default: false, + type: String, + default: undefined, }, }, data() { return { visibility: "password", - readOnlyValue: false, } }, computed: { @@ -66,22 +61,10 @@ export default { set(value) { this.$emit("update:modelValue", value) } - }, - isReadOnly() { - // Actually readonly from prop - if (this.readonly) { - return true; - } - - // Hack - Disable Chrome save password - return this.readOnlyValue; } }, created() { - // Hack - Disable Chrome save password - if (this.autocomplete) { - this.readOnlyValue = "readonly"; - } + }, methods: { showInput() { @@ -90,13 +73,6 @@ export default { hideInput() { this.visibility = "password"; }, - - // Hack - Disable Chrome save password - removeReadOnly() { - if (this.autocomplete) { - this.readOnlyValue = false; - } - } } } </script> diff --git a/src/components/NotificationDialog.vue b/src/components/NotificationDialog.vue index 080fb859e..bc7d4c054 100644 --- a/src/components/NotificationDialog.vue +++ b/src/components/NotificationDialog.vue @@ -40,7 +40,7 @@ <template v-if="notification.type === 'telegram'"> <div class="mb-3"> <label for="telegram-bot-token" class="form-label">Bot Token</label> - <HiddenInput id="telegram-bot-token" v-model="notification.telegramBotToken" :required="true" :readonly="true"></HiddenInput> + <HiddenInput id="telegram-bot-token" v-model="notification.telegramBotToken" :required="true" autocomplete="one-time-code"></HiddenInput> <div class="form-text"> You can get a token from <a href="https://t.me/BotFather" target="_blank">https://t.me/BotFather</a>. </div> @@ -130,7 +130,7 @@ <div class="mb-3"> <label for="password" class="form-label">Password</label> - <HiddenInput id="password" v-model="notification.smtpPassword" :required="true" autocomplete="false"></HiddenInput> + <HiddenInput id="password" v-model="notification.smtpPassword" :required="true" autocomplete="one-time-code"></HiddenInput> </div> <div class="mb-3"> @@ -195,7 +195,7 @@ <template v-if="notification.type === 'gotify'"> <div class="mb-3"> <label for="gotify-application-token" class="form-label">Application Token</label> - <HiddenInput id="gotify-application-token" v-model="notification.gotifyapplicationToken" :required="true"></HiddenInput> + <HiddenInput id="gotify-application-token" v-model="notification.gotifyapplicationToken" :required="true" autocomplete="one-time-code"></HiddenInput> </div> <div class="mb-3"> <label for="gotify-server-url" class="form-label">Server URL</label> @@ -306,13 +306,13 @@ <template v-if="notification.type === 'pushy'"> <div class="mb-3"> <label for="pushy-app-token" class="form-label">API_KEY</label> - <HiddenInput id="pushy-app-token" v-model="notification.pushyAPIKey" :required="true"></HiddenInput> + <HiddenInput id="pushy-app-token" v-model="notification.pushyAPIKey" :required="true" autocomplete="one-time-code"></HiddenInput> </div> <div class="mb-3"> <label for="pushy-user-key" class="form-label">USER_TOKEN</label> <div class="input-group mb-3"> - <HiddenInput id="pushy-user-key" v-model="notification.pushyToken" :required="true"></HiddenInput> + <HiddenInput id="pushy-user-key" v-model="notification.pushyToken" :required="true" autocomplete="one-time-code"></HiddenInput> </div> </div> <p style="margin-top: 8px;"> @@ -323,7 +323,7 @@ <template v-if="notification.type === 'octopush'"> <div class="mb-3"> <label for="octopush-key" class="form-label">API KEY</label> - <HiddenInput id="octopush-key" v-model="notification.octopushAPIKey" :required="true"></HiddenInput> + <HiddenInput id="octopush-key" v-model="notification.octopushAPIKey" :required="true" autocomplete="one-time-code"></HiddenInput> <label for="octopush-login" class="form-label">API LOGIN</label> <input id="octopush-login" v-model="notification.octopushLogin" type="text" class="form-control" required> </div> @@ -354,9 +354,9 @@ <template v-if="notification.type === 'pushover'"> <div class="mb-3"> <label for="pushover-user" class="form-label">User Key<span style="color: red;"><sup>*</sup></span></label> - <HiddenInput id="pushover-user" v-model="notification.pushoveruserkey" :required="true"></HiddenInput> + <HiddenInput id="pushover-user" v-model="notification.pushoveruserkey" :required="true" autocomplete="one-time-code"></HiddenInput> <label for="pushover-app-token" class="form-label">Application Token<span style="color: red;"><sup>*</sup></span></label> - <HiddenInput id="pushover-app-token" v-model="notification.pushoverapptoken" :required="true"></HiddenInput> + <HiddenInput id="pushover-app-token" v-model="notification.pushoverapptoken" :required="true" autocomplete="one-time-code"></HiddenInput> <label for="pushover-device" class="form-label">Device</label> <input id="pushover-device" v-model="notification.pushoverdevice" type="text" class="form-control"> <label for="pushover-device" class="form-label">Message Title</label> @@ -442,7 +442,7 @@ <template v-if="notification.type === 'pushbullet'"> <div class="mb-3"> <label for="pushbullet-access-token" class="form-label">Access Token</label> - <HiddenInput id="pushbullet-access-token" v-model="notification.pushbulletAccessToken" :required="true"></HiddenInput> + <HiddenInput id="pushbullet-access-token" v-model="notification.pushbulletAccessToken" :required="true" autocomplete="one-time-code"></HiddenInput> </div> <p style="margin-top: 8px;"> @@ -453,7 +453,7 @@ <template v-if="notification.type === 'line'"> <div class="mb-3"> <label for="line-channel-access-token" class="form-label">Channel access token</label> - <HiddenInput id="line-channel-access-token" v-model="notification.lineChannelAccessToken" :required="true"></HiddenInput> + <HiddenInput id="line-channel-access-token" v-model="notification.lineChannelAccessToken" :required="true" autocomplete="one-time-code"></HiddenInput> </div> <div class="form-text"> Line Developers Console - <b>Basic Settings</b> From 4a106431f39328b13deec16ded67118c519426aa Mon Sep 17 00:00:00 2001 From: LouisLam <louislam@users.noreply.github.com> Date: Tue, 7 Sep 2021 23:32:05 +0800 Subject: [PATCH 4/5] convert Telegram into a vue components --- src/components/NotificationDialog.vue | 84 +++---------------- src/components/notifications/Telegram.vue | 98 +++++++++++++++++++++++ src/languages/da-DK.js | 3 +- src/languages/de-DE.js | 1 + src/languages/en.js | 3 +- src/languages/es-ES.js | 3 +- src/languages/fr-FR.js | 3 +- src/languages/ja.js | 3 +- src/languages/ko-KR.js | 3 +- src/languages/nl-NL.js | 3 +- src/languages/pl.js | 9 ++- src/languages/ru-RU.js | 3 +- src/languages/sr-latn.js | 3 +- src/languages/sr.js | 3 +- src/languages/sv-SE.js | 3 +- src/languages/zh-CN.js | 3 +- src/languages/zh-HK.js | 3 +- 17 files changed, 142 insertions(+), 89 deletions(-) create mode 100644 src/components/notifications/Telegram.vue diff --git a/src/components/NotificationDialog.vue b/src/components/NotificationDialog.vue index bc7d4c054..8a05d66ef 100644 --- a/src/components/NotificationDialog.vue +++ b/src/components/NotificationDialog.vue @@ -37,44 +37,9 @@ <input id="name" v-model="notification.name" type="text" class="form-control" required> </div> - <template v-if="notification.type === 'telegram'"> - <div class="mb-3"> - <label for="telegram-bot-token" class="form-label">Bot Token</label> - <HiddenInput id="telegram-bot-token" v-model="notification.telegramBotToken" :required="true" autocomplete="one-time-code"></HiddenInput> - <div class="form-text"> - You can get a token from <a href="https://t.me/BotFather" target="_blank">https://t.me/BotFather</a>. - </div> - </div> + <Telegram></Telegram> - <div class="mb-3"> - <label for="telegram-chat-id" class="form-label">Chat ID</label> - - <div class="input-group mb-3"> - <input id="telegram-chat-id" v-model="notification.telegramChatID" type="text" class="form-control" required> - <button v-if="notification.telegramBotToken" class="btn btn-outline-secondary" type="button" @click="autoGetTelegramChatID"> - Auto Get - </button> - </div> - - <div class="form-text"> - Support Direct Chat / Group / Channel's Chat ID - - <p style="margin-top: 8px;"> - You can get your chat id by sending message to the bot and go to this url to view the chat_id: - </p> - - <p style="margin-top: 8px;"> - <template v-if="notification.telegramBotToken"> - <a :href="telegramGetUpdatesURL" target="_blank" style="word-break: break-word;">{{ telegramGetUpdatesURL }}</a> - </template> - - <template v-else> - {{ telegramGetUpdatesURL }} - </template> - </p> - </div> - </div> - </template> + <!-- TODO: Convert all into vue components, but not an easy task. --> <template v-if="notification.type === 'webhook'"> <div class="mb-3"> @@ -469,6 +434,8 @@ First access the <a href="https://developers.line.biz/console/" target="_blank">Line Developers Console</a>, create a provider and channel (Messaging API), then you can get the channel access token and user id from the above mentioned menu items. </div> </template> + + <!-- DEPRECATED! Please create vue component in "./src/components/notifications/{notification name}.vue" --> </div> <div class="modal-footer"> <button v-if="id" type="button" class="btn btn-danger" :disabled="processing" @click="deleteConfirm"> @@ -495,15 +462,18 @@ import { Modal } from "bootstrap" import { ucfirst } from "../util.ts" import axios from "axios"; -import { useToast } from "vue-toastification" + import Confirm from "./Confirm.vue"; import HiddenInput from "./HiddenInput.vue"; -const toast = useToast() +import Telegram from "./notifications/Telegram.vue"; +import { useToast } from "vue-toastification" +const toast = useToast(); export default { components: { Confirm, HiddenInput, + Telegram, }, props: {}, data() { @@ -519,17 +489,7 @@ export default { appriseInstalled: false, } }, - computed: { - telegramGetUpdatesURL() { - let token = "<YOUR BOT TOKEN HERE>" - if (this.notification.telegramBotToken) { - token = this.notification.telegramBotToken; - } - - return `https://api.telegram.org/bot${token}/getUpdates`; - }, - }, watch: { "notification.type"(to, from) { let oldName; @@ -615,32 +575,6 @@ export default { } }) }, - - async autoGetTelegramChatID() { - try { - let res = await axios.get(this.telegramGetUpdatesURL) - - if (res.data.result.length >= 1) { - let update = res.data.result[res.data.result.length - 1] - - if (update.channel_post) { - this.notification.telegramChatID = update.channel_post.chat.id; - } else if (update.message) { - this.notification.telegramChatID = update.message.chat.id; - } else { - throw new Error("Chat ID is not found, please send a message to this bot first") - } - - } else { - throw new Error("Chat ID is not found, please send a message to this bot first") - } - - } catch (error) { - toast.error(error.message) - } - - }, - }, } </script> diff --git a/src/components/notifications/Telegram.vue b/src/components/notifications/Telegram.vue new file mode 100644 index 000000000..59d1992d6 --- /dev/null +++ b/src/components/notifications/Telegram.vue @@ -0,0 +1,98 @@ +<template> + <template v-if="$parent.notification.type === name"> + <div class="mb-3"> + <label for="telegram-bot-token" class="form-label">Bot Token</label> + <HiddenInput id="telegram-bot-token" v-model="$parent.notification.telegramBotToken" :required="true" autocomplete="one-time-code"></HiddenInput> + <div class="form-text"> + You can get a token from <a href="https://t.me/BotFather" target="_blank">https://t.me/BotFather</a>. + </div> + </div> + + <div class="mb-3"> + <label for="telegram-chat-id" class="form-label">Chat ID</label> + + <div class="input-group mb-3"> + <input id="telegram-chat-id" v-model="$parent.notification.telegramChatID" type="text" class="form-control" required> + <button v-if="$parent.notification.telegramBotToken" class="btn btn-outline-secondary" type="button" @click="autoGetTelegramChatID"> + {{ $t("Auto Get") }} + </button> + </div> + + <div class="form-text"> + Support Direct Chat / Group / Channel's Chat ID + + <p style="margin-top: 8px;"> + You can get your chat id by sending message to the bot and go to this url to view the chat_id: + </p> + + <p style="margin-top: 8px;"> + <template v-if="$parent.notification.telegramBotToken"> + <a :href="telegramGetUpdatesURL" target="_blank" style="word-break: break-word;">{{ telegramGetUpdatesURL }}</a> + </template> + + <template v-else> + {{ telegramGetUpdatesURL }} + </template> + </p> + </div> + </div> + </template> +</template> + +<script> +import HiddenInput from "../HiddenInput.vue"; +import axios from "axios"; +import { useToast } from "vue-toastification" +const toast = useToast(); + +export default { + components: { + HiddenInput, + }, + data() { + return { + name: "telegram", + } + }, + computed: { + telegramGetUpdatesURL() { + let token = "<YOUR BOT TOKEN HERE>" + + if (this.$parent.notification.telegramBotToken) { + token = this.$parent.notification.telegramBotToken; + } + + return `https://api.telegram.org/bot${token}/getUpdates`; + }, + }, + mounted() { + console.log(this.$parent.notification.type); + }, + methods: { + async autoGetTelegramChatID() { + try { + let res = await axios.get(this.telegramGetUpdatesURL) + + if (res.data.result.length >= 1) { + let update = res.data.result[res.data.result.length - 1] + + if (update.channel_post) { + this.notification.telegramChatID = update.channel_post.chat.id; + } else if (update.message) { + this.notification.telegramChatID = update.message.chat.id; + } else { + throw new Error("Chat ID is not found, please send a message to this bot first") + } + + } else { + throw new Error("Chat ID is not found, please send a message to this bot first") + } + + } catch (error) { + toast.error(error.message) + } + + }, + } +} +</script> diff --git a/src/languages/da-DK.js b/src/languages/da-DK.js index 7e2e8a9f1..c8e4e68fd 100644 --- a/src/languages/da-DK.js +++ b/src/languages/da-DK.js @@ -115,5 +115,6 @@ export default { confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", "Clear Data": "Clear Data", Events: "Events", - Heartbeats: "Heartbeats" + Heartbeats: "Heartbeats", + "Auto Get": "Auto Get" } diff --git a/src/languages/de-DE.js b/src/languages/de-DE.js index 9945bdff6..a39d2b110 100644 --- a/src/languages/de-DE.js +++ b/src/languages/de-DE.js @@ -116,4 +116,5 @@ export default { respTime: "Antw. Zeit (ms)", notAvailableShort: "N/A", Create: "Erstellen", + "Auto Get": "Auto Get" } diff --git a/src/languages/en.js b/src/languages/en.js index 435160750..cece7e96f 100644 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -115,5 +115,6 @@ export default { Create: "Create", "Clear Data": "Clear Data", Events: "Events", - Heartbeats: "Heartbeats" + Heartbeats: "Heartbeats", + "Auto Get": "Auto Get" } diff --git a/src/languages/es-ES.js b/src/languages/es-ES.js index fadd3fccc..035608483 100644 --- a/src/languages/es-ES.js +++ b/src/languages/es-ES.js @@ -115,5 +115,6 @@ export default { confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", "Clear Data": "Clear Data", Events: "Events", - Heartbeats: "Heartbeats" + Heartbeats: "Heartbeats", + "Auto Get": "Auto Get" } diff --git a/src/languages/fr-FR.js b/src/languages/fr-FR.js index f11781966..dca82ac25 100644 --- a/src/languages/fr-FR.js +++ b/src/languages/fr-FR.js @@ -115,5 +115,6 @@ export default { confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", "Clear Data": "Clear Data", Events: "Events", - Heartbeats: "Heartbeats" + Heartbeats: "Heartbeats", + "Auto Get": "Auto Get" } diff --git a/src/languages/ja.js b/src/languages/ja.js index f7fb55eee..d7b4e6e2a 100644 --- a/src/languages/ja.js +++ b/src/languages/ja.js @@ -115,5 +115,6 @@ export default { confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", "Clear Data": "Clear Data", Events: "Events", - Heartbeats: "Heartbeats" + Heartbeats: "Heartbeats", + "Auto Get": "Auto Get" } diff --git a/src/languages/ko-KR.js b/src/languages/ko-KR.js index 8956de072..d57dcbd7e 100644 --- a/src/languages/ko-KR.js +++ b/src/languages/ko-KR.js @@ -115,5 +115,6 @@ export default { confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", "Clear Data": "Clear Data", Events: "Events", - Heartbeats: "Heartbeats" + Heartbeats: "Heartbeats", + "Auto Get": "Auto Get" } diff --git a/src/languages/nl-NL.js b/src/languages/nl-NL.js index 647d136b2..ef2305ca5 100644 --- a/src/languages/nl-NL.js +++ b/src/languages/nl-NL.js @@ -115,5 +115,6 @@ export default { confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", "Clear Data": "Clear Data", Events: "Events", - Heartbeats: "Heartbeats" + Heartbeats: "Heartbeats", + "Auto Get": "Auto Get" } diff --git a/src/languages/pl.js b/src/languages/pl.js index cc625d6de..74137dbbf 100644 --- a/src/languages/pl.js +++ b/src/languages/pl.js @@ -109,5 +109,12 @@ export default { "Repeat Password": "Powtórz hasło", respTime: "Czas odp. (ms)", notAvailableShort: "N/A", - Create: "Stwórz" + Create: "Stwórz", + clearEventsMsg: "Are you sure want to delete all events for this monitor?", + clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?", + confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", + "Clear Data": "Clear Data", + Events: "Events", + Heartbeats: "Heartbeats", + "Auto Get": "Auto Get" } diff --git a/src/languages/ru-RU.js b/src/languages/ru-RU.js index e3835816b..1cea0567f 100644 --- a/src/languages/ru-RU.js +++ b/src/languages/ru-RU.js @@ -115,5 +115,6 @@ export default { confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", "Clear Data": "Clear Data", Events: "Events", - Heartbeats: "Heartbeats" + Heartbeats: "Heartbeats", + "Auto Get": "Auto Get" } diff --git a/src/languages/sr-latn.js b/src/languages/sr-latn.js index 98453decd..46a6efec7 100644 --- a/src/languages/sr-latn.js +++ b/src/languages/sr-latn.js @@ -115,5 +115,6 @@ export default { confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", "Clear Data": "Clear Data", Events: "Events", - Heartbeats: "Heartbeats" + Heartbeats: "Heartbeats", + "Auto Get": "Auto Get" } diff --git a/src/languages/sr.js b/src/languages/sr.js index 779754b96..c46244ada 100644 --- a/src/languages/sr.js +++ b/src/languages/sr.js @@ -115,5 +115,6 @@ export default { confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", "Clear Data": "Clear Data", Events: "Events", - Heartbeats: "Heartbeats" + Heartbeats: "Heartbeats", + "Auto Get": "Auto Get" } diff --git a/src/languages/sv-SE.js b/src/languages/sv-SE.js index c830e8907..a07396678 100644 --- a/src/languages/sv-SE.js +++ b/src/languages/sv-SE.js @@ -115,5 +115,6 @@ export default { confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", "Clear Data": "Clear Data", Events: "Events", - Heartbeats: "Heartbeats" + Heartbeats: "Heartbeats", + "Auto Get": "Auto Get" } diff --git a/src/languages/zh-CN.js b/src/languages/zh-CN.js index 5491ab5be..d837aced5 100644 --- a/src/languages/zh-CN.js +++ b/src/languages/zh-CN.js @@ -115,5 +115,6 @@ export default { confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", "Clear Data": "Clear Data", Events: "Events", - Heartbeats: "Heartbeats" + Heartbeats: "Heartbeats", + "Auto Get": "Auto Get" } diff --git a/src/languages/zh-HK.js b/src/languages/zh-HK.js index b76bdfc85..d7aa9de88 100644 --- a/src/languages/zh-HK.js +++ b/src/languages/zh-HK.js @@ -115,5 +115,6 @@ export default { confirmClearStatisticsMsg: "是否確定刪除所有監測器的脈搏資料?(您的監測器會繼續正常運作)", "Clear Data": "清除資料", Events: "事件", - Heartbeats: "脈搏" + Heartbeats: "脈搏", + "Auto Get": "自動獲取" } From 5ca0dd628d09616dafed5d0906afaaa1939ba121 Mon Sep 17 00:00:00 2001 From: LouisLam <louislam@users.noreply.github.com> Date: Tue, 7 Sep 2021 23:36:55 +0800 Subject: [PATCH 5/5] remove debug msg --- src/components/HeartbeatBar.vue | 1 - src/components/notifications/Telegram.vue | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/HeartbeatBar.vue b/src/components/HeartbeatBar.vue index 04d046bd5..fb6086d00 100644 --- a/src/components/HeartbeatBar.vue +++ b/src/components/HeartbeatBar.vue @@ -137,7 +137,6 @@ export default { if (! Number.isInteger(actualWidth)) { this.beatWidth = Math.round(actualWidth) / window.devicePixelRatio; - console.log(this.beatWidth); } if (! Number.isInteger(actualMargin)) { diff --git a/src/components/notifications/Telegram.vue b/src/components/notifications/Telegram.vue index 59d1992d6..537b7fd34 100644 --- a/src/components/notifications/Telegram.vue +++ b/src/components/notifications/Telegram.vue @@ -66,7 +66,7 @@ export default { }, }, mounted() { - console.log(this.$parent.notification.type); + }, methods: { async autoGetTelegramChatID() {