You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

292 lines
9.6 KiB

const { R } = require("redbean-node");
const { Liquid } = require( 'liquidjs');
const { UP } = require("../src/util");
const dayjs = require("dayjs");
const engine = new Liquid();
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 Matrix = require("./notification-providers/matrix");
const Octopush = require("./notification-providers/octopush");
const PromoSMS = require("./notification-providers/promosms");
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 Teams = require("./notification-providers/teams");
const Telegram = require("./notification-providers/telegram");
const Webhook = require("./notification-providers/webhook");
const Feishu = require("./notification-providers/feishu");
const AliyunSms = require("./notification-providers/aliyun-sms");
const DingDing = require("./notification-providers/dingding");
const MinimalDetailTemplate = "{{monitor.name}}: {{monitor.health}}"
const LowDetailTemplate = "[{{monitor.name}}] [{{monitor.health}}] {{heartbeat.msg}}"
const MediumDetailTemplate = `Monitor: {{monitor.name}}
Health: {{monitor.health}}
Address: {{monitor.url}}
{% if heartbeat.status == 1 and monitor.upsideDown -%}
Your Upside down {{monitor.type}} monitor is unexpectedly connected
{%- elsif heartbeat.status == 1 and monitor.upsideDown==false -%}
Your {{monitor.type}} monitor is up
{%- elsif heartbeat.status == 0 and monitor.upsideDown -%}
Your Upside down {{monitor.type}} monitor is no longer connected
{%- elsif heartbeat.status == 0 and monitor.upsideDown == false -%}
Your {{monitor.type}} monitor is unexpectedly down.
{%- endif %}
Time: {{heartbeat.time}}
Uptime Message: {{heartbeat.msg}}`
const FullDetailTemplate = `Monitor: {{monitor.name}}
Health: {{monitor.health}}
Address: {{monitor.url}}
{% if heartbeat.status == 1 and monitor.upsideDown -%}
Your Upside down {{monitor.type}} monitor is unexpectedly connected
{%- elsif heartbeat.status == 1 and monitor.upsideDown==false -%}
Your {{monitor.type}} monitor is up
{%- elsif heartbeat.status == 0 and monitor.upsideDown -%}
Your Upside down {{monitor.type}} monitor is no longer connected
{%- elsif heartbeat.status == 0 and monitor.upsideDown == false -%}
Your {{monitor.type}} monitor is unexpectedly down.
{%- endif %}
Time: {{heartbeat.time}}
Uptime Message: {{heartbeat.msg}}
Tags
----------------------------------------
{% for tag in monitor.tags -%}
{{ tag.name }}
{%- if tag.value and tag.value != "" -%}
: {{tag.value}}
{%- endif %}
{% endfor -%}`
class Notification {
static generateTestHeartbeat(){
return {
monitorID: 5,
status: 1,
time: R.isoDateTime(dayjs.utc()),
msg: "TEST NOTIFICATION MESSAGE",
ping: 278,
important: true,
duration: 8,
};
}
static generateTestMonitor(){
return {
id: 5,
name: "Test Notification Monitor",
url: "https://www.example.com",
method: "Get",
body: "OK",
headers: null,
hostname: "www.example.com",
port: 443,
maxretries: 2,
weight: 2000,
active: 1,
type: "HTTP",
interval: 60,
retryInterval: this.retryInterval,
keyword: null,
ignoreTls: false,
upsideDown: false,
maxredirects: 10,
accepted_statuscodes: ["200-299"],
dns_resolve_type: "A",
dns_resolve_server: "1.1.1.1",
dns_last_result: null,
pushToken: null,
notificationIDList:{"1":true,"5":true},
tags: [{"id":21,"monitor_id":16,"tag_id":2,"value":"","name":"Internal","color":"#059669"}],
};
}
providerList = {};
static init() {
console.log("Prepare Notification Providers");
this.providerList = {};
const list = [
new Apprise(),
new AliyunSms(),
new DingDing(),
new Discord(),
new Teams(),
new Gotify(),
new Line(),
new LunaSea(),
new Feishu(),
new Mattermost(),
new Matrix(),
new Octopush(),
new PromoSMS(),
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 : 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, heartbeatJSON) {
if (this.providerList[notification.type]) {
try {
monitorJSON.health = ((heartbeatJSON.status == 1) !== monitorJSON.upsideDown) ? "✅ Healthy": "❌ Unhealthy"
let parseData = {
// I actually dont think that it is necessary to put the notification in the data sent to the template.
// notification: notification,
monitor: monitorJSON,
heartbeat: heartbeatJSON,
}
let template = this.getTemplateFromNotification(notification)
console.log(`Template: (${template})`)
let message = await engine.parseAndRender(template, parseData)
return this.providerList[notification.type].send(notification, message, monitorJSON, heartbeatJSON);
} catch (e) {
throw e
}
} else {
throw new Error("Notification type is not supported");
}
}
static getTemplateFromNotification(notification){
let template = notification.template
let detail = notification.detail
console.log(`Detail: (${detail}) Template: (${template})`)
switch (detail) {
case "Minimal Detail":
return MinimalDetailTemplate
case "Low Detail":
return LowDetailTemplate
case "Medium Detail":
return MediumDetailTemplate
case "Full Detail":
return FullDetailTemplate
case "Custom Template":
if (template) {
return template
}
//returns low in the case of a template being empty string or undefined.
}
return LowDetailTemplate
}
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);
bean.is_default = notification.isDefault || false;
await R.store(bean)
if (notification.applyExisting) {
await applyNotificationEveryMonitor(bean.id, userID);
}
return 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)
}
static checkApprise() {
let commandExistsSync = require("command-exists").sync;
let exists = commandExistsSync("apprise");
return exists;
}
}
async function applyNotificationEveryMonitor(notificationID, userID) {
let monitors = await R.getAll("SELECT id FROM monitor WHERE user_id = ?", [
userID
]);
for (let i = 0; i < monitors.length; i++) {
let checkNotification = await R.findOne("monitor_notification", " monitor_id = ? AND notification_id = ? ", [
monitors[i].id,
notificationID,
])
if (! checkNotification) {
let relation = R.dispense("monitor_notification");
relation.monitor_id = monitors[i].id;
relation.notification_id = notificationID;
await R.store(relation)
}
}
}
module.exports = {
Notification,
}