mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-11-27 16:54:04 +00:00
add telegram notification
This commit is contained in:
parent
04ec91d7a9
commit
3bdf174e90
11 changed files with 418 additions and 85 deletions
39
package-lock.json
generated
39
package-lock.json
generated
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "uptime-kuma",
|
"name": "uptime-kuma",
|
||||||
"version": "0.0.0",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -14,6 +14,12 @@
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz",
|
||||||
"integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA=="
|
"integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA=="
|
||||||
},
|
},
|
||||||
|
"@babel/standalone": {
|
||||||
|
"version": "7.14.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/standalone/-/standalone-7.14.7.tgz",
|
||||||
|
"integrity": "sha512-7RlfMPR4604SbYpj5zvs2ZK587hVhixgU9Pd9Vs8lB8KYtT3U0apXSf0vZXhy8XRh549eUmJOHXhWKTO3ObzOQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@babel/types": {
|
"@babel/types": {
|
||||||
"version": "7.14.5",
|
"version": "7.14.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz",
|
||||||
|
@ -48,6 +54,19 @@
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.4.tgz",
|
||||||
"integrity": "sha512-zrNj1+yqYF4WskCMOHwN+w9iuD12+dGm0rQ35HLl9/Ouuq52cEtd0CH9qMgrdNmi5ejC1/V7vKEXYubB+65DkA=="
|
"integrity": "sha512-zrNj1+yqYF4WskCMOHwN+w9iuD12+dGm0rQ35HLl9/Ouuq52cEtd0CH9qMgrdNmi5ejC1/V7vKEXYubB+65DkA=="
|
||||||
},
|
},
|
||||||
|
"@vitejs/plugin-legacy": {
|
||||||
|
"version": "1.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-legacy/-/plugin-legacy-1.4.3.tgz",
|
||||||
|
"integrity": "sha512-lxZUJaMWYMQuqvZM1wPzDP6KABQgA/drVL5fnaygEPcz9adc2OHhfFNN/SvvHQ1V0rP8gybIc7uA+iI1gAdkVQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/standalone": "^7.14.7",
|
||||||
|
"core-js": "^3.15.1",
|
||||||
|
"magic-string": "^0.25.7",
|
||||||
|
"regenerator-runtime": "^0.13.7",
|
||||||
|
"systemjs": "^6.10.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@vitejs/plugin-vue": {
|
"@vitejs/plugin-vue": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.2.3.tgz",
|
||||||
|
@ -618,6 +637,12 @@
|
||||||
"resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
|
||||||
"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40="
|
"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40="
|
||||||
},
|
},
|
||||||
|
"core-js": {
|
||||||
|
"version": "3.15.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.15.2.tgz",
|
||||||
|
"integrity": "sha512-tKs41J7NJVuaya8DxIOCnl8QuPHx5/ZVbFo1oKgVl1qHFBBrDctzQGtuLjPpRdNTWmKPH6oEvgN/MUID+l485Q==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"core-util-is": {
|
"core-util-is": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||||
|
@ -2682,6 +2707,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"regenerator-runtime": {
|
||||||
|
"version": "0.13.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
|
||||||
|
"integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"regex-not": {
|
"regex-not": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
|
||||||
|
@ -3203,6 +3234,12 @@
|
||||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||||
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
|
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
|
||||||
},
|
},
|
||||||
|
"systemjs": {
|
||||||
|
"version": "6.10.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/systemjs/-/systemjs-6.10.2.tgz",
|
||||||
|
"integrity": "sha512-PwaC0Z6Y1E6gFekY2u38EC5+5w2M65jYVrD1aAcOptpHVhCwPIwPFJvYJyryQKUyeuQ5bKKI3PBHWNjdE9aizg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"tarn": {
|
"tarn": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.1.tgz",
|
||||||
|
|
|
@ -3,9 +3,10 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host",
|
"dev": "vite --host",
|
||||||
"dev-server": "node server/server.js",
|
"start-server": "node server/server.js",
|
||||||
"build": "vite build",
|
"update": "",
|
||||||
"serve": "vite preview --host"
|
"build": "npm install && vite build",
|
||||||
|
"vite-preview-dist": "vite preview --host"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@popperjs/core": "^2.9.2",
|
"@popperjs/core": "^2.9.2",
|
||||||
|
@ -25,8 +26,10 @@
|
||||||
"vue-toastification": "^2.0.0-rc.1"
|
"vue-toastification": "^2.0.0-rc.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-legacy": "^1.4.3",
|
||||||
"@vitejs/plugin-vue": "^1.2.3",
|
"@vitejs/plugin-vue": "^1.2.3",
|
||||||
"@vue/compiler-sfc": "^3.0.5",
|
"@vue/compiler-sfc": "^3.0.5",
|
||||||
|
"core-js": "^3.15.2",
|
||||||
"sass": "^1.35.1",
|
"sass": "^1.35.1",
|
||||||
"vite": "^2.3.7"
|
"vite": "^2.3.7"
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,71 +141,52 @@ class Monitor extends BeanModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Uptime with calculation
|
||||||
|
* Calculation based on:
|
||||||
|
* https://www.uptrends.com/support/kb/reporting/calculation-of-uptime-and-downtime
|
||||||
* @param duration : int Hours
|
* @param duration : int Hours
|
||||||
*/
|
*/
|
||||||
static async sendUptime(duration, io, monitorID, userID) {
|
static async sendUptime(duration, io, monitorID, userID) {
|
||||||
let sec = duration * 3600;
|
let sec = duration * 3600;
|
||||||
|
|
||||||
let downtimeList = await R.getAll(`
|
let downtimeList = await R.getAll(`
|
||||||
SELECT duration, time
|
SELECT duration, time, status
|
||||||
FROM heartbeat
|
FROM heartbeat
|
||||||
WHERE time > DATE('now', ? || ' hours')
|
WHERE time > DATE('now', ? || ' hours')
|
||||||
AND status = 0
|
|
||||||
AND monitor_id = ? `, [
|
AND monitor_id = ? `, [
|
||||||
-duration,
|
-duration,
|
||||||
monitorID
|
monitorID
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let downtime = 0;
|
let downtime = 0;
|
||||||
let uptime = 0;
|
let total = 0;
|
||||||
|
let uptime;
|
||||||
|
|
||||||
if (downtimeList.length === 0) {
|
for (let row of downtimeList) {
|
||||||
for (let row of downtimeList) {
|
let value = parseInt(row.duration)
|
||||||
let value = parseInt(row.duration)
|
let time = row.time
|
||||||
let time = row.time
|
|
||||||
|
|
||||||
// Handle if heartbeat duration longer than the target duration
|
// Handle if heartbeat duration longer than the target duration
|
||||||
// e.g. Heartbeat duration = 28hrs, but target duration = 24hrs
|
// e.g. Heartbeat duration = 28hrs, but target duration = 24hrs
|
||||||
if (value <= sec) {
|
if (value > sec) {
|
||||||
downtime += value;
|
let trim = dayjs.utc().diff(dayjs(time), 'second');
|
||||||
} else {
|
value = sec - trim;
|
||||||
let trim = dayjs.utc().diff(dayjs(time), 'second');
|
|
||||||
|
|
||||||
value = sec - trim;
|
if (value < 0) {
|
||||||
|
value = 0;
|
||||||
if (value < 0) {
|
|
||||||
value = 0;
|
|
||||||
}
|
|
||||||
downtime += value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uptime = (sec - downtime) / sec;
|
total += value;
|
||||||
|
if (row.status === 0) {
|
||||||
if (uptime < 0) {
|
downtime += value;
|
||||||
uptime = 0;
|
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
// This case for someone who are not running UptimeKuma 24x7.
|
|
||||||
// If there is no heartbeat in this time range, use last heartbeat as reference
|
|
||||||
// If is down, uptime = 0
|
|
||||||
// If is up, uptime = 1
|
|
||||||
|
|
||||||
let lastHeartbeat = await R.findOne("heartbeat", " monitor_id = ? ORDER BY time DESC", [
|
uptime = (total - downtime) / total;
|
||||||
monitorID
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (lastHeartbeat) {
|
if (uptime < 0) {
|
||||||
if (lastHeartbeat.status === 1) {
|
uptime = 0;
|
||||||
uptime = 1;
|
|
||||||
} else {
|
|
||||||
uptime = 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// No heartbeat is found, assume 100%
|
|
||||||
uptime = 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
io.to(userID).emit("uptime", monitorID, duration, uptime);
|
io.to(userID).emit("uptime", monitorID, duration, uptime);
|
||||||
|
|
58
server/notification.js
Normal file
58
server/notification.js
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
const axios = require("axios");
|
||||||
|
const {R} = require("redbean-node");
|
||||||
|
|
||||||
|
class Notification {
|
||||||
|
static async send(notification, msg) {
|
||||||
|
if (notification.type === "telegram") {
|
||||||
|
let res = await axios.get(`https://api.telegram.org/bot${notification.telegramBotToken}/sendMessage`, {
|
||||||
|
params: {
|
||||||
|
chat_id: notification.telegramChatID,
|
||||||
|
text: msg,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error("Notification type is not supported")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async save(notification, notificationID, userID) {
|
||||||
|
let bean
|
||||||
|
|
||||||
|
if (notificationID) {
|
||||||
|
bean = await R.findOne("notification", " id = ? AND user_id = ? ", [
|
||||||
|
notificationID,
|
||||||
|
userID,
|
||||||
|
])
|
||||||
|
|
||||||
|
if (! bean) {
|
||||||
|
throw new Error("notification not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
bean = R.dispense("notification")
|
||||||
|
}
|
||||||
|
|
||||||
|
bean.name = notification.name;
|
||||||
|
bean.user_id = userID;
|
||||||
|
bean.config = JSON.stringify(notification)
|
||||||
|
await R.store(bean)
|
||||||
|
}
|
||||||
|
|
||||||
|
static async delete(notificationID, userID) {
|
||||||
|
let bean = await R.findOne("notification", " id = ? AND user_id = ? ", [
|
||||||
|
notificationID,
|
||||||
|
userID,
|
||||||
|
])
|
||||||
|
|
||||||
|
if (! bean) {
|
||||||
|
throw new Error("notification not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
await R.trash(bean)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
Notification,
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ const passwordHash = require('password-hash');
|
||||||
const jwt = require('jsonwebtoken');
|
const jwt = require('jsonwebtoken');
|
||||||
const Monitor = require("./model/monitor");
|
const Monitor = require("./model/monitor");
|
||||||
const {getSettings} = require("./util-server");
|
const {getSettings} = require("./util-server");
|
||||||
|
const {Notification} = require("./notification")
|
||||||
|
|
||||||
let totalClient = 0;
|
let totalClient = 0;
|
||||||
let jwtSecret = null;
|
let jwtSecret = null;
|
||||||
|
@ -26,6 +27,10 @@ let monitorList = {};
|
||||||
|
|
||||||
app.use('/', express.static("dist"));
|
app.use('/', express.static("dist"));
|
||||||
|
|
||||||
|
app.get('*', function(request, response, next) {
|
||||||
|
response.sendFile(process.cwd() + '/dist/index.html');
|
||||||
|
});
|
||||||
|
|
||||||
io.on('connection', async (socket) => {
|
io.on('connection', async (socket) => {
|
||||||
console.log('a user connected');
|
console.log('a user connected');
|
||||||
totalClient++;
|
totalClient++;
|
||||||
|
@ -318,6 +323,65 @@ let monitorList = {};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add or Edit
|
||||||
|
socket.on("addNotification", async (notification, notificationID, callback) => {
|
||||||
|
try {
|
||||||
|
checkLogin(socket)
|
||||||
|
|
||||||
|
await Notification.save(notification, notificationID, socket.userID)
|
||||||
|
await sendNotificationList(socket)
|
||||||
|
|
||||||
|
callback({
|
||||||
|
ok: true,
|
||||||
|
msg: "Saved",
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
callback({
|
||||||
|
ok: false,
|
||||||
|
msg: e.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("deleteNotification", async (notificationID, callback) => {
|
||||||
|
try {
|
||||||
|
checkLogin(socket)
|
||||||
|
|
||||||
|
await Notification.delete(notificationID, socket.userID)
|
||||||
|
await sendNotificationList(socket)
|
||||||
|
|
||||||
|
callback({
|
||||||
|
ok: true,
|
||||||
|
msg: "Deleted",
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
callback({
|
||||||
|
ok: false,
|
||||||
|
msg: e.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("testNotification", async (notification, callback) => {
|
||||||
|
try {
|
||||||
|
checkLogin(socket)
|
||||||
|
|
||||||
|
await Notification.send(notification, notification.name + " Testing")
|
||||||
|
|
||||||
|
callback({
|
||||||
|
ok: true,
|
||||||
|
msg: "Sent Successfully"
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
callback({
|
||||||
|
ok: false,
|
||||||
|
msg: e.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
server.listen(3001, () => {
|
server.listen(3001, () => {
|
||||||
|
@ -344,6 +408,20 @@ async function sendMonitorList(socket) {
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function sendNotificationList(socket) {
|
||||||
|
let result = [];
|
||||||
|
let list = await R.find("notification", " user_id = ? ", [
|
||||||
|
socket.userID
|
||||||
|
]);
|
||||||
|
|
||||||
|
for (let bean of list) {
|
||||||
|
result.push(bean.export())
|
||||||
|
}
|
||||||
|
|
||||||
|
io.to(socket.userID).emit("notificationList", result)
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
async function afterLogin(socket, user) {
|
async function afterLogin(socket, user) {
|
||||||
socket.userID = user.id;
|
socket.userID = user.id;
|
||||||
socket.join(user.id)
|
socket.join(user.id)
|
||||||
|
@ -355,6 +433,8 @@ async function afterLogin(socket, user) {
|
||||||
await sendImportantHeartbeatList(socket, monitorID);
|
await sendImportantHeartbeatList(socket, monitorID);
|
||||||
await Monitor.sendStats(io, monitorID, user.id)
|
await Monitor.sendStats(io, monitorID, user.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await sendNotificationList(socket)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getMonitorJSONList(userID) {
|
async function getMonitorJSONList(userID) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
const tcpp = require('tcp-ping');
|
const tcpp = require('tcp-ping');
|
||||||
const Ping = require("./ping-lite");
|
const Ping = require("./ping-lite");
|
||||||
|
const {R} = require("redbean-node");
|
||||||
|
|
||||||
exports.tcping = function (hostname, port) {
|
exports.tcping = function (hostname, port) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
@ -37,3 +38,25 @@ exports.ping = function (hostname) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.setting = async function (key) {
|
||||||
|
return await R.getCell("SELECT `value` FROM setting WHERE `key` = ? ", [
|
||||||
|
key
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.getSettings = async function (type) {
|
||||||
|
let list = await R.getAll("SELECT * FROM setting WHERE `type` = ? ", [
|
||||||
|
type
|
||||||
|
])
|
||||||
|
|
||||||
|
let result = {};
|
||||||
|
|
||||||
|
for (let row of list) {
|
||||||
|
result[row.key] = row.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(result)
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,10 @@ export function sleep(ms) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ucfirst(str) {
|
export function ucfirst(str) {
|
||||||
|
if (! str) {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
const firstLetter = str.substr(0, 1);
|
const firstLetter = str.substr(0, 1);
|
||||||
return firstLetter.toUpperCase() + str.substr(1);
|
return firstLetter.toUpperCase() + str.substr(1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,42 +1,81 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="modal fade" tabindex="-1" ref="modal" data-bs-backdrop="static">
|
<form @submit.prevent="submit">
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title" id="exampleModalLabel">Setup Notification</h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form @submit.prevent="submit">
|
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="modal fade" tabindex="-1" ref="modal" data-bs-backdrop="static">
|
||||||
<label for="type" class="form-label">Notification Type</label>
|
<div class="modal-dialog">
|
||||||
<select class="form-select" id="type" v-model="type">
|
<div class="modal-content">
|
||||||
<option value="email">Email</option>
|
<div class="modal-header">
|
||||||
<option value="webhook">Webhook</option>
|
<h5 class="modal-title" id="exampleModalLabel">Setup Notification</h5>
|
||||||
<option value="telegram">Telegram</option>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
<option value="discord">Discord</option>
|
</div>
|
||||||
</select>
|
<div class="modal-body">
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="name" class="form-label">Friendly Name</label>
|
|
||||||
<input type="text" class="form-control" id="name" required v-model="name">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
<div class="mb-3">
|
||||||
</div>
|
<label for="type" class="form-label">Notification Type</label>
|
||||||
<div class="modal-footer">
|
<select class="form-select" id="type" v-model="notification.type">
|
||||||
<button type="button" class="btn btn-primary" @click="yes" data-bs-dismiss="modal">Save</button>
|
<option value="telegram">Telegram</option>
|
||||||
|
<option value="webhook">Webhook</option>
|
||||||
|
<option value="email">Email</option>
|
||||||
|
<option value="discord">Discord</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="name" class="form-label">Friendly Name</label>
|
||||||
|
<input type="text" class="form-control" id="name" required v-model="notification.name">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3" v-if="notification.type === 'telegram'">
|
||||||
|
<label for="telegram-bot-token" class="form-label">Bot Token</label>
|
||||||
|
<input type="text" class="form-control" id="telegram-bot-token" required v-model="notification.telegramBotToken">
|
||||||
|
<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" v-if="notification.type === 'telegram'">
|
||||||
|
<label for="telegram-chat-id" class="form-label">Chat ID</label>
|
||||||
|
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<input type="text" class="form-control" id="telegram-chat-id" required v-model="notification.telegramChatID">
|
||||||
|
<button class="btn btn-outline-secondary" type="button" @click="autoGetTelegramChatID" v-if="notification.telegramBotToken">Auto Get</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-text">
|
||||||
|
You can get your chat id by sending message to the bot and go to this url to view the chat_id:
|
||||||
|
|
||||||
|
<p style="margin-top: 8px;">
|
||||||
|
|
||||||
|
<template v-if="notification.telegramBotToken">
|
||||||
|
<a :href="telegramGetUpdatesURL" target="_blank">{{ telegramGetUpdatesURL }}</a>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
{{ telegramGetUpdatesURL }}
|
||||||
|
</template>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-danger" @click="deleteNotification" :disabled="processing" v-if="id">Delete</button>
|
||||||
|
<button type="button" class="btn btn-warning" @click="test" :disabled="processing">Test</button>
|
||||||
|
<button type="submit" class="btn btn-primary" :disabled="processing">Save</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { Modal } from 'bootstrap'
|
import { Modal } from 'bootstrap'
|
||||||
import { ucfirst } from "../../server/util";
|
import { ucfirst } from "../../server/util";
|
||||||
|
import axios from "axios";
|
||||||
|
import { useToast } from 'vue-toastification'
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
@ -45,33 +84,123 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
model: null,
|
model: null,
|
||||||
type: null,
|
processing: false,
|
||||||
name: "",
|
id: null,
|
||||||
|
notification: {
|
||||||
|
name: "",
|
||||||
|
type: null,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.modal = new Modal(this.$refs.modal)
|
this.modal = new Modal(this.$refs.modal)
|
||||||
|
|
||||||
|
// TODO: for edit
|
||||||
|
this.$root.getSocket().emit("getSettings", "notification", (data) => {
|
||||||
|
// this.notification = data
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
show() {
|
|
||||||
|
show(notificationID) {
|
||||||
|
if (notificationID) {
|
||||||
|
this.id = notificationID;
|
||||||
|
|
||||||
|
for (let n of this.$root.notificationList) {
|
||||||
|
if (n.id === notificationID) {
|
||||||
|
this.notification = JSON.parse(n.config);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.id = null;
|
||||||
|
this.notification = {
|
||||||
|
name: "",
|
||||||
|
type: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default set to Telegram
|
||||||
|
this.notification.type = "telegram"
|
||||||
|
}
|
||||||
|
|
||||||
this.modal.show()
|
this.modal.show()
|
||||||
},
|
},
|
||||||
submit() {
|
|
||||||
|
|
||||||
}
|
submit() {
|
||||||
|
this.processing = true;
|
||||||
|
this.$root.getSocket().emit("addNotification", this.notification, this.id, (res) => {
|
||||||
|
this.$root.toastRes(res)
|
||||||
|
this.processing = false;
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
this.modal.hide()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
test() {
|
||||||
|
this.processing = true;
|
||||||
|
this.$root.getSocket().emit("testNotification", this.notification, (res) => {
|
||||||
|
this.$root.toastRes(res)
|
||||||
|
this.processing = false;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteNotification() {
|
||||||
|
this.processing = true;
|
||||||
|
this.$root.getSocket().emit("deleteNotification", this.id, (res) => {
|
||||||
|
this.$root.toastRes(res)
|
||||||
|
this.processing = false;
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
this.modal.hide()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
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]
|
||||||
|
this.notification.telegramChatID = update.message.chat.id;
|
||||||
|
} else {
|
||||||
|
throw new Error("Chat ID is not found, please send a message to this bot first")
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
toast.error(error.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
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: {
|
watch: {
|
||||||
type(to, from) {
|
"notification.type"(to, from) {
|
||||||
let oldName;
|
let oldName;
|
||||||
|
|
||||||
if (from) {
|
if (from) {
|
||||||
oldName = `My ${ucfirst(from)} Notification`;
|
oldName = `My ${ucfirst(from)} Alert (1)`;
|
||||||
} else {
|
} else {
|
||||||
oldName = "";
|
oldName = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! this.name || this.name === oldName) {
|
if (! this.notification.name || this.notification.name === oldName) {
|
||||||
this.name = `My ${ucfirst(to)} Alert (1)`
|
this.notification.name = `My ${ucfirst(to)} Alert (1)`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,8 @@ export default {
|
||||||
heartbeatList: { },
|
heartbeatList: { },
|
||||||
importantHeartbeatList: { },
|
importantHeartbeatList: { },
|
||||||
avgPingList: { },
|
avgPingList: { },
|
||||||
uptimeList: { }
|
uptimeList: { },
|
||||||
|
notificationList: [],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -36,6 +37,10 @@ export default {
|
||||||
this.monitorList = data;
|
this.monitorList = data;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on('notificationList', (data) => {
|
||||||
|
this.notificationList = data;
|
||||||
|
});
|
||||||
|
|
||||||
socket.on('heartbeat', (data) => {
|
socket.on('heartbeat', (data) => {
|
||||||
if (! (data.monitorID in this.heartbeatList)) {
|
if (! (data.monitorID in this.heartbeatList)) {
|
||||||
this.heartbeatList[data.monitorID] = [];
|
this.heartbeatList[data.monitorID] = [];
|
||||||
|
|
|
@ -55,7 +55,13 @@
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h2>Notifications</h2>
|
<h2>Notifications</h2>
|
||||||
<p>Not available, please setup.</p>
|
<p v-if="$root.notificationList.length === 0">Not available, please setup.</p>
|
||||||
|
|
||||||
|
<div class="form-check form-switch mb-3" v-for="notification in $root.notificationList">
|
||||||
|
<input class="form-check-input" type="checkbox" :id=" 'notification' + notification.id">
|
||||||
|
<label class="form-check-label" :for=" 'notification' + notification.id">{{ notification.name }} <a href="#" @click="$refs.notificationDialog.show(notification.id)">Edit</a></label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button class="btn btn-primary me-2" @click="$refs.notificationDialog.show()" type="button">Setup Notification</button>
|
<button class="btn btn-primary me-2" @click="$refs.notificationDialog.show()" type="button">Setup Notification</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import legacy from '@vitejs/plugin-legacy'
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [vue()]
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
legacy({
|
||||||
|
targets: ['ie >= 11'],
|
||||||
|
additionalLegacyPolyfills: ['regenerator-runtime/runtime']
|
||||||
|
})
|
||||||
|
]
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue