mirror of
https://github.com/koichixD/uptime-kuma.git
synced 2026-09-04 09:13:21 +00:00
Merge branch 'master' into websocket_test
This commit is contained in:
+3
-3
@@ -18,15 +18,15 @@ exports.login = async function (username, password) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let user = await R.findOne("user", " username = ? AND active = 1 ", [
|
||||
username,
|
||||
let user = await R.findOne("user", "TRIM(username) = ? AND active = 1 ", [
|
||||
username.trim(),
|
||||
]);
|
||||
|
||||
if (user && passwordHash.verify(password, user.password)) {
|
||||
// Upgrade the hash to bcrypt
|
||||
if (passwordHash.needRehash(user.password)) {
|
||||
await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [
|
||||
passwordHash.generate(password),
|
||||
await passwordHash.generate(password),
|
||||
user.id,
|
||||
]);
|
||||
}
|
||||
|
||||
+12
-10
@@ -1,4 +1,5 @@
|
||||
const fs = require("fs");
|
||||
const fsAsync = fs.promises;
|
||||
const { R } = require("redbean-node");
|
||||
const { setSetting, setting } = require("./util-server");
|
||||
const { log, sleep } = require("../src/util");
|
||||
@@ -11,6 +12,7 @@ const { UptimeCalculator } = require("./uptime-calculator");
|
||||
const dayjs = require("dayjs");
|
||||
const { SimpleMigrationServer } = require("./utils/simple-migration-server");
|
||||
const KumaColumnCompiler = require("./utils/knex/lib/dialects/mysql2/schema/mysql2-columncompiler");
|
||||
const SqlString = require("sqlstring");
|
||||
|
||||
/**
|
||||
* Database & App Data Folder
|
||||
@@ -18,7 +20,7 @@ const KumaColumnCompiler = require("./utils/knex/lib/dialects/mysql2/schema/mysq
|
||||
class Database {
|
||||
|
||||
/**
|
||||
* Boostrap database for SQLite
|
||||
* Bootstrap database for SQLite
|
||||
* @type {string}
|
||||
*/
|
||||
static templatePath = "./db/kuma.db";
|
||||
@@ -255,10 +257,6 @@ class Database {
|
||||
}
|
||||
};
|
||||
} else if (dbConfig.type === "mariadb") {
|
||||
if (!/^\w+$/.test(dbConfig.dbName)) {
|
||||
throw Error("Invalid database name. A database name can only consist of letters, numbers and underscores");
|
||||
}
|
||||
|
||||
const connection = await mysql.createConnection({
|
||||
host: dbConfig.hostname,
|
||||
port: dbConfig.port,
|
||||
@@ -266,7 +264,11 @@ class Database {
|
||||
password: dbConfig.password,
|
||||
});
|
||||
|
||||
await connection.execute("CREATE DATABASE IF NOT EXISTS " + dbConfig.dbName + " CHARACTER SET utf8mb4");
|
||||
// Set to true, so for example "uptime.kuma", becomes `uptime.kuma`, not `uptime`.`kuma`
|
||||
// Doc: https://github.com/mysqljs/sqlstring?tab=readme-ov-file#escaping-query-identifiers
|
||||
const escapedDBName = SqlString.escapeId(dbConfig.dbName, true);
|
||||
|
||||
await connection.execute("CREATE DATABASE IF NOT EXISTS " + escapedDBName + " CHARACTER SET utf8mb4");
|
||||
connection.end();
|
||||
|
||||
config = {
|
||||
@@ -707,12 +709,12 @@ class Database {
|
||||
|
||||
/**
|
||||
* Get the size of the database (SQLite only)
|
||||
* @returns {number} Size of database
|
||||
* @returns {Promise<number>} Size of database
|
||||
*/
|
||||
static getSize() {
|
||||
static async getSize() {
|
||||
if (Database.dbConfig.type === "sqlite") {
|
||||
log.debug("db", "Database.getSize()");
|
||||
let stats = fs.statSync(Database.sqlitePath);
|
||||
let stats = await fsAsync.stat(Database.sqlitePath);
|
||||
log.debug("db", stats);
|
||||
return stats.size;
|
||||
}
|
||||
@@ -736,7 +738,7 @@ class Database {
|
||||
if (Database.dbConfig.type === "sqlite") {
|
||||
return "DATETIME('now', ? || ' hours')";
|
||||
} else {
|
||||
return "DATE_ADD(NOW(), INTERVAL ? HOUR)";
|
||||
return "DATE_ADD(UTC_TIMESTAMP(), INTERVAL ? HOUR)";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
-9
@@ -1,10 +1,10 @@
|
||||
const axios = require("axios");
|
||||
const { R } = require("redbean-node");
|
||||
const https = require("https");
|
||||
const fs = require("fs");
|
||||
const fsAsync = require("fs").promises;
|
||||
const path = require("path");
|
||||
const Database = require("./database");
|
||||
const { axiosAbortSignal } = require("./util-server");
|
||||
const { axiosAbortSignal, fsExists } = require("./util-server");
|
||||
|
||||
class DockerHost {
|
||||
|
||||
@@ -81,7 +81,7 @@ class DockerHost {
|
||||
options.socketPath = dockerHost.dockerDaemon;
|
||||
} else if (dockerHost.dockerType === "tcp") {
|
||||
options.baseURL = DockerHost.patchDockerURL(dockerHost.dockerDaemon);
|
||||
options.httpsAgent = new https.Agent(DockerHost.getHttpsAgentOptions(dockerHost.dockerType, options.baseURL));
|
||||
options.httpsAgent = new https.Agent(await DockerHost.getHttpsAgentOptions(dockerHost.dockerType, options.baseURL));
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -141,9 +141,9 @@ class DockerHost {
|
||||
* File names can also be overridden via 'DOCKER_TLS_FILE_NAME_(CA|KEY|CERT)'.
|
||||
* @param {string} dockerType i.e. "tcp" or "socket"
|
||||
* @param {string} url The docker host URL rewritten to https://
|
||||
* @returns {object} HTTP agent options
|
||||
* @returns {Promise<object>} HTTP agent options
|
||||
*/
|
||||
static getHttpsAgentOptions(dockerType, url) {
|
||||
static async getHttpsAgentOptions(dockerType, url) {
|
||||
let baseOptions = {
|
||||
maxCachedSessions: 0,
|
||||
rejectUnauthorized: true
|
||||
@@ -156,10 +156,10 @@ class DockerHost {
|
||||
let certPath = path.join(Database.dockerTLSDir, dirName, DockerHost.CertificateFileNameCert);
|
||||
let keyPath = path.join(Database.dockerTLSDir, dirName, DockerHost.CertificateFileNameKey);
|
||||
|
||||
if (dockerType === "tcp" && fs.existsSync(caPath) && fs.existsSync(certPath) && fs.existsSync(keyPath)) {
|
||||
let ca = fs.readFileSync(caPath);
|
||||
let key = fs.readFileSync(keyPath);
|
||||
let cert = fs.readFileSync(certPath);
|
||||
if (dockerType === "tcp" && await fsExists(caPath) && await fsExists(certPath) && await fsExists(keyPath)) {
|
||||
let ca = await fsAsync.readFile(caPath);
|
||||
let key = await fsAsync.readFile(keyPath);
|
||||
let cert = await fsAsync.readFile(certPath);
|
||||
certOptions = {
|
||||
ca,
|
||||
key,
|
||||
|
||||
@@ -33,7 +33,7 @@ class Group extends BeanModel {
|
||||
*/
|
||||
async getMonitorList() {
|
||||
return R.convertToBeans("monitor", await R.getAll(`
|
||||
SELECT monitor.*, monitor_group.send_url FROM monitor, monitor_group
|
||||
SELECT monitor.*, monitor_group.send_url, monitor_group.custom_url FROM monitor, monitor_group
|
||||
WHERE monitor.id = monitor_group.monitor_id
|
||||
AND group_id = ?
|
||||
ORDER BY monitor_group.weight
|
||||
|
||||
@@ -158,12 +158,22 @@ class Maintenance extends BeanModel {
|
||||
bean.active = obj.active;
|
||||
|
||||
if (obj.dateRange[0]) {
|
||||
const parsedDate = new Date(obj.dateRange[0]);
|
||||
if (isNaN(parsedDate.getTime()) || parsedDate.getFullYear() > 9999) {
|
||||
throw new Error("Invalid start date");
|
||||
}
|
||||
|
||||
bean.start_date = obj.dateRange[0];
|
||||
} else {
|
||||
bean.start_date = null;
|
||||
}
|
||||
|
||||
if (obj.dateRange[1]) {
|
||||
const parsedDate = new Date(obj.dateRange[1]);
|
||||
if (isNaN(parsedDate.getTime()) || parsedDate.getFullYear() > 9999) {
|
||||
throw new Error("Invalid end date");
|
||||
}
|
||||
|
||||
bean.end_date = obj.dateRange[1];
|
||||
} else {
|
||||
bean.end_date = null;
|
||||
@@ -192,7 +202,7 @@ class Maintenance extends BeanModel {
|
||||
* @returns {void}
|
||||
*/
|
||||
static validateCron(cron) {
|
||||
let job = new Cron(cron, () => {});
|
||||
let job = new Cron(cron, () => { });
|
||||
job.stop();
|
||||
}
|
||||
|
||||
@@ -229,11 +239,13 @@ class Maintenance extends BeanModel {
|
||||
apicache.clear();
|
||||
});
|
||||
} else if (this.cron != null) {
|
||||
let current = dayjs();
|
||||
|
||||
// Here should be cron or recurring
|
||||
try {
|
||||
this.beanMeta.status = "scheduled";
|
||||
|
||||
let startEvent = (customDuration = 0) => {
|
||||
let startEvent = async (customDuration = 0) => {
|
||||
log.info("maintenance", "Maintenance id: " + this.id + " is under maintenance now");
|
||||
|
||||
this.beanMeta.status = "under-maintenance";
|
||||
@@ -248,6 +260,10 @@ class Maintenance extends BeanModel {
|
||||
this.beanMeta.status = "scheduled";
|
||||
UptimeKumaServer.getInstance().sendMaintenanceListByUserID(this.user_id);
|
||||
}, duration);
|
||||
|
||||
// Set last start date to current time
|
||||
this.last_start_date = current.toISOString();
|
||||
await R.store(this);
|
||||
};
|
||||
|
||||
// Create Cron
|
||||
@@ -256,11 +272,34 @@ class Maintenance extends BeanModel {
|
||||
const startDate = dayjs(this.startDate);
|
||||
const [ hour, minute ] = this.startTime.split(":");
|
||||
const startDateTime = startDate.hour(hour).minute(minute);
|
||||
|
||||
// Fix #6118, since the startDateTime is optional, it will throw error if the date is null when using toISOString()
|
||||
let startAt = undefined;
|
||||
try {
|
||||
startAt = startDateTime.toISOString();
|
||||
} catch (_) {}
|
||||
|
||||
this.beanMeta.job = new Cron(this.cron, {
|
||||
timezone: await this.getTimezone(),
|
||||
interval: this.interval_day * 24 * 60 * 60,
|
||||
startAt: startDateTime.toISOString(),
|
||||
}, startEvent);
|
||||
startAt,
|
||||
}, () => {
|
||||
if (!this.lastStartDate || this.interval_day === 1) {
|
||||
return startEvent();
|
||||
}
|
||||
|
||||
// If last start date is set, it means the maintenance has been started before
|
||||
let lastStartDate = dayjs(this.lastStartDate)
|
||||
.subtract(1.1, "hour"); // Subtract 1.1 hour to avoid issues with timezone differences
|
||||
|
||||
// Check if the interval is enough
|
||||
if (current.diff(lastStartDate, "day") < this.interval_day) {
|
||||
log.debug("maintenance", "Maintenance id: " + this.id + " is still in the window, skipping start event");
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug("maintenance", "Maintenance id: " + this.id + " is not in the window, starting event");
|
||||
return startEvent();
|
||||
});
|
||||
} else {
|
||||
this.beanMeta.job = new Cron(this.cron, {
|
||||
timezone: await this.getTimezone(),
|
||||
@@ -269,7 +308,6 @@ class Maintenance extends BeanModel {
|
||||
|
||||
// Continue if the maintenance is still in the window
|
||||
let runningTimeslot = this.getRunningTimeslot();
|
||||
let current = dayjs();
|
||||
|
||||
if (runningTimeslot) {
|
||||
let duration = dayjs(runningTimeslot.endDate).diff(current, "second") * 1000;
|
||||
@@ -413,8 +451,11 @@ class Maintenance extends BeanModel {
|
||||
} else if (!this.strategy.startsWith("recurring-")) {
|
||||
this.cron = "";
|
||||
} else if (this.strategy === "recurring-interval") {
|
||||
// For intervals, the pattern is calculated in the run function as the interval-option is set
|
||||
this.cron = "* * * * *";
|
||||
// For intervals, the pattern is used to check if the execution should be started
|
||||
let array = this.start_time.split(":");
|
||||
let hour = parseInt(array[0]);
|
||||
let minute = parseInt(array[1]);
|
||||
this.cron = `${minute} ${hour} * * *`;
|
||||
this.duration = this.calcDuration();
|
||||
log.debug("maintenance", "Cron: " + this.cron);
|
||||
log.debug("maintenance", "Duration: " + this.duration);
|
||||
|
||||
+131
-58
@@ -2,10 +2,14 @@ const dayjs = require("dayjs");
|
||||
const axios = require("axios");
|
||||
const { Prometheus } = require("../prometheus");
|
||||
const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND,
|
||||
SQL_DATETIME_FORMAT, evaluateJsonQuery
|
||||
SQL_DATETIME_FORMAT, evaluateJsonQuery,
|
||||
PING_PACKET_SIZE_MIN, PING_PACKET_SIZE_MAX, PING_PACKET_SIZE_DEFAULT,
|
||||
PING_GLOBAL_TIMEOUT_MIN, PING_GLOBAL_TIMEOUT_MAX, PING_GLOBAL_TIMEOUT_DEFAULT,
|
||||
PING_COUNT_MIN, PING_COUNT_MAX, PING_COUNT_DEFAULT,
|
||||
PING_PER_REQUEST_TIMEOUT_MIN, PING_PER_REQUEST_TIMEOUT_MAX, PING_PER_REQUEST_TIMEOUT_DEFAULT
|
||||
} = require("../../src/util");
|
||||
const { tcping, ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, setSetting, httpNtlm, radius, grpcQuery,
|
||||
redisPingAsync, kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal
|
||||
const { ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, setSetting, httpNtlm, radius, grpcQuery,
|
||||
kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal
|
||||
} = require("../util-server");
|
||||
const { R } = require("redbean-node");
|
||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||
@@ -53,7 +57,7 @@ class Monitor extends BeanModel {
|
||||
};
|
||||
|
||||
if (this.sendUrl) {
|
||||
obj.url = this.url;
|
||||
obj.url = this.customUrl ?? this.url;
|
||||
}
|
||||
|
||||
if (showTags) {
|
||||
@@ -155,8 +159,15 @@ class Monitor extends BeanModel {
|
||||
snmpOid: this.snmpOid,
|
||||
jsonPathOperator: this.jsonPathOperator,
|
||||
snmpVersion: this.snmpVersion,
|
||||
smtpSecurity: this.smtpSecurity,
|
||||
rabbitmqNodes: JSON.parse(this.rabbitmqNodes),
|
||||
conditions: JSON.parse(this.conditions),
|
||||
ipFamily: this.ipFamily,
|
||||
|
||||
// ping advanced options
|
||||
ping_numeric: this.isPingNumeric(),
|
||||
ping_count: this.ping_count,
|
||||
ping_per_request_timeout: this.ping_per_request_timeout,
|
||||
};
|
||||
|
||||
if (includeSensitiveData) {
|
||||
@@ -172,6 +183,7 @@ class Monitor extends BeanModel {
|
||||
oauth_client_secret: this.oauth_client_secret,
|
||||
oauth_token_url: this.oauth_token_url,
|
||||
oauth_scopes: this.oauth_scopes,
|
||||
oauth_audience: this.oauth_audience,
|
||||
oauth_auth_method: this.oauth_auth_method,
|
||||
pushToken: this.pushToken,
|
||||
databaseConnectionString: this.databaseConnectionString,
|
||||
@@ -180,6 +192,7 @@ class Monitor extends BeanModel {
|
||||
radiusSecret: this.radiusSecret,
|
||||
mqttUsername: this.mqttUsername,
|
||||
mqttPassword: this.mqttPassword,
|
||||
mqttWebsocketPath: this.mqttWebsocketPath,
|
||||
authWorkstation: this.authWorkstation,
|
||||
authDomain: this.authDomain,
|
||||
tlsCa: this.tlsCa,
|
||||
@@ -249,6 +262,14 @@ class Monitor extends BeanModel {
|
||||
return Boolean(this.expiryNotification);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if ping should use numeric output only
|
||||
* @returns {boolean} True if IP addresses will be output instead of symbolic hostnames
|
||||
*/
|
||||
isPingNumeric() {
|
||||
return Boolean(this.ping_numeric);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse to boolean
|
||||
* @returns {boolean} Should TLS errors be ignored?
|
||||
@@ -338,7 +359,7 @@ class Monitor extends BeanModel {
|
||||
let previousBeat = null;
|
||||
let retries = 0;
|
||||
|
||||
this.prometheus = new Prometheus(this);
|
||||
this.prometheus = new Prometheus(this, await this.getTags());
|
||||
|
||||
const beat = async () => {
|
||||
|
||||
@@ -390,39 +411,6 @@ class Monitor extends BeanModel {
|
||||
if (await Monitor.isUnderMaintenance(this.id)) {
|
||||
bean.msg = "Monitor under maintenance";
|
||||
bean.status = MAINTENANCE;
|
||||
} else if (this.type === "group") {
|
||||
const children = await Monitor.getChildren(this.id);
|
||||
|
||||
if (children.length > 0) {
|
||||
bean.status = UP;
|
||||
bean.msg = "All children up and running";
|
||||
for (const child of children) {
|
||||
if (!child.active) {
|
||||
// Ignore inactive childs
|
||||
continue;
|
||||
}
|
||||
const lastBeat = await Monitor.getPreviousHeartbeat(child.id);
|
||||
|
||||
// Only change state if the monitor is in worse conditions then the ones before
|
||||
// lastBeat.status could be null
|
||||
if (!lastBeat) {
|
||||
bean.status = PENDING;
|
||||
} else if (bean.status === UP && (lastBeat.status === PENDING || lastBeat.status === DOWN)) {
|
||||
bean.status = lastBeat.status;
|
||||
} else if (bean.status === PENDING && lastBeat.status === DOWN) {
|
||||
bean.status = lastBeat.status;
|
||||
}
|
||||
}
|
||||
|
||||
if (bean.status !== UP) {
|
||||
bean.msg = "Child inaccessible";
|
||||
}
|
||||
} else {
|
||||
// Set status pending if group is empty
|
||||
bean.status = PENDING;
|
||||
bean.msg = "Group empty";
|
||||
}
|
||||
|
||||
} else if (this.type === "http" || this.type === "keyword" || this.type === "json-query") {
|
||||
// Do not do any queries/high loading things before the "bean.ping"
|
||||
let startTime = dayjs().valueOf();
|
||||
@@ -451,10 +439,26 @@ class Monitor extends BeanModel {
|
||||
}
|
||||
}
|
||||
|
||||
let agentFamily = undefined;
|
||||
if (this.ipFamily === "ipv4") {
|
||||
agentFamily = 4;
|
||||
}
|
||||
if (this.ipFamily === "ipv6") {
|
||||
agentFamily = 6;
|
||||
}
|
||||
|
||||
const httpsAgentOptions = {
|
||||
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
|
||||
rejectUnauthorized: !this.getIgnoreTls(),
|
||||
secureOptions: crypto.constants.SSL_OP_LEGACY_SERVER_CONNECT,
|
||||
autoSelectFamily: true,
|
||||
...(agentFamily ? { family: agentFamily } : {})
|
||||
};
|
||||
|
||||
const httpAgentOptions = {
|
||||
maxCachedSessions: 0,
|
||||
autoSelectFamily: true,
|
||||
...(agentFamily ? { family: agentFamily } : {})
|
||||
};
|
||||
|
||||
log.debug("monitor", `[${this.name}] Prepare Options for axios`);
|
||||
@@ -516,6 +520,7 @@ class Monitor extends BeanModel {
|
||||
if (proxy && proxy.active) {
|
||||
const { httpAgent, httpsAgent } = Proxy.createAgents(proxy, {
|
||||
httpsAgentOptions: httpsAgentOptions,
|
||||
httpAgentOptions: httpAgentOptions,
|
||||
});
|
||||
|
||||
options.proxy = false;
|
||||
@@ -524,6 +529,10 @@ class Monitor extends BeanModel {
|
||||
}
|
||||
}
|
||||
|
||||
if (!options.httpAgent) {
|
||||
options.httpAgent = new http.Agent(httpAgentOptions);
|
||||
}
|
||||
|
||||
if (!options.httpsAgent) {
|
||||
let jar = new CookieJar();
|
||||
let httpsCookieAgentOptions = {
|
||||
@@ -579,7 +588,8 @@ class Monitor extends BeanModel {
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.UPTIME_KUMA_LOG_RESPONSE_BODY_MONITOR_ID === this.id) {
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if (process.env.UPTIME_KUMA_LOG_RESPONSE_BODY_MONITOR_ID == this.id) {
|
||||
log.info("monitor", res.data);
|
||||
}
|
||||
|
||||
@@ -621,13 +631,8 @@ class Monitor extends BeanModel {
|
||||
|
||||
}
|
||||
|
||||
} else if (this.type === "port") {
|
||||
bean.ping = await tcping(this.hostname, this.port);
|
||||
bean.msg = "";
|
||||
bean.status = UP;
|
||||
|
||||
} else if (this.type === "ping") {
|
||||
bean.ping = await ping(this.hostname, this.packetSize);
|
||||
bean.ping = await ping(this.hostname, this.ping_count, "", this.ping_numeric, this.packetSize, this.timeout, this.ping_per_request_timeout);
|
||||
bean.msg = "";
|
||||
bean.status = UP;
|
||||
} else if (this.type === "push") { // Type: Push
|
||||
@@ -699,7 +704,7 @@ class Monitor extends BeanModel {
|
||||
bean.msg = res.data.response.servers[0].name;
|
||||
|
||||
try {
|
||||
bean.ping = await ping(this.hostname, this.packetSize);
|
||||
bean.ping = await ping(this.hostname, PING_COUNT_DEFAULT, "", true, this.packetSize, PING_GLOBAL_TIMEOUT_DEFAULT, PING_PER_REQUEST_TIMEOUT_DEFAULT);
|
||||
} catch (_) { }
|
||||
} else {
|
||||
throw new Error("Server not found on Steam");
|
||||
@@ -749,7 +754,7 @@ class Monitor extends BeanModel {
|
||||
} else if (dockerHost._dockerType === "tcp") {
|
||||
options.baseURL = DockerHost.patchDockerURL(dockerHost._dockerDaemon);
|
||||
options.httpsAgent = new https.Agent(
|
||||
DockerHost.getHttpsAgentOptions(dockerHost._dockerType, options.baseURL)
|
||||
await DockerHost.getHttpsAgentOptions(dockerHost._dockerType, options.baseURL)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -851,13 +856,6 @@ class Monitor extends BeanModel {
|
||||
bean.msg = resp.code;
|
||||
bean.status = UP;
|
||||
bean.ping = dayjs().valueOf() - startTime;
|
||||
} else if (this.type === "redis") {
|
||||
let startTime = dayjs().valueOf();
|
||||
|
||||
bean.msg = await redisPingAsync(this.databaseConnectionString, !this.ignoreTls);
|
||||
bean.status = UP;
|
||||
bean.ping = dayjs().valueOf() - startTime;
|
||||
|
||||
} else if (this.type in UptimeKumaServer.monitorTypeList) {
|
||||
let startTime = dayjs().valueOf();
|
||||
const monitorType = UptimeKumaServer.monitorTypeList[this.type];
|
||||
@@ -1316,7 +1314,7 @@ class Monitor extends BeanModel {
|
||||
/**
|
||||
* Send a notification about a monitor
|
||||
* @param {boolean} isFirstBeat Is this beat the first of this monitor?
|
||||
* @param {Monitor} monitor The monitor to send a notificaton about
|
||||
* @param {Monitor} monitor The monitor to send a notification about
|
||||
* @param {Bean} bean Status information about monitor
|
||||
* @returns {void}
|
||||
*/
|
||||
@@ -1337,7 +1335,8 @@ class Monitor extends BeanModel {
|
||||
try {
|
||||
const heartbeatJSON = bean.toJSON();
|
||||
const monitorData = [{ id: monitor.id,
|
||||
active: monitor.active
|
||||
active: monitor.active,
|
||||
name: monitor.name
|
||||
}];
|
||||
const preloadData = await Monitor.preparePreloadData(monitorData);
|
||||
// Prevent if the msg is undefined, notifications such as Discord cannot send out.
|
||||
@@ -1510,6 +1509,31 @@ class Monitor extends BeanModel {
|
||||
if (this.interval < MIN_INTERVAL_SECOND) {
|
||||
throw new Error(`Interval cannot be less than ${MIN_INTERVAL_SECOND} seconds`);
|
||||
}
|
||||
|
||||
if (this.type === "ping") {
|
||||
// ping parameters validation
|
||||
if (this.packetSize && (this.packetSize < PING_PACKET_SIZE_MIN || this.packetSize > PING_PACKET_SIZE_MAX)) {
|
||||
throw new Error(`Packet size must be between ${PING_PACKET_SIZE_MIN} and ${PING_PACKET_SIZE_MAX} (default: ${PING_PACKET_SIZE_DEFAULT})`);
|
||||
}
|
||||
|
||||
if (this.ping_per_request_timeout && (this.ping_per_request_timeout < PING_PER_REQUEST_TIMEOUT_MIN || this.ping_per_request_timeout > PING_PER_REQUEST_TIMEOUT_MAX)) {
|
||||
throw new Error(`Per-ping timeout must be between ${PING_PER_REQUEST_TIMEOUT_MIN} and ${PING_PER_REQUEST_TIMEOUT_MAX} seconds (default: ${PING_PER_REQUEST_TIMEOUT_DEFAULT})`);
|
||||
}
|
||||
|
||||
if (this.ping_count && (this.ping_count < PING_COUNT_MIN || this.ping_count > PING_COUNT_MAX)) {
|
||||
throw new Error(`Echo requests count must be between ${PING_COUNT_MIN} and ${PING_COUNT_MAX} (default: ${PING_COUNT_DEFAULT})`);
|
||||
}
|
||||
|
||||
if (this.timeout) {
|
||||
const pingGlobalTimeout = Math.round(Number(this.timeout));
|
||||
|
||||
if (pingGlobalTimeout < this.ping_per_request_timeout || pingGlobalTimeout < PING_GLOBAL_TIMEOUT_MIN || pingGlobalTimeout > PING_GLOBAL_TIMEOUT_MAX) {
|
||||
throw new Error(`Timeout must be between ${PING_GLOBAL_TIMEOUT_MIN} and ${PING_GLOBAL_TIMEOUT_MAX} seconds (default: ${PING_GLOBAL_TIMEOUT_DEFAULT})`);
|
||||
}
|
||||
|
||||
this.timeout = pingGlobalTimeout;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1635,7 +1659,7 @@ class Monitor extends BeanModel {
|
||||
/**
|
||||
* Gets all Children of the monitor
|
||||
* @param {number} monitorID ID of monitor to get
|
||||
* @returns {Promise<LooseObject<any>>} Children
|
||||
* @returns {Promise<LooseObject<any>[]>} Children
|
||||
*/
|
||||
static async getChildren(monitorID) {
|
||||
return await R.getAll(`
|
||||
@@ -1701,6 +1725,55 @@ class Monitor extends BeanModel {
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a monitor from the system
|
||||
* @param {number} monitorID ID of the monitor to delete
|
||||
* @param {number} userID ID of the user who owns the monitor
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async deleteMonitor(monitorID, userID) {
|
||||
const server = UptimeKumaServer.getInstance();
|
||||
|
||||
// Stop the monitor if it's running
|
||||
if (monitorID in server.monitorList) {
|
||||
await server.monitorList[monitorID].stop();
|
||||
delete server.monitorList[monitorID];
|
||||
}
|
||||
|
||||
// Delete from database
|
||||
await R.exec("DELETE FROM monitor WHERE id = ? AND user_id = ? ", [
|
||||
monitorID,
|
||||
userID,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively delete a monitor and all its descendants
|
||||
* @param {number} monitorID ID of the monitor to delete
|
||||
* @param {number} userID ID of the user who owns the monitor
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async deleteMonitorRecursively(monitorID, userID) {
|
||||
// Check if this monitor is a group
|
||||
const monitor = await R.findOne("monitor", " id = ? AND user_id = ? ", [
|
||||
monitorID,
|
||||
userID,
|
||||
]);
|
||||
|
||||
if (monitor && monitor.type === "group") {
|
||||
// Get all children and delete them recursively
|
||||
const children = await Monitor.getChildren(monitorID);
|
||||
if (children && children.length > 0) {
|
||||
for (const child of children) {
|
||||
await Monitor.deleteMonitorRecursively(child.id, userID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the monitor itself
|
||||
await Monitor.deleteMonitor(monitorID, userID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks recursive if parent (ancestors) are active
|
||||
* @param {number} monitorID ID of the monitor to get
|
||||
@@ -1723,7 +1796,7 @@ class Monitor extends BeanModel {
|
||||
*/
|
||||
async makeOidcTokenClientCredentialsRequest() {
|
||||
log.debug("monitor", `[${this.name}] The oauth access-token undefined or expired. Requesting a new token`);
|
||||
const oAuthAccessToken = await getOidcTokenClientCredentials(this.oauth_token_url, this.oauth_client_id, this.oauth_client_secret, this.oauth_scopes, this.oauth_auth_method);
|
||||
const oAuthAccessToken = await getOidcTokenClientCredentials(this.oauth_token_url, this.oauth_client_id, this.oauth_client_secret, this.oauth_scopes, this.oauth_audience, this.oauth_auth_method);
|
||||
if (this.oauthAccessToken?.expires_at) {
|
||||
log.debug("monitor", `[${this.name}] Obtained oauth access-token. Expires at ${new Date(this.oauthAccessToken?.expires_at * 1000)}`);
|
||||
} else {
|
||||
|
||||
@@ -120,8 +120,8 @@ class StatusPage extends BeanModel {
|
||||
|
||||
const head = $("head");
|
||||
|
||||
if (statusPage.googleAnalyticsTagId) {
|
||||
let escapedGoogleAnalyticsScript = googleAnalytics.getGoogleAnalyticsScript(statusPage.googleAnalyticsTagId);
|
||||
if (statusPage.google_analytics_tag_id) {
|
||||
let escapedGoogleAnalyticsScript = googleAnalytics.getGoogleAnalyticsScript(statusPage.google_analytics_tag_id);
|
||||
head.append($(escapedGoogleAnalyticsScript));
|
||||
}
|
||||
|
||||
@@ -409,6 +409,7 @@ class StatusPage extends BeanModel {
|
||||
showPoweredBy: !!this.show_powered_by,
|
||||
googleAnalyticsId: this.google_analytics_tag_id,
|
||||
showCertificateExpiry: !!this.show_certificate_expiry,
|
||||
showOnlyLastHeartbeat: !!this.show_only_last_heartbeat
|
||||
};
|
||||
}
|
||||
|
||||
@@ -432,6 +433,7 @@ class StatusPage extends BeanModel {
|
||||
showPoweredBy: !!this.show_powered_by,
|
||||
googleAnalyticsId: this.google_analytics_tag_id,
|
||||
showCertificateExpiry: !!this.show_certificate_expiry,
|
||||
showOnlyLastHeartbeat: !!this.show_only_last_heartbeat
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ class User extends BeanModel {
|
||||
*/
|
||||
static async resetPassword(userID, newPassword) {
|
||||
await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [
|
||||
passwordHash.generate(newPassword),
|
||||
await passwordHash.generate(newPassword),
|
||||
userID
|
||||
]);
|
||||
}
|
||||
@@ -25,7 +25,7 @@ class User extends BeanModel {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async resetPassword(newPassword) {
|
||||
const hashedPassword = passwordHash.generate(newPassword);
|
||||
const hashedPassword = await passwordHash.generate(newPassword);
|
||||
|
||||
await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [
|
||||
hashedPassword,
|
||||
|
||||
@@ -82,7 +82,7 @@ function createNTLMv2Response(type2message, username, ntlmhash, nonce, targetNam
|
||||
//reserved
|
||||
buf.writeUInt32LE(0, 20);
|
||||
//timestamp
|
||||
//TODO: we are loosing precision here since js is not able to handle those large integers
|
||||
//TODO: we are losing precision here since js is not able to handle those large integers
|
||||
// maybe think about a different solution here
|
||||
// 11644473600000 = diff between 1970 and 1601
|
||||
var timestamp = ((Date.now() + 11644473600000) * 10000).toString(16);
|
||||
|
||||
@@ -89,6 +89,9 @@ function NtlmClient(credentials, AxiosConfig) {
|
||||
switch (_b.label) {
|
||||
case 0:
|
||||
error = err.response;
|
||||
// The header may look like this: `Negotiate, NTLM, Basic realm="itsahiddenrealm.example.net"`Add commentMore actions
|
||||
// so extract the 'NTLM' part first
|
||||
const ntlmheader = error.headers['www-authenticate'].split(',').find(_ => _.match(/ *NTLM/))?.trim() || '';
|
||||
if (!(error && error.status === 401
|
||||
&& error.headers['www-authenticate']
|
||||
&& error.headers['www-authenticate'].includes('NTLM'))) return [3 /*break*/, 3];
|
||||
@@ -96,12 +99,12 @@ function NtlmClient(credentials, AxiosConfig) {
|
||||
// include the Negotiate option when responding with the T2 message
|
||||
// There is nore we could do to ensure we are processing correctly,
|
||||
// but this is the easiest option for now
|
||||
if (error.headers['www-authenticate'].length < 50) {
|
||||
if (ntlmheader.length < 50) {
|
||||
t1Msg = ntlm.createType1Message(credentials.workstation, credentials.domain);
|
||||
error.config.headers["Authorization"] = t1Msg;
|
||||
}
|
||||
else {
|
||||
t2Msg = ntlm.decodeType2Message((error.headers['www-authenticate'].match(/^NTLM\s+(.+?)(,|\s+|$)/) || [])[1]);
|
||||
t2Msg = ntlm.decodeType2Message((ntlmheader.match(/^NTLM\s+(.+?)(,|\s+|$)/) || [])[1]);
|
||||
t3Msg = ntlm.createType3Message(t2Msg, credentials.username, credentials.password, credentials.workstation, credentials.domain);
|
||||
error.config.headers["X-retry"] = "false";
|
||||
error.config.headers["Authorization"] = t3Msg;
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
const { UP, PENDING, DOWN } = require("../../src/util");
|
||||
const { MonitorType } = require("./monitor-type");
|
||||
const Monitor = require("../model/monitor");
|
||||
|
||||
class GroupMonitorType extends MonitorType {
|
||||
name = "group";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async check(monitor, heartbeat, _server) {
|
||||
const children = await Monitor.getChildren(monitor.id);
|
||||
|
||||
if (children.length === 0) {
|
||||
// Set status pending if group is empty
|
||||
heartbeat.status = PENDING;
|
||||
heartbeat.msg = "Group empty";
|
||||
return;
|
||||
}
|
||||
|
||||
let worstStatus = UP;
|
||||
const downChildren = [];
|
||||
const pendingChildren = [];
|
||||
|
||||
for (const child of children) {
|
||||
if (!child.active) {
|
||||
// Ignore inactive (=paused) children
|
||||
continue;
|
||||
}
|
||||
|
||||
const label = child.name || `#${child.id}`;
|
||||
const lastBeat = await Monitor.getPreviousHeartbeat(child.id);
|
||||
|
||||
if (!lastBeat) {
|
||||
if (worstStatus === UP) {
|
||||
worstStatus = PENDING;
|
||||
}
|
||||
pendingChildren.push(label);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lastBeat.status === DOWN) {
|
||||
worstStatus = DOWN;
|
||||
downChildren.push(label);
|
||||
} else if (lastBeat.status === PENDING) {
|
||||
if (worstStatus !== DOWN) {
|
||||
worstStatus = PENDING;
|
||||
}
|
||||
pendingChildren.push(label);
|
||||
}
|
||||
}
|
||||
|
||||
if (worstStatus === UP) {
|
||||
heartbeat.status = UP;
|
||||
heartbeat.msg = "All children up and running";
|
||||
return;
|
||||
}
|
||||
|
||||
if (worstStatus === PENDING) {
|
||||
heartbeat.status = PENDING;
|
||||
heartbeat.msg = `Pending child monitors: ${pendingChildren.join(", ")}`;
|
||||
return;
|
||||
}
|
||||
|
||||
heartbeat.status = DOWN;
|
||||
|
||||
let message = `Child monitors down: ${downChildren.join(", ")}`;
|
||||
|
||||
if (pendingChildren.length > 0) {
|
||||
message += `; pending: ${pendingChildren.join(", ")}`;
|
||||
}
|
||||
|
||||
// Throw to leverage the generic retry handling and notification flow
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
GroupMonitorType,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
const { MonitorType } = require("./monitor-type");
|
||||
const { UP, DOWN, PENDING } = require("../../src/util");
|
||||
|
||||
class ManualMonitorType extends MonitorType {
|
||||
name = "Manual";
|
||||
type = "manual";
|
||||
description = "A monitor that allows manual control of the status";
|
||||
supportsConditions = false;
|
||||
conditionVariables = [];
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async check(monitor, heartbeat) {
|
||||
if (monitor.manual_status !== null) {
|
||||
heartbeat.status = monitor.manual_status;
|
||||
switch (monitor.manual_status) {
|
||||
case UP:
|
||||
heartbeat.msg = "Up";
|
||||
break;
|
||||
case DOWN:
|
||||
heartbeat.msg = "Down";
|
||||
break;
|
||||
default:
|
||||
heartbeat.msg = "Pending";
|
||||
}
|
||||
} else {
|
||||
heartbeat.status = PENDING;
|
||||
heartbeat.msg = "Manual monitoring - No status set";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ManualMonitorType
|
||||
};
|
||||
@@ -10,11 +10,12 @@ class MqttMonitorType extends MonitorType {
|
||||
* @inheritdoc
|
||||
*/
|
||||
async check(monitor, heartbeat, server) {
|
||||
const receivedMessage = await this.mqttAsync(monitor.hostname, monitor.mqttTopic, {
|
||||
const [ messageTopic, receivedMessage ] = await this.mqttAsync(monitor.hostname, monitor.mqttTopic, {
|
||||
port: monitor.port,
|
||||
username: monitor.mqttUsername,
|
||||
password: monitor.mqttPassword,
|
||||
interval: monitor.interval,
|
||||
websocketPath: monitor.mqttWebsocketPath,
|
||||
});
|
||||
|
||||
if (monitor.mqttCheckType == null || monitor.mqttCheckType === "") {
|
||||
@@ -24,7 +25,7 @@ class MqttMonitorType extends MonitorType {
|
||||
|
||||
if (monitor.mqttCheckType === "keyword") {
|
||||
if (receivedMessage != null && receivedMessage.includes(monitor.mqttSuccessMessage)) {
|
||||
heartbeat.msg = `Topic: ${monitor.mqttTopic}; Message: ${receivedMessage}`;
|
||||
heartbeat.msg = `Topic: ${messageTopic}; Message: ${receivedMessage}`;
|
||||
heartbeat.status = UP;
|
||||
} else {
|
||||
throw Error(`Message Mismatch - Topic: ${monitor.mqttTopic}; Message: ${receivedMessage}`);
|
||||
@@ -52,12 +53,12 @@ class MqttMonitorType extends MonitorType {
|
||||
* @param {string} hostname Hostname / address of machine to test
|
||||
* @param {string} topic MQTT topic
|
||||
* @param {object} options MQTT options. Contains port, username,
|
||||
* password and interval (interval defaults to 20)
|
||||
* password, websocketPath and interval (interval defaults to 20)
|
||||
* @returns {Promise<string>} Received MQTT message
|
||||
*/
|
||||
mqttAsync(hostname, topic, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { port, username, password, interval = 20 } = options;
|
||||
const { port, username, password, websocketPath, interval = 20 } = options;
|
||||
|
||||
// Adds MQTT protocol to the hostname if not already present
|
||||
if (!/^(?:http|mqtt|ws)s?:\/\//.test(hostname)) {
|
||||
@@ -70,7 +71,15 @@ class MqttMonitorType extends MonitorType {
|
||||
reject(new Error("Timeout, Message not received"));
|
||||
}, interval * 1000 * 0.8);
|
||||
|
||||
const mqttUrl = `${hostname}:${port}`;
|
||||
// Construct the URL based on protocol
|
||||
let mqttUrl = `${hostname}:${port}`;
|
||||
if (hostname.startsWith("ws://") || hostname.startsWith("wss://")) {
|
||||
if (websocketPath && !websocketPath.startsWith("/")) {
|
||||
mqttUrl = `${hostname}:${port}/${websocketPath || ""}`;
|
||||
} else {
|
||||
mqttUrl = `${hostname}:${port}${websocketPath || ""}`;
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("mqtt", `MQTT connecting to ${mqttUrl}`);
|
||||
|
||||
@@ -101,11 +110,9 @@ class MqttMonitorType extends MonitorType {
|
||||
});
|
||||
|
||||
client.on("message", (messageTopic, message) => {
|
||||
if (messageTopic === topic) {
|
||||
client.end();
|
||||
clearTimeout(timeoutID);
|
||||
resolve(message.toString("utf8"));
|
||||
}
|
||||
client.end();
|
||||
clearTimeout(timeoutID);
|
||||
resolve([ messageTopic, message.toString("utf8") ]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -2,13 +2,13 @@ const { MonitorType } = require("./monitor-type");
|
||||
const { chromium } = require("playwright-core");
|
||||
const { UP, log } = require("../../src/util");
|
||||
const { Settings } = require("../settings");
|
||||
const commandExistsSync = require("command-exists").sync;
|
||||
const childProcess = require("child_process");
|
||||
const path = require("path");
|
||||
const Database = require("../database");
|
||||
const jwt = require("jsonwebtoken");
|
||||
const config = require("../config");
|
||||
const { RemoteBrowser } = require("../remote-browser");
|
||||
const { commandExists } = require("../util-server");
|
||||
|
||||
/**
|
||||
* Cached instance of a browser
|
||||
@@ -122,7 +122,7 @@ async function prepareChromeExecutable(executablePath) {
|
||||
executablePath = "/usr/bin/chromium";
|
||||
|
||||
// Install chromium in container via apt install
|
||||
if ( !commandExistsSync(executablePath)) {
|
||||
if (! await commandExists(executablePath)) {
|
||||
await new Promise((resolve, reject) => {
|
||||
log.info("Chromium", "Installing Chromium...");
|
||||
let child = childProcess.exec("apt update && apt --yes --no-install-recommends install chromium fonts-indic fonts-noto fonts-noto-cjk");
|
||||
@@ -146,7 +146,7 @@ async function prepareChromeExecutable(executablePath) {
|
||||
}
|
||||
|
||||
} else {
|
||||
executablePath = findChrome(allowedList);
|
||||
executablePath = await findChrome(allowedList);
|
||||
}
|
||||
} else {
|
||||
// User specified a path
|
||||
@@ -160,20 +160,20 @@ async function prepareChromeExecutable(executablePath) {
|
||||
|
||||
/**
|
||||
* Find the chrome executable
|
||||
* @param {any[]} executables Executables to search through
|
||||
* @returns {any} Executable
|
||||
* @throws Could not find executable
|
||||
* @param {string[]} executables Executables to search through
|
||||
* @returns {Promise<string>} Executable
|
||||
* @throws {Error} Could not find executable
|
||||
*/
|
||||
function findChrome(executables) {
|
||||
async function findChrome(executables) {
|
||||
// Use the last working executable, so we don't have to search for it again
|
||||
if (lastAutoDetectChromeExecutable) {
|
||||
if (commandExistsSync(lastAutoDetectChromeExecutable)) {
|
||||
if (await commandExists(lastAutoDetectChromeExecutable)) {
|
||||
return lastAutoDetectChromeExecutable;
|
||||
}
|
||||
}
|
||||
|
||||
for (let executable of executables) {
|
||||
if (commandExistsSync(executable)) {
|
||||
if (await commandExists(executable)) {
|
||||
lastAutoDetectChromeExecutable = executable;
|
||||
return executable;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
const { MonitorType } = require("./monitor-type");
|
||||
const { UP } = require("../../src/util");
|
||||
const redis = require("redis");
|
||||
|
||||
class RedisMonitorType extends MonitorType {
|
||||
name = "redis";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async check(monitor, heartbeat, _server) {
|
||||
heartbeat.msg = await this.redisPingAsync(monitor.databaseConnectionString, !monitor.ignoreTls);
|
||||
heartbeat.status = UP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redis server ping
|
||||
* @param {string} dsn The redis connection string
|
||||
* @param {boolean} rejectUnauthorized If false, allows unverified server certificates.
|
||||
* @returns {Promise<any>} Response from redis server
|
||||
*/
|
||||
redisPingAsync(dsn, rejectUnauthorized) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const client = redis.createClient({
|
||||
url: dsn,
|
||||
socket: {
|
||||
rejectUnauthorized
|
||||
}
|
||||
});
|
||||
client.on("error", (err) => {
|
||||
if (client.isOpen) {
|
||||
client.disconnect();
|
||||
}
|
||||
reject(err);
|
||||
});
|
||||
client.connect().then(() => {
|
||||
if (!client.isOpen) {
|
||||
client.emit("error", new Error("connection isn't open"));
|
||||
}
|
||||
client.ping().then((res, err) => {
|
||||
if (client.isOpen) {
|
||||
client.disconnect();
|
||||
}
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(res);
|
||||
}
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
RedisMonitorType,
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
const { MonitorType } = require("./monitor-type");
|
||||
const { UP } = require("../../src/util");
|
||||
const nodemailer = require("nodemailer");
|
||||
|
||||
class SMTPMonitorType extends MonitorType {
|
||||
name = "smtp";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async check(monitor, heartbeat, _server) {
|
||||
let options = {
|
||||
port: monitor.port || 25,
|
||||
host: monitor.hostname,
|
||||
secure: monitor.smtpSecurity === "secure", // use SMTPS (not STARTTLS)
|
||||
ignoreTLS: monitor.smtpSecurity === "nostarttls", // don't use STARTTLS even if it's available
|
||||
requireTLS: monitor.smtpSecurity === "starttls", // use STARTTLS or fail
|
||||
};
|
||||
let transporter = nodemailer.createTransport(options);
|
||||
try {
|
||||
await transporter.verify();
|
||||
|
||||
heartbeat.status = UP;
|
||||
heartbeat.msg = "SMTP connection verifies successfully";
|
||||
} catch (e) {
|
||||
throw new Error(`SMTP connection doesn't verify: ${e}`);
|
||||
} finally {
|
||||
transporter.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
SMTPMonitorType,
|
||||
};
|
||||
@@ -31,7 +31,7 @@ class TailscalePing extends MonitorType {
|
||||
timeout: timeout,
|
||||
encoding: "utf8",
|
||||
});
|
||||
if (res.stderr && res.stderr.toString()) {
|
||||
if (res.stderr && res.stderr.toString() && res.code !== 0) {
|
||||
throw new Error(`Error in output: ${res.stderr.toString()}`);
|
||||
}
|
||||
if (res.stdout && res.stdout.toString()) {
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
const { MonitorType } = require("./monitor-type");
|
||||
const { UP, DOWN, PING_GLOBAL_TIMEOUT_DEFAULT: TIMEOUT, log } = require("../../src/util");
|
||||
const { checkCertificate } = require("../util-server");
|
||||
const tls = require("tls");
|
||||
const net = require("net");
|
||||
const tcpp = require("tcp-ping");
|
||||
|
||||
/**
|
||||
* Send TCP request to specified hostname and port
|
||||
* @param {string} hostname Hostname / address of machine
|
||||
* @param {number} port TCP port to test
|
||||
* @returns {Promise<number>} Maximum time in ms rounded to nearest integer
|
||||
*/
|
||||
const tcping = (hostname, port) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
tcpp.ping(
|
||||
{
|
||||
address: hostname,
|
||||
port: port,
|
||||
attempts: 1,
|
||||
},
|
||||
(err, data) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
|
||||
if (data.results.length >= 1 && data.results[0].err) {
|
||||
reject(data.results[0].err);
|
||||
}
|
||||
|
||||
resolve(Math.round(data.max));
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
class TCPMonitorType extends MonitorType {
|
||||
name = "port";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async check(monitor, heartbeat, _server) {
|
||||
try {
|
||||
const resp = await tcping(monitor.hostname, monitor.port);
|
||||
heartbeat.ping = resp;
|
||||
heartbeat.msg = `${resp} ms`;
|
||||
heartbeat.status = UP;
|
||||
} catch {
|
||||
heartbeat.status = DOWN;
|
||||
heartbeat.msg = "Connection failed";
|
||||
return;
|
||||
}
|
||||
|
||||
let socket_;
|
||||
|
||||
const preTLS = () =>
|
||||
new Promise((resolve, reject) => {
|
||||
let timeout;
|
||||
socket_ = net.connect(monitor.port, monitor.hostname);
|
||||
|
||||
const onTimeout = () => {
|
||||
log.debug(this.name, `[${monitor.name}] Pre-TLS connection timed out`);
|
||||
reject("Connection timed out");
|
||||
};
|
||||
|
||||
socket_.on("connect", () => {
|
||||
log.debug(this.name, `[${monitor.name}] Pre-TLS connection: ${JSON.stringify(socket_)}`);
|
||||
});
|
||||
|
||||
socket_.on("data", data => {
|
||||
const response = data.toString();
|
||||
const response_ = response.toLowerCase();
|
||||
log.debug(this.name, `[${monitor.name}] Pre-TLS response: ${response}`);
|
||||
switch (true) {
|
||||
case response_.includes("start tls") || response_.includes("begin tls"):
|
||||
timeout && clearTimeout(timeout);
|
||||
resolve({ socket: socket_ });
|
||||
break;
|
||||
case response.startsWith("* OK") || response.match(/CAPABILITY.+STARTTLS/):
|
||||
socket_.write("a001 STARTTLS\r\n");
|
||||
break;
|
||||
case response.startsWith("220") || response.includes("ESMTP"):
|
||||
socket_.write(`EHLO ${monitor.hostname}\r\n`);
|
||||
break;
|
||||
case response.includes("250-STARTTLS"):
|
||||
socket_.write("STARTTLS\r\n");
|
||||
break;
|
||||
default:
|
||||
reject(`Unexpected response: ${response}`);
|
||||
}
|
||||
});
|
||||
socket_.on("error", error => {
|
||||
log.debug(this.name, `[${monitor.name}] ${error.toString()}`);
|
||||
reject(error);
|
||||
});
|
||||
socket_.setTimeout(1000 * TIMEOUT, onTimeout);
|
||||
timeout = setTimeout(onTimeout, 1000 * TIMEOUT);
|
||||
});
|
||||
|
||||
const reuseSocket = monitor.smtpSecurity === "starttls" ? await preTLS() : {};
|
||||
|
||||
if ([ "secure", "starttls" ].includes(monitor.smtpSecurity) && monitor.isEnabledExpiryNotification()) {
|
||||
let socket = null;
|
||||
try {
|
||||
const options = {
|
||||
host: monitor.hostname,
|
||||
port: monitor.port,
|
||||
servername: monitor.hostname,
|
||||
...reuseSocket,
|
||||
};
|
||||
|
||||
const tlsInfoObject = await new Promise((resolve, reject) => {
|
||||
socket = tls.connect(options);
|
||||
|
||||
socket.on("secureConnect", () => {
|
||||
try {
|
||||
const info = checkCertificate(socket);
|
||||
resolve(info);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("error", error => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
socket.setTimeout(1000 * TIMEOUT, () => {
|
||||
reject(new Error("Connection timed out"));
|
||||
});
|
||||
});
|
||||
|
||||
await monitor.handleTlsInfo(tlsInfoObject);
|
||||
if (!tlsInfoObject.valid) {
|
||||
heartbeat.status = DOWN;
|
||||
heartbeat.msg = "Certificate is invalid";
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Unknown error";
|
||||
heartbeat.status = DOWN;
|
||||
heartbeat.msg = `TLS Connection failed: ${message}`;
|
||||
} finally {
|
||||
if (socket && !socket.destroyed) {
|
||||
socket.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (socket_ && !socket_.destroyed) {
|
||||
socket_.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
TCPMonitorType,
|
||||
};
|
||||
@@ -17,12 +17,14 @@ class Elks extends NotificationProvider {
|
||||
data.append("to", notification.elksToNumber );
|
||||
data.append("message", msg);
|
||||
|
||||
const config = {
|
||||
let config = {
|
||||
headers: {
|
||||
"Authorization": "Basic " + Buffer.from(`${notification.elksUsername}:${notification.elksAuthToken}`).toString("base64")
|
||||
}
|
||||
};
|
||||
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
|
||||
await axios.post(url, data, config);
|
||||
|
||||
return okMsg;
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const { UP } = require("../../src/util");
|
||||
const webpush = require("web-push");
|
||||
const { setting } = require("../util-server");
|
||||
|
||||
class Webpush extends NotificationProvider {
|
||||
name = "Webpush";
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
const okMsg = "Sent Successfully.";
|
||||
|
||||
try {
|
||||
const publicVapidKey = await setting("webpushPublicVapidKey");
|
||||
const privateVapidKey = await setting("webpushPrivateVapidKey");
|
||||
|
||||
webpush.setVapidDetails("https://github.com/louislam/uptime-kuma", publicVapidKey, privateVapidKey);
|
||||
|
||||
if (heartbeatJSON === null && monitorJSON === null) {
|
||||
// Test message
|
||||
const data = JSON.stringify({
|
||||
title: "TEST",
|
||||
body: `Test Alert - ${msg}`
|
||||
});
|
||||
|
||||
await webpush.sendNotification(notification.subscription, data);
|
||||
|
||||
return okMsg;
|
||||
}
|
||||
|
||||
const data = JSON.stringify({
|
||||
title: heartbeatJSON["status"] === UP ? "Monitor Up" : "Monitor DOWN",
|
||||
body: heartbeatJSON["status"] === UP ? `❌ ${heartbeatJSON["name"]} is DOWN` : `✅ ${heartbeatJSON["name"]} is UP`
|
||||
});
|
||||
|
||||
await webpush.sendNotification(notification.subscription, data);
|
||||
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Webpush;
|
||||
@@ -30,6 +30,8 @@ class Alerta extends NotificationProvider {
|
||||
type: "exceptionAlert",
|
||||
};
|
||||
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
|
||||
if (heartbeatJSON == null) {
|
||||
let postData = Object.assign({
|
||||
event: "msg",
|
||||
|
||||
@@ -41,7 +41,9 @@ class AlertNow extends NotificationProvider {
|
||||
"event_id": eventId,
|
||||
};
|
||||
|
||||
await axios.post(notification.alertNowWebhookURL, data);
|
||||
let config = this.getAxiosConfigWithProxy({});
|
||||
|
||||
await axios.post(notification.alertNowWebhookURL, data, config);
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
|
||||
@@ -72,6 +72,8 @@ class AliyunSMS extends NotificationProvider {
|
||||
data: qs.stringify(params),
|
||||
};
|
||||
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
|
||||
let result = await axios(config);
|
||||
if (result.data.Message === "OK") {
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
|
||||
class Bale extends NotificationProvider {
|
||||
name = "bale";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
const okMsg = "Sent Successfully.";
|
||||
const url = "https://tapi.bale.ai";
|
||||
|
||||
try {
|
||||
await axios.post(
|
||||
`${url}/bot${notification.baleBotToken}/sendMessage`,
|
||||
{
|
||||
chat_id: notification.baleChatID,
|
||||
text: msg
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Bale;
|
||||
@@ -96,20 +96,21 @@ class Bark extends NotificationProvider {
|
||||
*/
|
||||
async postNotification(notification, title, subtitle, endpoint) {
|
||||
let result;
|
||||
let config = this.getAxiosConfigWithProxy({});
|
||||
if (notification.apiVersion === "v1" || notification.apiVersion == null) {
|
||||
// url encode title and subtitle
|
||||
title = encodeURIComponent(title);
|
||||
subtitle = encodeURIComponent(subtitle);
|
||||
const params = this.additionalParameters(notification);
|
||||
result = await axios.get(`${endpoint}/${title}/${subtitle}${params}`);
|
||||
result = await axios.get(`${endpoint}/${title}/${subtitle}${params}`, config);
|
||||
} else {
|
||||
result = await axios.post(`${endpoint}/push`, {
|
||||
result = await axios.post(endpoint, {
|
||||
title,
|
||||
body: subtitle,
|
||||
icon: barkNotificationAvatar,
|
||||
sound: notification.barkSound || "telegraph", // default sound is telegraph
|
||||
group: notification.barkGroup || "UptimeKuma", // default group is UptimeKuma
|
||||
});
|
||||
}, config);
|
||||
}
|
||||
this.checkResult(result);
|
||||
if (result.statusText != null) {
|
||||
|
||||
@@ -19,7 +19,8 @@ class Bitrix24 extends NotificationProvider {
|
||||
"ATTACH[BLOCKS][0][MESSAGE]": msg
|
||||
};
|
||||
|
||||
await axios.get(`${notification.bitrix24WebhookURL}/im.notify.system.add.json`, { params });
|
||||
let config = this.getAxiosConfigWithProxy({ params });
|
||||
await axios.get(`${notification.bitrix24WebhookURL}/im.notify.system.add.json`, config);
|
||||
return okMsg;
|
||||
|
||||
} catch (error) {
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
|
||||
class Brevo extends NotificationProvider {
|
||||
name = "Brevo";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
const okMsg = "Sent Successfully.";
|
||||
|
||||
try {
|
||||
let config = {
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"api-key": notification.brevoApiKey,
|
||||
},
|
||||
};
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
|
||||
let to = [{ email: notification.brevoToEmail }];
|
||||
|
||||
let data = {
|
||||
sender: {
|
||||
email: notification.brevoFromEmail.trim(),
|
||||
name: notification.brevoFromName || "Uptime Kuma"
|
||||
},
|
||||
to: to,
|
||||
subject: notification.brevoSubject || "Notification from Your Uptime Kuma",
|
||||
htmlContent: `<html><head></head><body><p>${msg.replace(/\n/g, "<br>")}</p></body></html>`
|
||||
};
|
||||
|
||||
if (notification.brevoCcEmail) {
|
||||
data.cc = notification.brevoCcEmail
|
||||
.split(",")
|
||||
.map((email) => ({ email: email.trim() }));
|
||||
}
|
||||
|
||||
if (notification.brevoBccEmail) {
|
||||
data.bcc = notification.brevoBccEmail
|
||||
.split(",")
|
||||
.map((email) => ({ email: email.trim() }));
|
||||
}
|
||||
|
||||
let result = await axios.post(
|
||||
"https://api.brevo.com/v3/smtp/email",
|
||||
data,
|
||||
config
|
||||
);
|
||||
if (result.status === 201) {
|
||||
return okMsg;
|
||||
} else {
|
||||
throw new Error(`Unexpected status code: ${result.status}`);
|
||||
}
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Brevo;
|
||||
@@ -12,7 +12,8 @@ class CallMeBot extends NotificationProvider {
|
||||
try {
|
||||
const url = new URL(notification.callMeBotEndpoint);
|
||||
url.searchParams.set("text", msg);
|
||||
await axios.get(url.toString());
|
||||
let config = this.getAxiosConfigWithProxy({});
|
||||
await axios.get(url.toString(), config);
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
|
||||
@@ -22,7 +22,8 @@ class Cellsynt extends NotificationProvider {
|
||||
}
|
||||
};
|
||||
try {
|
||||
const resp = await axios.post("https://se-1.cellsynt.net/sms.php", null, data);
|
||||
let config = this.getAxiosConfigWithProxy(data);
|
||||
const resp = await axios.post("https://se-1.cellsynt.net/sms.php", null, config);
|
||||
if (resp.data == null ) {
|
||||
throw new Error("Could not connect to Cellsynt, please try again.");
|
||||
} else if (resp.data.includes("Error:")) {
|
||||
|
||||
@@ -29,6 +29,7 @@ class ClickSendSMS extends NotificationProvider {
|
||||
}
|
||||
]
|
||||
};
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
let resp = await axios.post(url, data, config);
|
||||
if (resp.data.data.messages[0].status !== "SUCCESS") {
|
||||
let error = "Something gone wrong. Api returned " + resp.data.data.messages[0].status + ".";
|
||||
|
||||
@@ -11,17 +11,23 @@ class DingDing extends NotificationProvider {
|
||||
*/
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
const okMsg = "Sent Successfully.";
|
||||
|
||||
const mentionAll = notification.mentioning === "everyone";
|
||||
const mobileList = notification.mentioning === "specify-mobiles" ? notification.mobileList : [];
|
||||
const userList = notification.mentioning === "specify-users" ? notification.userList : [];
|
||||
const finalList = [ ...mobileList || [], ...userList || [] ];
|
||||
const mentionStr = finalList.length > 0 ? "\n" : "" + finalList.map(item => `@${item}`).join(" ");
|
||||
try {
|
||||
if (heartbeatJSON != null) {
|
||||
let params = {
|
||||
msgtype: "markdown",
|
||||
markdown: {
|
||||
title: `[${this.statusToString(heartbeatJSON["status"])}] ${monitorJSON["name"]}`,
|
||||
text: `## [${this.statusToString(heartbeatJSON["status"])}] ${monitorJSON["name"]} \n> ${heartbeatJSON["msg"]}\n> Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`,
|
||||
text: `## [${this.statusToString(heartbeatJSON["status"])}] ${monitorJSON["name"]} \n> ${heartbeatJSON["msg"]}\n> Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}${mentionStr}`,
|
||||
},
|
||||
"at": {
|
||||
"isAtAll": notification.mentioning === "everyone"
|
||||
at: {
|
||||
isAtAll: mentionAll,
|
||||
atUserIds: userList,
|
||||
atMobiles: mobileList
|
||||
}
|
||||
};
|
||||
if (await this.sendToDingDing(notification, params)) {
|
||||
@@ -31,7 +37,12 @@ class DingDing extends NotificationProvider {
|
||||
let params = {
|
||||
msgtype: "text",
|
||||
text: {
|
||||
content: msg
|
||||
content: `${msg}${mentionStr}`
|
||||
},
|
||||
at: {
|
||||
isAtAll: mentionAll,
|
||||
atUserIds: userList,
|
||||
atMobiles: mobileList
|
||||
}
|
||||
};
|
||||
if (await this.sendToDingDing(notification, params)) {
|
||||
@@ -60,6 +71,7 @@ class DingDing extends NotificationProvider {
|
||||
url: `${notification.webHookUrl}×tamp=${timestamp}&sign=${encodeURIComponent(this.sign(timestamp, notification.secretKey))}`,
|
||||
data: JSON.stringify(params),
|
||||
};
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
|
||||
let result = await axios(config);
|
||||
if (result.data.errmsg === "ok") {
|
||||
|
||||
@@ -12,24 +12,36 @@ class Discord extends NotificationProvider {
|
||||
const okMsg = "Sent Successfully.";
|
||||
|
||||
try {
|
||||
let config = this.getAxiosConfigWithProxy({});
|
||||
const discordDisplayName = notification.discordUsername || "Uptime Kuma";
|
||||
const webhookUrl = new URL(notification.discordWebhookUrl);
|
||||
if (notification.discordChannelType === "postToThread") {
|
||||
webhookUrl.searchParams.append("thread_id", notification.threadId);
|
||||
}
|
||||
|
||||
// Check if the webhook has an avatar
|
||||
let webhookHasAvatar = true;
|
||||
try {
|
||||
const webhookInfo = await axios.get(webhookUrl.toString(), config);
|
||||
webhookHasAvatar = !!webhookInfo.data.avatar;
|
||||
} catch (e) {
|
||||
// If we can't verify, we assume he has an avatar to avoid forcing the default avatar
|
||||
webhookHasAvatar = true;
|
||||
}
|
||||
|
||||
// If heartbeatJSON is null, assume we're testing.
|
||||
if (heartbeatJSON == null) {
|
||||
let discordtestdata = {
|
||||
username: discordDisplayName,
|
||||
content: msg,
|
||||
};
|
||||
|
||||
if (!webhookHasAvatar) {
|
||||
discordtestdata.avatar_url = "https://github.com/louislam/uptime-kuma/raw/master/public/icon.png";
|
||||
}
|
||||
if (notification.discordChannelType === "createNewForumPost") {
|
||||
discordtestdata.thread_name = notification.postName;
|
||||
}
|
||||
|
||||
await axios.post(webhookUrl.toString(), discordtestdata);
|
||||
await axios.post(webhookUrl.toString(), discordtestdata, config);
|
||||
return okMsg;
|
||||
}
|
||||
|
||||
@@ -46,10 +58,10 @@ class Discord extends NotificationProvider {
|
||||
name: "Service Name",
|
||||
value: monitorJSON["name"],
|
||||
},
|
||||
{
|
||||
...(!notification.disableUrl ? [{
|
||||
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
|
||||
value: this.extractAddress(monitorJSON),
|
||||
},
|
||||
}] : []),
|
||||
{
|
||||
name: `Time (${heartbeatJSON["timezone"]})`,
|
||||
value: heartbeatJSON["localDateTime"],
|
||||
@@ -61,6 +73,9 @@ class Discord extends NotificationProvider {
|
||||
],
|
||||
}],
|
||||
};
|
||||
if (!webhookHasAvatar) {
|
||||
discorddowndata.avatar_url = "https://github.com/louislam/uptime-kuma/raw/master/public/icon.png";
|
||||
}
|
||||
if (notification.discordChannelType === "createNewForumPost") {
|
||||
discorddowndata.thread_name = notification.postName;
|
||||
}
|
||||
@@ -68,7 +83,7 @@ class Discord extends NotificationProvider {
|
||||
discorddowndata.content = notification.discordPrefixMessage;
|
||||
}
|
||||
|
||||
await axios.post(webhookUrl.toString(), discorddowndata);
|
||||
await axios.post(webhookUrl.toString(), discorddowndata, config);
|
||||
return okMsg;
|
||||
|
||||
} else if (heartbeatJSON["status"] === UP) {
|
||||
@@ -83,10 +98,10 @@ class Discord extends NotificationProvider {
|
||||
name: "Service Name",
|
||||
value: monitorJSON["name"],
|
||||
},
|
||||
{
|
||||
...(!notification.disableUrl ? [{
|
||||
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
|
||||
value: this.extractAddress(monitorJSON),
|
||||
},
|
||||
}] : []),
|
||||
{
|
||||
name: `Time (${heartbeatJSON["timezone"]})`,
|
||||
value: heartbeatJSON["localDateTime"],
|
||||
@@ -98,6 +113,9 @@ class Discord extends NotificationProvider {
|
||||
],
|
||||
}],
|
||||
};
|
||||
if (!webhookHasAvatar) {
|
||||
discordupdata.avatar_url = "https://github.com/louislam/uptime-kuma/raw/master/public/icon.png";
|
||||
}
|
||||
|
||||
if (notification.discordChannelType === "createNewForumPost") {
|
||||
discordupdata.thread_name = notification.postName;
|
||||
@@ -107,7 +125,7 @@ class Discord extends NotificationProvider {
|
||||
discordupdata.content = notification.discordPrefixMessage;
|
||||
}
|
||||
|
||||
await axios.post(webhookUrl.toString(), discordupdata);
|
||||
await axios.post(webhookUrl.toString(), discordupdata, config);
|
||||
return okMsg;
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
|
||||
class Evolution extends NotificationProvider {
|
||||
name = "evolution";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
const okMsg = "Sent Successfully.";
|
||||
|
||||
try {
|
||||
let config = {
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"apikey": notification.evolutionAuthToken,
|
||||
}
|
||||
};
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
|
||||
let data = {
|
||||
"number": notification.evolutionRecipient,
|
||||
"text": msg,
|
||||
};
|
||||
|
||||
let url = (notification.evolutionApiUrl || "https://evolapicloud.com/").replace(/([^/])\/+$/, "$1") + "/message/sendText/" + encodeURIComponent(notification.evolutionInstanceName);
|
||||
|
||||
await axios.post(url, data, config);
|
||||
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Evolution;
|
||||
@@ -12,6 +12,7 @@ class Feishu extends NotificationProvider {
|
||||
const okMsg = "Sent Successfully.";
|
||||
|
||||
try {
|
||||
let config = this.getAxiosConfigWithProxy({});
|
||||
if (heartbeatJSON == null) {
|
||||
let testdata = {
|
||||
msg_type: "text",
|
||||
@@ -19,7 +20,7 @@ class Feishu extends NotificationProvider {
|
||||
text: msg,
|
||||
},
|
||||
};
|
||||
await axios.post(notification.feishuWebHookUrl, testdata);
|
||||
await axios.post(notification.feishuWebHookUrl, testdata, config);
|
||||
return okMsg;
|
||||
}
|
||||
|
||||
@@ -49,7 +50,7 @@ class Feishu extends NotificationProvider {
|
||||
]
|
||||
}
|
||||
};
|
||||
await axios.post(notification.feishuWebHookUrl, downdata);
|
||||
await axios.post(notification.feishuWebHookUrl, downdata, config);
|
||||
return okMsg;
|
||||
}
|
||||
|
||||
@@ -79,7 +80,7 @@ class Feishu extends NotificationProvider {
|
||||
]
|
||||
}
|
||||
};
|
||||
await axios.post(notification.feishuWebHookUrl, updata);
|
||||
await axios.post(notification.feishuWebHookUrl, updata, config);
|
||||
return okMsg;
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -38,7 +38,7 @@ class FlashDuty extends NotificationProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a monitor url from the monitors infomation
|
||||
* Generate a monitor url from the monitors information
|
||||
* @param {object} monitorInfo Monitor details
|
||||
* @returns {string|undefined} Monitor URL
|
||||
*/
|
||||
@@ -73,13 +73,13 @@ class FlashDuty extends NotificationProvider {
|
||||
}
|
||||
const options = {
|
||||
method: "POST",
|
||||
url: "https://api.flashcat.cloud/event/push/alert/standard?integration_key=" + notification.flashdutyIntegrationKey,
|
||||
url: notification.flashdutyIntegrationKey.startsWith("http") ? notification.flashdutyIntegrationKey : "https://api.flashcat.cloud/event/push/alert/standard?integration_key=" + notification.flashdutyIntegrationKey,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data: {
|
||||
description: `[${title}] [${monitorInfo.name}] ${body}`,
|
||||
title,
|
||||
event_status: eventStatus || "Info",
|
||||
alert_key: String(monitorInfo.id) || Math.random().toString(36).substring(7),
|
||||
alert_key: monitorInfo.id ? String(monitorInfo.id) : Math.random().toString(36).substring(7),
|
||||
labels,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -11,10 +11,11 @@ class FreeMobile extends NotificationProvider {
|
||||
const okMsg = "Sent Successfully.";
|
||||
|
||||
try {
|
||||
let config = this.getAxiosConfigWithProxy({});
|
||||
await axios.post(`https://smsapi.free-mobile.fr/sendmsg?msg=${encodeURIComponent(msg.replace("🔴", "⛔️"))}`, {
|
||||
"user": notification.freemobileUser,
|
||||
"pass": notification.freemobilePass,
|
||||
});
|
||||
}, config);
|
||||
|
||||
return okMsg;
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ class GoAlert extends NotificationProvider {
|
||||
let config = {
|
||||
headers: headers
|
||||
};
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
await axios.post(`${notification.goAlertBaseURL}/api/v2/generic/incoming?token=${notification.goAlertToken}`, data, config);
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
|
||||
@@ -12,8 +12,44 @@ class GoogleChat extends NotificationProvider {
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
const okMsg = "Sent Successfully.";
|
||||
|
||||
// If Google Chat Webhook rate limit is reached, retry to configured max retries defaults to 3, delay between 60-180 seconds
|
||||
const post = async (url, data, config) => {
|
||||
let retries = notification.googleChatMaxRetries || 1; // Default to 1 retries
|
||||
retries = (retries > 10) ? 10 : retries; // Enforce maximum retries in backend
|
||||
while (retries > 0) {
|
||||
try {
|
||||
await axios.post(url, data, config);
|
||||
return;
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 429) {
|
||||
retries--;
|
||||
if (retries === 0) {
|
||||
throw error;
|
||||
}
|
||||
const delay = 60000 + Math.random() * 120000;
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
let config = this.getAxiosConfigWithProxy({});
|
||||
// Google Chat message formatting: https://developers.google.com/chat/api/guides/message-formats/basic
|
||||
if (notification.googleChatUseTemplate && notification.googleChatTemplate) {
|
||||
// Send message using template
|
||||
const renderedText = await this.renderTemplate(
|
||||
notification.googleChatTemplate,
|
||||
msg,
|
||||
monitorJSON,
|
||||
heartbeatJSON
|
||||
);
|
||||
const data = { "text": renderedText };
|
||||
await post(notification.googleChatWebhookURL, data, config);
|
||||
return okMsg;
|
||||
}
|
||||
|
||||
let chatHeader = {
|
||||
title: "Uptime Kuma Alert",
|
||||
@@ -83,12 +119,11 @@ class GoogleChat extends NotificationProvider {
|
||||
],
|
||||
};
|
||||
|
||||
await axios.post(notification.googleChatWebhookURL, data);
|
||||
await post(notification.googleChatWebhookURL, data, config);
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,8 +31,7 @@ class Gorush extends NotificationProvider {
|
||||
}
|
||||
]
|
||||
};
|
||||
let config = {};
|
||||
|
||||
let config = this.getAxiosConfigWithProxy({});
|
||||
await axios.post(`${notification.gorushServerURL}/api/push`, data, config);
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
|
||||
@@ -11,6 +11,7 @@ class Gotify extends NotificationProvider {
|
||||
const okMsg = "Sent Successfully.";
|
||||
|
||||
try {
|
||||
let config = this.getAxiosConfigWithProxy({});
|
||||
if (notification.gotifyserverurl && notification.gotifyserverurl.endsWith("/")) {
|
||||
notification.gotifyserverurl = notification.gotifyserverurl.slice(0, -1);
|
||||
}
|
||||
@@ -18,7 +19,7 @@ class Gotify extends NotificationProvider {
|
||||
"message": msg,
|
||||
"priority": notification.gotifyPriority || 8,
|
||||
"title": "Uptime-Kuma",
|
||||
});
|
||||
}, config);
|
||||
|
||||
return okMsg;
|
||||
|
||||
|
||||
@@ -16,13 +16,14 @@ class GrafanaOncall extends NotificationProvider {
|
||||
}
|
||||
|
||||
try {
|
||||
let config = this.getAxiosConfigWithProxy({});
|
||||
if (heartbeatJSON === null) {
|
||||
let grafanaupdata = {
|
||||
title: "General notification",
|
||||
message: msg,
|
||||
state: "alerting",
|
||||
};
|
||||
await axios.post(notification.GrafanaOncallURL, grafanaupdata);
|
||||
await axios.post(notification.GrafanaOncallURL, grafanaupdata, config);
|
||||
return okMsg;
|
||||
} else if (heartbeatJSON["status"] === DOWN) {
|
||||
let grafanadowndata = {
|
||||
@@ -30,7 +31,7 @@ class GrafanaOncall extends NotificationProvider {
|
||||
message: heartbeatJSON["msg"],
|
||||
state: "alerting",
|
||||
};
|
||||
await axios.post(notification.GrafanaOncallURL, grafanadowndata);
|
||||
await axios.post(notification.GrafanaOncallURL, grafanadowndata, config);
|
||||
return okMsg;
|
||||
} else if (heartbeatJSON["status"] === UP) {
|
||||
let grafanaupdata = {
|
||||
@@ -38,7 +39,7 @@ class GrafanaOncall extends NotificationProvider {
|
||||
message: heartbeatJSON["msg"],
|
||||
state: "ok",
|
||||
};
|
||||
await axios.post(notification.GrafanaOncallURL, grafanaupdata);
|
||||
await axios.post(notification.GrafanaOncallURL, grafanaupdata, config);
|
||||
return okMsg;
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -14,6 +14,7 @@ class GtxMessaging extends NotificationProvider {
|
||||
const text = msg.replaceAll("🔴 ", "").replaceAll("✅ ", "");
|
||||
|
||||
try {
|
||||
let config = this.getAxiosConfigWithProxy({});
|
||||
const data = new URLSearchParams();
|
||||
data.append("from", notification.gtxMessagingFrom.trim());
|
||||
data.append("to", notification.gtxMessagingTo.trim());
|
||||
@@ -21,7 +22,7 @@ class GtxMessaging extends NotificationProvider {
|
||||
|
||||
const url = `https://rest.gtx-messaging.net/smsc/sendsms/${notification.gtxMessagingApiKey}/json`;
|
||||
|
||||
await axios.post(url, data);
|
||||
await axios.post(url, data, config);
|
||||
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
|
||||
@@ -18,7 +18,7 @@ class HeiiOnCall extends NotificationProvider {
|
||||
payload["url"] = baseURL + getMonitorRelativeURL(monitorJSON.id);
|
||||
}
|
||||
|
||||
const config = {
|
||||
let config = {
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
@@ -28,6 +28,7 @@ class HeiiOnCall extends NotificationProvider {
|
||||
const heiiUrl = `https://heiioncall.com/triggers/${notification.heiiOnCallTriggerId}/`;
|
||||
// docs https://heiioncall.com/docs#manual-triggers
|
||||
try {
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
if (!heartbeatJSON) {
|
||||
// Testing or general notification like certificate expiry
|
||||
payload["msg"] = msg;
|
||||
|
||||
@@ -15,6 +15,13 @@ class HomeAssistant extends NotificationProvider {
|
||||
const notificationService = notification?.notificationService || defaultNotificationService;
|
||||
|
||||
try {
|
||||
let config = {
|
||||
headers: {
|
||||
Authorization: `Bearer ${notification.longLivedAccessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
};
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
await axios.post(
|
||||
`${notification.homeAssistantUrl.trim().replace(/\/*$/, "")}/api/services/notify/${notificationService}`,
|
||||
{
|
||||
@@ -26,14 +33,7 @@ class HomeAssistant extends NotificationProvider {
|
||||
channel: "Uptime Kuma",
|
||||
icon_url: "https://github.com/louislam/uptime-kuma/blob/master/public/icon.png?raw=true",
|
||||
} }),
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${notification.longLivedAccessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
}, config);
|
||||
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
|
||||
@@ -31,6 +31,8 @@ class Keep extends NotificationProvider {
|
||||
|
||||
let webhookURL = url + "/alerts/event/uptimekuma";
|
||||
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
|
||||
await axios.post(webhookURL, data, config);
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
|
||||
@@ -22,6 +22,7 @@ class Kook extends NotificationProvider {
|
||||
},
|
||||
};
|
||||
try {
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
await axios.post(url, data, config);
|
||||
return okMsg;
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ class Line extends NotificationProvider {
|
||||
"Authorization": "Bearer " + notification.lineChannelAccessToken
|
||||
}
|
||||
};
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
if (heartbeatJSON == null) {
|
||||
let testMessage = {
|
||||
"to": notification.lineUserID,
|
||||
|
||||
@@ -20,6 +20,7 @@ class LineNotify extends NotificationProvider {
|
||||
"Authorization": "Bearer " + notification.lineNotifyAccessToken
|
||||
}
|
||||
};
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
if (heartbeatJSON == null) {
|
||||
let testMessage = {
|
||||
"message": msg,
|
||||
|
||||
@@ -13,13 +13,14 @@ class LunaSea extends NotificationProvider {
|
||||
const url = "https://notify.lunasea.app/v1";
|
||||
|
||||
try {
|
||||
let config = this.getAxiosConfigWithProxy({});
|
||||
const target = this.getTarget(notification);
|
||||
if (heartbeatJSON == null) {
|
||||
let testdata = {
|
||||
"title": "Uptime Kuma Alert",
|
||||
"body": msg,
|
||||
};
|
||||
await axios.post(`${url}/custom/${target}`, testdata);
|
||||
await axios.post(`${url}/custom/${target}`, testdata, config);
|
||||
return okMsg;
|
||||
}
|
||||
|
||||
@@ -30,7 +31,7 @@ class LunaSea extends NotificationProvider {
|
||||
heartbeatJSON["msg"] +
|
||||
`\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`
|
||||
};
|
||||
await axios.post(`${url}/custom/${target}`, downdata);
|
||||
await axios.post(`${url}/custom/${target}`, downdata, config);
|
||||
return okMsg;
|
||||
}
|
||||
|
||||
@@ -41,7 +42,7 @@ class LunaSea extends NotificationProvider {
|
||||
heartbeatJSON["msg"] +
|
||||
`\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`
|
||||
};
|
||||
await axios.post(`${url}/custom/${target}`, updata);
|
||||
await axios.post(`${url}/custom/${target}`, updata, config);
|
||||
return okMsg;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ class Matrix extends NotificationProvider {
|
||||
"body": msg
|
||||
};
|
||||
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
await axios.put(`${notification.homeserverUrl}/_matrix/client/r0/rooms/${roomId}/send/m.room.message/${randomString}`, data, config);
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
|
||||
@@ -12,6 +12,7 @@ class Mattermost extends NotificationProvider {
|
||||
const okMsg = "Sent Successfully.";
|
||||
|
||||
try {
|
||||
let config = this.getAxiosConfigWithProxy({});
|
||||
const mattermostUserName = notification.mattermostusername || "Uptime Kuma";
|
||||
// If heartbeatJSON is null, assume non monitoring notification (Certificate warning) or testing.
|
||||
if (heartbeatJSON == null) {
|
||||
@@ -19,7 +20,7 @@ class Mattermost extends NotificationProvider {
|
||||
username: mattermostUserName,
|
||||
text: msg,
|
||||
};
|
||||
await axios.post(notification.mattermostWebhookUrl, mattermostTestData);
|
||||
await axios.post(notification.mattermostWebhookUrl, mattermostTestData, config);
|
||||
return okMsg;
|
||||
}
|
||||
|
||||
@@ -79,13 +80,11 @@ class Mattermost extends NotificationProvider {
|
||||
fallback:
|
||||
"Your " +
|
||||
monitorJSON.pathName +
|
||||
monitorJSON.name +
|
||||
" service went " +
|
||||
statusText,
|
||||
color: color,
|
||||
title:
|
||||
monitorJSON.pathName +
|
||||
monitorJSON.name +
|
||||
" service went " +
|
||||
statusText,
|
||||
title_link: monitorJSON.url,
|
||||
@@ -100,7 +99,7 @@ class Mattermost extends NotificationProvider {
|
||||
},
|
||||
],
|
||||
};
|
||||
await axios.post(notification.mattermostWebhookUrl, mattermostdata);
|
||||
await axios.post(notification.mattermostWebhookUrl, mattermostdata, config);
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
const { UP, DOWN } = require("../../src/util");
|
||||
const Crypto = require("crypto");
|
||||
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
|
||||
class NextcloudTalk extends NotificationProvider {
|
||||
name = "nextcloudtalk";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
// See documentation at https://nextcloud-talk.readthedocs.io/en/latest/bots/#sending-a-chat-message
|
||||
const okMsg = "Sent Successfully.";
|
||||
|
||||
// Create a random string
|
||||
const talkRandom = encodeURIComponent(
|
||||
Crypto
|
||||
.randomBytes(64)
|
||||
.toString("hex")
|
||||
.slice(0, 64)
|
||||
);
|
||||
|
||||
// Create the signature over random and message
|
||||
const talkSignature = Crypto
|
||||
.createHmac("sha256", Buffer.from(notification.botSecret, "utf8"))
|
||||
.update(Buffer.from(`${talkRandom}${msg}`, "utf8"))
|
||||
.digest("hex");
|
||||
|
||||
let silentUp = (heartbeatJSON?.status === UP && notification.sendSilentUp);
|
||||
let silentDown = (heartbeatJSON?.status === DOWN && notification.sendSilentDown);
|
||||
let silent = (silentUp || silentDown);
|
||||
|
||||
let url = `${notification.host}/ocs/v2.php/apps/spreed/api/v1/bot/${notification.conversationToken}/message`;
|
||||
let config = this.getAxiosConfigWithProxy({});
|
||||
|
||||
const data = {
|
||||
message: msg,
|
||||
silent
|
||||
};
|
||||
|
||||
const options = {
|
||||
...config,
|
||||
headers: {
|
||||
"X-Nextcloud-Talk-Bot-Random": talkRandom,
|
||||
"X-Nextcloud-Talk-Bot-Signature": talkSignature,
|
||||
"OCS-APIRequest": true,
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
let result = await axios.post(url, data, options);
|
||||
|
||||
if (result?.status === 201) {
|
||||
return okMsg;
|
||||
}
|
||||
|
||||
throw new Error("Nextcloud Talk Error " + (result?.status ?? "Unknown"));
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NextcloudTalk;
|
||||
@@ -0,0 +1,54 @@
|
||||
const { getMonitorRelativeURL, UP } = require("../../src/util");
|
||||
const { setting } = require("../util-server");
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
|
||||
class Notifery extends NotificationProvider {
|
||||
name = "notifery";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
const okMsg = "Sent Successfully.";
|
||||
const url = "https://api.notifery.com/event";
|
||||
|
||||
let data = {
|
||||
title: notification.notiferyTitle || "Uptime Kuma Alert",
|
||||
message: msg,
|
||||
};
|
||||
|
||||
if (notification.notiferyGroup) {
|
||||
data.group = notification.notiferyGroup;
|
||||
}
|
||||
|
||||
// Link to the monitor
|
||||
const baseURL = await setting("primaryBaseURL");
|
||||
if (baseURL && monitorJSON) {
|
||||
data.message += `\n\nMonitor: ${baseURL}${getMonitorRelativeURL(monitorJSON.id)}`;
|
||||
}
|
||||
|
||||
if (heartbeatJSON) {
|
||||
data.code = heartbeatJSON.status === UP ? 0 : 1;
|
||||
|
||||
if (heartbeatJSON.ping) {
|
||||
data.duration = heartbeatJSON.ping;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"x-api-key": notification.notiferyApiKey,
|
||||
};
|
||||
|
||||
let config = this.getAxiosConfigWithProxy({ headers });
|
||||
await axios.post(url, data, config);
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Notifery;
|
||||
@@ -1,5 +1,7 @@
|
||||
const { Liquid } = require("liquidjs");
|
||||
const { DOWN } = require("../../src/util");
|
||||
const { HttpProxyAgent } = require("http-proxy-agent");
|
||||
const { HttpsProxyAgent } = require("https-proxy-agent");
|
||||
|
||||
class NotificationProvider {
|
||||
|
||||
@@ -61,7 +63,11 @@ class NotificationProvider {
|
||||
* @returns {Promise<string>} rendered template
|
||||
*/
|
||||
async renderTemplate(template, msg, monitorJSON, heartbeatJSON) {
|
||||
const engine = new Liquid();
|
||||
const engine = new Liquid({
|
||||
root: "./no-such-directory-uptime-kuma",
|
||||
relativeReference: false,
|
||||
dynamicPartials: false,
|
||||
});
|
||||
const parsedTpl = engine.parse(template);
|
||||
|
||||
// Let's start with dummy values to simplify code
|
||||
@@ -115,6 +121,30 @@ class NotificationProvider {
|
||||
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns axios config with proxy agent if proxy env is set.
|
||||
* @param {object} axiosConfig - Axios config containing params
|
||||
* @returns {object} Axios config
|
||||
*/
|
||||
getAxiosConfigWithProxy(axiosConfig = {}) {
|
||||
const proxyEnv = process.env.notification_proxy || process.env.NOTIFICATION_PROXY;
|
||||
if (proxyEnv) {
|
||||
const proxyUrl = new URL(proxyEnv);
|
||||
|
||||
if (proxyUrl.protocol === "http:") {
|
||||
axiosConfig.httpAgent = new HttpProxyAgent(proxyEnv);
|
||||
axiosConfig.httpsAgent = new HttpsProxyAgent(proxyEnv);
|
||||
} else if (proxyUrl.protocol === "https:") {
|
||||
const agent = new HttpsProxyAgent(proxyEnv);
|
||||
axiosConfig.httpAgent = agent;
|
||||
axiosConfig.httpsAgent = agent;
|
||||
}
|
||||
|
||||
axiosConfig.proxy = false;
|
||||
}
|
||||
return axiosConfig;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NotificationProvider;
|
||||
|
||||
@@ -22,6 +22,8 @@ class Ntfy extends NotificationProvider {
|
||||
"Authorization": "Bearer " + notification.ntfyaccesstoken,
|
||||
};
|
||||
}
|
||||
let config = { headers };
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
// If heartbeatJSON is null, assume non monitoring notification (Certificate warning) or testing.
|
||||
if (heartbeatJSON == null) {
|
||||
let ntfyTestData = {
|
||||
@@ -31,7 +33,7 @@ class Ntfy extends NotificationProvider {
|
||||
"priority": notification.ntfyPriority,
|
||||
"tags": [ "test_tube" ],
|
||||
};
|
||||
await axios.post(notification.ntfyserverurl, ntfyTestData, { headers: headers });
|
||||
await axios.post(notification.ntfyserverurl, ntfyTestData, config);
|
||||
return okMsg;
|
||||
}
|
||||
let tags = [];
|
||||
@@ -41,8 +43,8 @@ class Ntfy extends NotificationProvider {
|
||||
if (heartbeatJSON.status === DOWN) {
|
||||
tags = [ "red_circle" ];
|
||||
status = "Down";
|
||||
// if priority is not 5, increase priority for down alerts
|
||||
priority = priority === 5 ? priority : priority + 1;
|
||||
// defaults to max(priority + 1, 5)
|
||||
priority = notification.ntfyPriorityDown || (priority === 5 ? priority : priority + 1);
|
||||
} else if (heartbeatJSON["status"] === UP) {
|
||||
tags = [ "green_circle" ];
|
||||
status = "Up";
|
||||
@@ -70,7 +72,7 @@ class Ntfy extends NotificationProvider {
|
||||
data.icon = notification.ntfyIcon;
|
||||
}
|
||||
|
||||
await axios.post(notification.ntfyserverurl, data, { headers: headers });
|
||||
await axios.post(notification.ntfyserverurl, data, config);
|
||||
|
||||
return okMsg;
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ class Octopush extends NotificationProvider {
|
||||
"cache-control": "no-cache"
|
||||
}
|
||||
};
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
let data = {
|
||||
"recipients": [
|
||||
{
|
||||
@@ -53,6 +54,7 @@ class Octopush extends NotificationProvider {
|
||||
},
|
||||
params: data
|
||||
};
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
|
||||
// V1 API returns 200 even on error so we must check
|
||||
// response data
|
||||
|
||||
@@ -25,6 +25,7 @@ class OneBot extends NotificationProvider {
|
||||
"Authorization": "Bearer " + notification.accessToken,
|
||||
}
|
||||
};
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
let pushText = "UptimeKuma Alert: " + msg;
|
||||
let data = {
|
||||
"auto_escape": true,
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
const { DOWN, UP } = require("../../src/util");
|
||||
|
||||
class OneChat extends NotificationProvider {
|
||||
name = "OneChat";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
const okMsg = "Sent Successfully.";
|
||||
const url = "https://chat-api.one.th/message/api/v1/push_message";
|
||||
|
||||
try {
|
||||
let config = {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + notification.accessToken,
|
||||
},
|
||||
};
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
if (heartbeatJSON == null) {
|
||||
const testMessage = {
|
||||
to: notification.recieverId,
|
||||
bot_id: notification.botId,
|
||||
type: "text",
|
||||
message: "Test Successful!",
|
||||
};
|
||||
await axios.post(url, testMessage, config);
|
||||
} else if (heartbeatJSON["status"] === DOWN) {
|
||||
const downMessage = {
|
||||
to: notification.recieverId,
|
||||
bot_id: notification.botId,
|
||||
type: "text",
|
||||
message:
|
||||
`UptimeKuma Alert:
|
||||
[🔴 Down]
|
||||
Name: ${monitorJSON["name"]}
|
||||
${heartbeatJSON["msg"]}
|
||||
Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`,
|
||||
};
|
||||
await axios.post(url, downMessage, config);
|
||||
} else if (heartbeatJSON["status"] === UP) {
|
||||
const upMessage = {
|
||||
to: notification.recieverId,
|
||||
bot_id: notification.botId,
|
||||
type: "text",
|
||||
message:
|
||||
`UptimeKuma Alert:
|
||||
[🟢 Up]
|
||||
Name: ${monitorJSON["name"]}
|
||||
${heartbeatJSON["msg"]}
|
||||
Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`,
|
||||
};
|
||||
await axios.post(url, upMessage, config);
|
||||
}
|
||||
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
// Handle errors and throw a descriptive message
|
||||
if (error.response) {
|
||||
const errorMessage =
|
||||
error.response.data?.message ||
|
||||
"Unknown API error occurred.";
|
||||
throw new Error(`OneChat API Error: ${errorMessage}`);
|
||||
} else {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OneChat;
|
||||
@@ -33,6 +33,7 @@ class Onesender extends NotificationProvider {
|
||||
"Authorization": "Bearer " + notification.onesenderToken,
|
||||
}
|
||||
};
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
await axios.post(notification.onesenderURL, data, config);
|
||||
return okMsg;
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ class Opsgenie extends NotificationProvider {
|
||||
"Authorization": `GenieKey ${notification.opsgenieApiKey}`,
|
||||
}
|
||||
};
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
|
||||
let res = await axios.post(url, data, config);
|
||||
if (res.status == null) {
|
||||
|
||||
@@ -23,9 +23,7 @@ class PagerDuty extends NotificationProvider {
|
||||
|
||||
if (heartbeatJSON.status === UP) {
|
||||
const title = "Uptime Kuma Monitor ✅ Up";
|
||||
const eventAction = notification.pagerdutyAutoResolve || null;
|
||||
|
||||
return this.postNotification(notification, title, heartbeatJSON.msg, monitorJSON, eventAction);
|
||||
return this.postNotification(notification, title, heartbeatJSON.msg, monitorJSON, "resolve");
|
||||
}
|
||||
|
||||
if (heartbeatJSON.status === DOWN) {
|
||||
@@ -63,10 +61,6 @@ class PagerDuty extends NotificationProvider {
|
||||
*/
|
||||
async postNotification(notification, title, body, monitorInfo, eventAction = "trigger") {
|
||||
|
||||
if (eventAction == null) {
|
||||
return "No action required";
|
||||
}
|
||||
|
||||
let monitorUrl;
|
||||
if (monitorInfo.type === "port") {
|
||||
monitorUrl = monitorInfo.hostname;
|
||||
@@ -79,6 +73,13 @@ class PagerDuty extends NotificationProvider {
|
||||
monitorUrl = monitorInfo.url;
|
||||
}
|
||||
|
||||
if (eventAction === "resolve") {
|
||||
if (notification.pagerdutyAutoResolve === "0") {
|
||||
return "no action required";
|
||||
}
|
||||
eventAction = notification.pagerdutyAutoResolve;
|
||||
}
|
||||
|
||||
const options = {
|
||||
method: "POST",
|
||||
url: notification.pagerdutyIntegrationUrl,
|
||||
|
||||
@@ -15,7 +15,7 @@ class PromoSMS extends NotificationProvider {
|
||||
notification.promosmsAllowLongSMS = false;
|
||||
}
|
||||
|
||||
//TODO: Add option for enabling special characters. It will decrese message max length from 160 to 70 chars.
|
||||
//TODO: Add option for enabling special characters. It will decrease message max length from 160 to 70 chars.
|
||||
//Lets remove non ascii char
|
||||
let cleanMsg = msg.replace(/[^\x00-\x7F]/g, "");
|
||||
|
||||
@@ -27,6 +27,7 @@ class PromoSMS extends NotificationProvider {
|
||||
"Accept": "text/json",
|
||||
}
|
||||
};
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
let data = {
|
||||
"recipients": [ notification.promosmsPhoneNumber ],
|
||||
//Trim message to maximum length of 1 SMS or 4 if we allowed long messages
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
const { UP } = require("../../src/util");
|
||||
|
||||
class Pumble extends NotificationProvider {
|
||||
name = "pumble";
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
const okMsg = "Sent Successfully.";
|
||||
|
||||
try {
|
||||
let config = this.getAxiosConfigWithProxy({});
|
||||
if (heartbeatJSON === null && monitorJSON === null) {
|
||||
let data = {
|
||||
"attachments": [
|
||||
{
|
||||
"title": "Uptime Kuma Alert",
|
||||
"text": msg,
|
||||
"color": "#5BDD8B"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
await axios.post(notification.webhookURL, data, config);
|
||||
return okMsg;
|
||||
}
|
||||
|
||||
let data = {
|
||||
"attachments": [
|
||||
{
|
||||
"title": `${monitorJSON["name"]} is ${heartbeatJSON["status"] === UP ? "up" : "down"}`,
|
||||
"text": heartbeatJSON["msg"],
|
||||
"color": (heartbeatJSON["status"] === UP ? "#5BDD8B" : "#DC3645"),
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
await axios.post(notification.webhookURL, data, config);
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Pumble;
|
||||
@@ -20,6 +20,7 @@ class Pushbullet extends NotificationProvider {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
};
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
if (heartbeatJSON == null) {
|
||||
let data = {
|
||||
"type": "note",
|
||||
|
||||
@@ -11,7 +11,7 @@ class PushDeer extends NotificationProvider {
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
const okMsg = "Sent Successfully.";
|
||||
const serverUrl = notification.pushdeerServer || "https://api2.pushdeer.com";
|
||||
// capture group below is nessesary to prevent an ReDOS-attack
|
||||
// capture group below is necessary to prevent an ReDOS-attack
|
||||
const url = `${serverUrl.trim().replace(/([^/])\/+$/, "$1")}/message/push`;
|
||||
|
||||
let valid = msg != null && monitorJSON != null && heartbeatJSON != null;
|
||||
@@ -33,7 +33,8 @@ class PushDeer extends NotificationProvider {
|
||||
};
|
||||
|
||||
try {
|
||||
let res = await axios.post(url, data);
|
||||
let config = this.getAxiosConfigWithProxy({});
|
||||
let res = await axios.post(url, data, config);
|
||||
|
||||
if ("error" in res.data) {
|
||||
let error = res.data.error;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const { getMonitorRelativeURL } = require("../../src/util");
|
||||
const { setting } = require("../util-server");
|
||||
const { UP } = require("../../src/util");
|
||||
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
@@ -40,18 +41,24 @@ class Pushover extends NotificationProvider {
|
||||
}
|
||||
|
||||
try {
|
||||
let config = this.getAxiosConfigWithProxy({});
|
||||
if (heartbeatJSON == null) {
|
||||
await axios.post(url, data);
|
||||
return okMsg;
|
||||
} else {
|
||||
data.message += `\n<b>Time (${heartbeatJSON["timezone"]})</b>:${heartbeatJSON["localDateTime"]}`;
|
||||
await axios.post(url, data);
|
||||
await axios.post(url, data, config);
|
||||
return okMsg;
|
||||
}
|
||||
|
||||
if (heartbeatJSON.status === UP && notification.pushoversounds_up) {
|
||||
// default = DOWN => DOWN-sound is also played for non-UP/DOWN notiifcations
|
||||
data.sound = notification.pushoversounds_up;
|
||||
}
|
||||
|
||||
data.message += `\n<b>Time (${heartbeatJSON["timezone"]})</b>: ${heartbeatJSON["localDateTime"]}`;
|
||||
await axios.post(url, data, config);
|
||||
return okMsg;
|
||||
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,11 +17,12 @@ class PushPlus extends NotificationProvider {
|
||||
const okMsg = "Sent Successfully.";
|
||||
const url = "https://www.pushplus.plus/send";
|
||||
try {
|
||||
const config = {
|
||||
let config = {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
};
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
const params = {
|
||||
"token": notification.pushPlusSendKey,
|
||||
"title": this.checkStatus(heartbeatJSON, monitorJSON),
|
||||
|
||||
@@ -11,6 +11,7 @@ class Pushy extends NotificationProvider {
|
||||
const okMsg = "Sent Successfully.";
|
||||
|
||||
try {
|
||||
let config = this.getAxiosConfigWithProxy({});
|
||||
await axios.post(`https://api.pushy.me/push?api_key=${notification.pushyAPIKey}`, {
|
||||
"to": notification.pushyToken,
|
||||
"data": {
|
||||
@@ -21,7 +22,7 @@ class Pushy extends NotificationProvider {
|
||||
"badge": 1,
|
||||
"sound": "ping.aiff"
|
||||
}
|
||||
});
|
||||
}, config);
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
|
||||
@@ -14,6 +14,7 @@ class RocketChat extends NotificationProvider {
|
||||
const okMsg = "Sent Successfully.";
|
||||
|
||||
try {
|
||||
let config = this.getAxiosConfigWithProxy({});
|
||||
if (heartbeatJSON == null) {
|
||||
let data = {
|
||||
"text": msg,
|
||||
@@ -21,7 +22,7 @@ class RocketChat extends NotificationProvider {
|
||||
"username": notification.rocketusername,
|
||||
"icon_emoji": notification.rocketiconemo,
|
||||
};
|
||||
await axios.post(notification.rocketwebhookURL, data);
|
||||
await axios.post(notification.rocketwebhookURL, data, config);
|
||||
return okMsg;
|
||||
}
|
||||
|
||||
@@ -55,7 +56,7 @@ class RocketChat extends NotificationProvider {
|
||||
data.attachments[0].title_link = baseURL + getMonitorRelativeURL(monitorJSON.id);
|
||||
}
|
||||
|
||||
await axios.post(notification.rocketwebhookURL, data);
|
||||
await axios.post(notification.rocketwebhookURL, data, config);
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
|
||||
@@ -17,7 +17,7 @@ class SendGrid extends NotificationProvider {
|
||||
Authorization: `Bearer ${notification.sendgridApiKey}`,
|
||||
},
|
||||
};
|
||||
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
let personalizations = {
|
||||
to: [{ email: notification.sendgridToEmail }],
|
||||
};
|
||||
|
||||
@@ -18,10 +18,11 @@ class ServerChan extends NotificationProvider {
|
||||
: `https://sctapi.ftqq.com/${notification.serverChanSendKey}.send`;
|
||||
|
||||
try {
|
||||
let config = this.getAxiosConfigWithProxy({});
|
||||
await axios.post(url, {
|
||||
"title": this.checkStatus(heartbeatJSON, monitorJSON),
|
||||
"desp": msg,
|
||||
});
|
||||
}, config);
|
||||
|
||||
return okMsg;
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ class SerwerSMS extends NotificationProvider {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
};
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
let data = {
|
||||
"username": notification.serwersmsUsername,
|
||||
"password": notification.serwersmsPassword,
|
||||
|
||||
@@ -12,12 +12,12 @@ class SevenIO extends NotificationProvider {
|
||||
const okMsg = "Sent Successfully.";
|
||||
|
||||
const data = {
|
||||
to: notification.sevenioTo,
|
||||
to: notification.sevenioReceiver,
|
||||
from: notification.sevenioSender || "Uptime Kuma",
|
||||
text: msg,
|
||||
};
|
||||
|
||||
const config = {
|
||||
let config = {
|
||||
baseURL: "https://gateway.seven.io/api/",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -26,6 +26,7 @@ class SevenIO extends NotificationProvider {
|
||||
};
|
||||
|
||||
try {
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
// testing or certificate expiry notification
|
||||
if (heartbeatJSON == null) {
|
||||
await axios.post("sms", data, config);
|
||||
|
||||
@@ -17,7 +17,7 @@ class Signal extends NotificationProvider {
|
||||
"recipients": notification.signalRecipients.replace(/\s/g, "").split(","),
|
||||
};
|
||||
let config = {};
|
||||
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
await axios.post(notification.signalURL, data, config);
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
|
||||
@@ -21,11 +21,12 @@ class SIGNL4 extends NotificationProvider {
|
||||
monitorUrl: this.extractAddress(monitorJSON),
|
||||
};
|
||||
|
||||
const config = {
|
||||
let config = {
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
};
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
|
||||
if (heartbeatJSON == null) {
|
||||
// Test alert
|
||||
|
||||
@@ -2,6 +2,7 @@ const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
const { setSettings, setting } = require("../util-server");
|
||||
const { getMonitorRelativeURL, UP, log } = require("../../src/util");
|
||||
const isUrl = require("is-url");
|
||||
|
||||
class Slack extends NotificationProvider {
|
||||
name = "slack";
|
||||
@@ -49,7 +50,7 @@ class Slack extends NotificationProvider {
|
||||
}
|
||||
|
||||
const address = this.extractAddress(monitorJSON);
|
||||
if (address) {
|
||||
if (isUrl(address)) {
|
||||
try {
|
||||
actions.push({
|
||||
"type": "button",
|
||||
@@ -130,6 +131,7 @@ class Slack extends NotificationProvider {
|
||||
}
|
||||
|
||||
try {
|
||||
let config = this.getAxiosConfigWithProxy({});
|
||||
if (heartbeatJSON == null) {
|
||||
let data = {
|
||||
"text": msg,
|
||||
@@ -137,7 +139,7 @@ class Slack extends NotificationProvider {
|
||||
"username": notification.slackusername,
|
||||
"icon_emoji": notification.slackiconemo,
|
||||
};
|
||||
await axios.post(notification.slackwebhookURL, data);
|
||||
await axios.post(notification.slackwebhookURL, data, config);
|
||||
return okMsg;
|
||||
}
|
||||
|
||||
@@ -145,6 +147,7 @@ class Slack extends NotificationProvider {
|
||||
|
||||
const title = "Uptime Kuma Alert";
|
||||
let data = {
|
||||
"text": msg,
|
||||
"channel": notification.slackchannel,
|
||||
"username": notification.slackusername,
|
||||
"icon_emoji": notification.slackiconemo,
|
||||
@@ -166,7 +169,7 @@ class Slack extends NotificationProvider {
|
||||
await Slack.deprecateURL(notification.slackbutton);
|
||||
}
|
||||
|
||||
await axios.post(notification.slackwebhookURL, data);
|
||||
await axios.post(notification.slackwebhookURL, data, config);
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
|
||||
class SMSPlanet extends NotificationProvider {
|
||||
name = "SMSPlanet";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
const okMsg = "Sent Successfully.";
|
||||
const url = "https://api2.smsplanet.pl/sms";
|
||||
|
||||
try {
|
||||
let config = {
|
||||
headers: {
|
||||
"Authorization": "Bearer " + notification.smsplanetApiToken,
|
||||
"content-type": "multipart/form-data"
|
||||
}
|
||||
};
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
|
||||
let data = {
|
||||
"from": notification.smsplanetSenderName,
|
||||
"to": notification.smsplanetPhoneNumbers,
|
||||
"msg": msg.replace(/🔴/, "❌")
|
||||
};
|
||||
|
||||
let response = await axios.post(url, data, config);
|
||||
if (!response.data?.messageId) {
|
||||
throw new Error(response.data?.errorMsg ?? "SMSPlanet server did not respond with the expected result");
|
||||
}
|
||||
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SMSPlanet;
|
||||
@@ -18,6 +18,7 @@ class SMSC extends NotificationProvider {
|
||||
"Accept": "text/json",
|
||||
}
|
||||
};
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
|
||||
let getArray = [
|
||||
"fmt=3",
|
||||
|
||||
@@ -11,59 +11,129 @@ class SMSEagle extends NotificationProvider {
|
||||
const okMsg = "Sent Successfully.";
|
||||
|
||||
try {
|
||||
let config = {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
if (notification.smseagleApiType === "smseagle-apiv1") { // according to https://www.smseagle.eu/apiv1/
|
||||
let config = {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
}
|
||||
};
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
|
||||
let sendMethod;
|
||||
let recipientType;
|
||||
let duration;
|
||||
let voiceId;
|
||||
|
||||
if (notification.smseagleRecipientType === "smseagle-contact") {
|
||||
recipientType = "contactname";
|
||||
sendMethod = "/send_tocontact";
|
||||
} else if (notification.smseagleRecipientType === "smseagle-group") {
|
||||
recipientType = "groupname";
|
||||
sendMethod = "/send_togroup";
|
||||
} else if (notification.smseagleRecipientType === "smseagle-to") {
|
||||
recipientType = "to";
|
||||
sendMethod = "/send_sms";
|
||||
if (notification.smseagleMsgType !== "smseagle-sms") {
|
||||
duration = notification.smseagleDuration ?? 10;
|
||||
|
||||
if (notification.smseagleMsgType === "smseagle-ring") {
|
||||
sendMethod = "/ring_call";
|
||||
} else if (notification.smseagleMsgType === "smseagle-tts") {
|
||||
sendMethod = "/tts_call";
|
||||
} else if (notification.smseagleMsgType === "smseagle-tts-advanced") {
|
||||
sendMethod = "/tts_adv_call";
|
||||
voiceId = notification.smseagleTtsModel ? notification.smseagleTtsModel : 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let postData;
|
||||
let sendMethod;
|
||||
let recipientType;
|
||||
const url = new URL(notification.smseagleUrl + "/http_api" + sendMethod);
|
||||
|
||||
let encoding = (notification.smseagleEncoding) ? "1" : "0";
|
||||
let priority = (notification.smseaglePriority) ? notification.smseaglePriority : "0";
|
||||
|
||||
if (notification.smseagleRecipientType === "smseagle-contact") {
|
||||
recipientType = "contactname";
|
||||
sendMethod = "sms.send_tocontact";
|
||||
}
|
||||
if (notification.smseagleRecipientType === "smseagle-group") {
|
||||
recipientType = "groupname";
|
||||
sendMethod = "sms.send_togroup";
|
||||
}
|
||||
if (notification.smseagleRecipientType === "smseagle-to") {
|
||||
recipientType = "to";
|
||||
sendMethod = "sms.send_sms";
|
||||
}
|
||||
|
||||
let params = {
|
||||
access_token: notification.smseagleToken,
|
||||
[recipientType]: notification.smseagleRecipient,
|
||||
message: msg,
|
||||
responsetype: "extended",
|
||||
unicode: encoding,
|
||||
highpriority: priority
|
||||
};
|
||||
|
||||
postData = {
|
||||
method: sendMethod,
|
||||
params: params
|
||||
};
|
||||
|
||||
let resp = await axios.post(notification.smseagleUrl + "/jsonrpc/sms", postData, config);
|
||||
|
||||
if ((JSON.stringify(resp.data)).indexOf("message_id") === -1) {
|
||||
let error = "";
|
||||
if (resp.data.result && resp.data.result.error_text) {
|
||||
error = `SMSEagle API returned error: ${JSON.stringify(resp.data.result.error_text)}`;
|
||||
url.searchParams.append("access_token", notification.smseagleToken);
|
||||
url.searchParams.append(recipientType, notification.smseagleRecipient);
|
||||
if (!notification.smseagleRecipientType || notification.smseagleRecipientType === "smseagle-sms") {
|
||||
url.searchParams.append("unicode", (notification.smseagleEncoding) ? "1" : "0");
|
||||
url.searchParams.append("highpriority", notification.smseaglePriority ?? "0");
|
||||
} else {
|
||||
error = "SMSEagle API returned an unexpected response";
|
||||
url.searchParams.append("duration", duration);
|
||||
}
|
||||
if (notification.smseagleRecipientType !== "smseagle-ring") {
|
||||
url.searchParams.append("message", msg);
|
||||
}
|
||||
if (voiceId) {
|
||||
url.searchParams.append("voice_id", voiceId);
|
||||
}
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
return okMsg;
|
||||
let resp = await axios.get(url.toString(), config);
|
||||
|
||||
if (resp.data.indexOf("OK") === -1) {
|
||||
let error = `SMSEagle API returned error: ${resp.data}`;
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
return okMsg;
|
||||
} else if (notification.smseagleApiType === "smseagle-apiv2") { // according to https://www.smseagle.eu/docs/apiv2/
|
||||
let config = {
|
||||
headers: {
|
||||
"access-token": notification.smseagleToken,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
};
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
|
||||
let encoding = (notification.smseagleEncoding) ? "unicode" : "standard";
|
||||
let priority = (notification.smseaglePriority) ?? 0;
|
||||
|
||||
let postData = {
|
||||
text: msg,
|
||||
encoding: encoding,
|
||||
priority: priority
|
||||
};
|
||||
|
||||
if (notification.smseagleRecipientContact) {
|
||||
postData["contacts"] = notification.smseagleRecipientContact.split(",").map(Number);
|
||||
}
|
||||
if (notification.smseagleRecipientGroup) {
|
||||
postData["groups"] = notification.smseagleRecipientGroup.split(",").map(Number);
|
||||
}
|
||||
if (notification.smseagleRecipientTo) {
|
||||
postData["to"] = notification.smseagleRecipientTo.split(",");
|
||||
}
|
||||
|
||||
let endpoint = "/messages/sms";
|
||||
|
||||
if (notification.smseagleMsgType !== "smseagle-sms") {
|
||||
|
||||
postData["duration"] = notification.smseagleDuration ?? 10;
|
||||
|
||||
if (notification.smseagleMsgType === "smseagle-ring") {
|
||||
endpoint = "/calls/ring";
|
||||
} else if (notification.smseagleMsgType === "smseagle-tts") {
|
||||
endpoint = "/calls/tts";
|
||||
} else if (notification.smseagleMsgType === "smseagle-tts-advanced") {
|
||||
endpoint = "/calls/tts_advanced";
|
||||
postData["voice_id"] = notification.smseagleTtsModel ?? 1;
|
||||
}
|
||||
}
|
||||
|
||||
let resp = await axios.post(notification.smseagleUrl + "/api/v2" + endpoint, postData, config);
|
||||
|
||||
const queuedCount = resp.data.filter(x => x.status === "queued").length;
|
||||
const unqueuedCount = resp.data.length - queuedCount;
|
||||
|
||||
if (resp.status !== 200 || queuedCount === 0) {
|
||||
if (!resp.data.length) {
|
||||
throw new Error("SMSEagle API returned an empty response");
|
||||
}
|
||||
throw new Error(`SMSEagle API returned error: ${JSON.stringify(resp.data)}`);
|
||||
}
|
||||
|
||||
if (unqueuedCount) {
|
||||
return `Sent ${queuedCount}/${resp.data.length} Messages Successfully.`;
|
||||
}
|
||||
|
||||
return okMsg;
|
||||
}
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
|
||||
class SMSIR extends NotificationProvider {
|
||||
name = "smsir";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
const okMsg = "Sent Successfully.";
|
||||
const url = "https://api.sms.ir/v1/send/verify";
|
||||
|
||||
try {
|
||||
let config = {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
"X-API-Key": notification.smsirApiKey
|
||||
}
|
||||
};
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
|
||||
const formattedMobiles = notification.smsirNumber
|
||||
.split(",")
|
||||
.map(mobile => {
|
||||
if (mobile.length === 11 && mobile.startsWith("09") && String(parseInt(mobile)) === mobile.substring(1)) {
|
||||
// 09xxxxxxxxx Format
|
||||
return mobile.substring(1);
|
||||
}
|
||||
|
||||
return mobile;
|
||||
});
|
||||
|
||||
const MAX_MESSAGE_LENGTH = 20; // This is a limitation placed by SMSIR
|
||||
// Shorten By removing spaces, keeping context is better than cutting off the text
|
||||
// If that does not work, truncate. Still better than not receiving an SMS
|
||||
if (msg.length > MAX_MESSAGE_LENGTH) {
|
||||
msg = msg.replace(/\s/g, "");
|
||||
}
|
||||
|
||||
if (msg.length > MAX_MESSAGE_LENGTH) {
|
||||
msg = msg.substring(0, MAX_MESSAGE_LENGTH - 1 - "...".length) + "...";
|
||||
}
|
||||
|
||||
// Run multiple network requests at once
|
||||
const requestPromises = formattedMobiles
|
||||
.map(mobile => {
|
||||
axios.post(
|
||||
url,
|
||||
{
|
||||
mobile: mobile,
|
||||
templateId: parseInt(notification.smsirTemplate),
|
||||
parameters: [
|
||||
{
|
||||
name: "uptkumaalert",
|
||||
value: msg
|
||||
}
|
||||
]
|
||||
},
|
||||
config
|
||||
);
|
||||
});
|
||||
|
||||
await Promise.all(requestPromises);
|
||||
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SMSIR;
|
||||
@@ -18,7 +18,8 @@ class SMSManager extends NotificationProvider {
|
||||
number: notification.numbers,
|
||||
gateway: notification.messageType,
|
||||
};
|
||||
await axios.get(`${url}?apikey=${data.apikey}&message=${data.message}&number=${data.number}&gateway=${data.messageType}`);
|
||||
let config = this.getAxiosConfigWithProxy({});
|
||||
await axios.get(`${url}?apikey=${data.apikey}&message=${data.message}&number=${data.number}&gateway=${data.messageType}`, config);
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
|
||||
@@ -29,6 +29,7 @@ class SMSPartner extends NotificationProvider {
|
||||
"Accept": "application/json",
|
||||
}
|
||||
};
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
|
||||
let resp = await axios.post(url, data, config);
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ class SMTP extends NotificationProvider {
|
||||
// default values in case the user does not want to template
|
||||
let subject = msg;
|
||||
let body = msg;
|
||||
let useHTMLBody = false;
|
||||
if (heartbeatJSON) {
|
||||
body = `${msg}\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`;
|
||||
}
|
||||
@@ -50,11 +51,11 @@ class SMTP extends NotificationProvider {
|
||||
// cannot end with whitespace as this often raises spam scores
|
||||
const customSubject = notification.customSubject?.trim() || "";
|
||||
const customBody = notification.customBody?.trim() || "";
|
||||
|
||||
if (customSubject !== "") {
|
||||
subject = await this.renderTemplate(customSubject, msg, monitorJSON, heartbeatJSON);
|
||||
}
|
||||
if (customBody !== "") {
|
||||
useHTMLBody = notification.htmlBody || false;
|
||||
body = await this.renderTemplate(customBody, msg, monitorJSON, heartbeatJSON);
|
||||
}
|
||||
}
|
||||
@@ -67,7 +68,8 @@ class SMTP extends NotificationProvider {
|
||||
bcc: notification.smtpBCC,
|
||||
to: notification.smtpTo,
|
||||
subject: subject,
|
||||
text: body,
|
||||
// If the email body is custom, and the user wants it, set the email body as HTML
|
||||
[useHTMLBody ? "html" : "text"]: body
|
||||
});
|
||||
|
||||
return okMsg;
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
const { DOWN, UP } = require("../../src/util");
|
||||
|
||||
class SpugPush extends NotificationProvider {
|
||||
|
||||
name = "SpugPush";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
let okMsg = "Sent Successfully.";
|
||||
try {
|
||||
let formData = {
|
||||
title: "Uptime Kuma Message",
|
||||
content: msg
|
||||
};
|
||||
if (heartbeatJSON) {
|
||||
if (heartbeatJSON["status"] === UP) {
|
||||
formData.title = `UptimeKuma 「${monitorJSON["name"]}」 is Up`;
|
||||
formData.content = `[✅ Up] ${heartbeatJSON["msg"]}`;
|
||||
} else if (heartbeatJSON["status"] === DOWN) {
|
||||
formData.title = `UptimeKuma 「${monitorJSON["name"]}」 is Down`;
|
||||
formData.content = `[🔴 Down] ${heartbeatJSON["msg"]}`;
|
||||
}
|
||||
}
|
||||
const apiUrl = `https://push.spug.cc/send/${notification.templateKey}`;
|
||||
let config = this.getAxiosConfigWithProxy({});
|
||||
await axios.post(apiUrl, formData, config);
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SpugPush;
|
||||
@@ -45,6 +45,7 @@ class Squadcast extends NotificationProvider {
|
||||
}
|
||||
});
|
||||
}
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
|
||||
await axios.post(notification.squadcastWebhookURL, data, config);
|
||||
return okMsg;
|
||||
|
||||
@@ -31,8 +31,9 @@ class Stackfield extends NotificationProvider {
|
||||
const data = {
|
||||
"Title": textMsg,
|
||||
};
|
||||
let config = this.getAxiosConfigWithProxy({});
|
||||
|
||||
await axios.post(notification.stackfieldwebhookURL, data);
|
||||
await axios.post(notification.stackfieldwebhookURL, data, config);
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
|
||||
@@ -185,7 +185,8 @@ class Teams extends NotificationProvider {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
_sendNotification = async (webhookUrl, payload) => {
|
||||
await axios.post(webhookUrl, payload);
|
||||
let config = this.getAxiosConfigWithProxy({});
|
||||
await axios.post(webhookUrl, payload, config);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,7 +25,8 @@ class TechulusPush extends NotificationProvider {
|
||||
}
|
||||
|
||||
try {
|
||||
await axios.post(`https://push.techulus.com/api/v1/notify/${notification.pushAPIKey}`, data);
|
||||
let config = this.getAxiosConfigWithProxy({});
|
||||
await axios.post(`https://push.techulus.com/api/v1/notify/${notification.pushAPIKey}`, data, config);
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
|
||||
@@ -17,6 +17,7 @@ class Telegram extends NotificationProvider {
|
||||
text: msg,
|
||||
disable_notification: notification.telegramSendSilently ?? false,
|
||||
protect_content: notification.telegramProtectContent ?? false,
|
||||
link_preview_options: { is_disabled: true },
|
||||
};
|
||||
if (notification.telegramMessageThreadID) {
|
||||
params.message_thread_id = notification.telegramMessageThreadID;
|
||||
@@ -30,9 +31,9 @@ class Telegram extends NotificationProvider {
|
||||
}
|
||||
}
|
||||
|
||||
await axios.get(`${url}/bot${notification.telegramBotToken}/sendMessage`, {
|
||||
params: params,
|
||||
});
|
||||
let config = this.getAxiosConfigWithProxy();
|
||||
|
||||
await axios.post(`${url}/bot${notification.telegramBotToken}/sendMessage`, params, config);
|
||||
return okMsg;
|
||||
|
||||
} catch (error) {
|
||||
|
||||
@@ -10,12 +10,13 @@ class Threema extends NotificationProvider {
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
const url = "https://msgapi.threema.ch/send_simple";
|
||||
|
||||
const config = {
|
||||
let config = {
|
||||
headers: {
|
||||
"Accept": "*/*",
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8"
|
||||
}
|
||||
};
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
|
||||
const data = {
|
||||
from: notification.threemaSenderIdentity,
|
||||
|
||||
@@ -19,11 +19,15 @@ class Twilio extends NotificationProvider {
|
||||
"Authorization": "Basic " + Buffer.from(apiKey + ":" + notification.twilioAuthToken).toString("base64"),
|
||||
}
|
||||
};
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
|
||||
let data = new URLSearchParams();
|
||||
data.append("To", notification.twilioToNumber);
|
||||
data.append("From", notification.twilioFromNumber);
|
||||
data.append("Body", msg);
|
||||
if (notification.twilioMessagingServiceSID) {
|
||||
data.append("MessagingServiceSid", notification.twilioMessagingServiceSID);
|
||||
}
|
||||
|
||||
await axios.post(`https://api.twilio.com/2010-04-01/Accounts/${(notification.twilioAccountSID)}/Messages.json`, data, config);
|
||||
|
||||
|
||||
@@ -11,13 +11,14 @@ class WAHA extends NotificationProvider {
|
||||
const okMsg = "Sent Successfully.";
|
||||
|
||||
try {
|
||||
const config = {
|
||||
let config = {
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"X-Api-Key": notification.wahaApiKey,
|
||||
}
|
||||
};
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
|
||||
let data = {
|
||||
"session": notification.wahaSession,
|
||||
|
||||
@@ -12,6 +12,8 @@ class Webhook extends NotificationProvider {
|
||||
const okMsg = "Sent Successfully.";
|
||||
|
||||
try {
|
||||
const httpMethod = notification.httpMethod.toLowerCase() || "post";
|
||||
|
||||
let data = {
|
||||
heartbeat: heartbeatJSON,
|
||||
monitor: monitorJSON,
|
||||
@@ -21,7 +23,19 @@ class Webhook extends NotificationProvider {
|
||||
headers: {}
|
||||
};
|
||||
|
||||
if (notification.webhookContentType === "form-data") {
|
||||
if (httpMethod === "get") {
|
||||
config.params = {
|
||||
msg: msg
|
||||
};
|
||||
|
||||
if (heartbeatJSON) {
|
||||
config.params.heartbeat = JSON.stringify(heartbeatJSON);
|
||||
}
|
||||
|
||||
if (monitorJSON) {
|
||||
config.params.monitor = JSON.stringify(monitorJSON);
|
||||
}
|
||||
} else if (notification.webhookContentType === "form-data") {
|
||||
const formData = new FormData();
|
||||
formData.append("data", JSON.stringify(data));
|
||||
config.headers = formData.getHeaders();
|
||||
@@ -37,11 +51,18 @@ class Webhook extends NotificationProvider {
|
||||
...JSON.parse(notification.webhookAdditionalHeaders)
|
||||
};
|
||||
} catch (err) {
|
||||
throw "Additional Headers is not a valid JSON";
|
||||
throw new Error("Additional Headers is not a valid JSON");
|
||||
}
|
||||
}
|
||||
|
||||
await axios.post(notification.webhookURL, data, config);
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
|
||||
if (httpMethod === "get") {
|
||||
await axios.get(notification.webhookURL, config);
|
||||
} else {
|
||||
await axios.post(notification.webhookURL, data, config);
|
||||
}
|
||||
|
||||
return okMsg;
|
||||
|
||||
} catch (error) {
|
||||
|
||||
@@ -17,6 +17,7 @@ class WeCom extends NotificationProvider {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
};
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
let body = this.composeMessage(heartbeatJSON, msg);
|
||||
await axios.post(`https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${notification.weComBotKey}`, body, config);
|
||||
return okMsg;
|
||||
|
||||
@@ -11,13 +11,14 @@ class Whapi extends NotificationProvider {
|
||||
const okMsg = "Sent Successfully.";
|
||||
|
||||
try {
|
||||
const config = {
|
||||
let config = {
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer " + notification.whapiAuthToken,
|
||||
}
|
||||
};
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
|
||||
let data = {
|
||||
"to": notification.whapiRecipient,
|
||||
|
||||
@@ -18,7 +18,8 @@ class WPush extends NotificationProvider {
|
||||
"apikey": notification.wpushAPIkey,
|
||||
"channel": notification.wpushChannel
|
||||
};
|
||||
const result = await axios.post("https://api.wpush.cn/api/v1/send", context);
|
||||
let config = this.getAxiosConfigWithProxy({});
|
||||
const result = await axios.post("https://api.wpush.cn/api/v1/send", context, config);
|
||||
if (result.data.code !== 0) {
|
||||
throw result.data.message;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ class YZJ extends NotificationProvider {
|
||||
msg = `${this.statusToString(heartbeatJSON["status"])} ${monitorJSON["name"]} \n> ${heartbeatJSON["msg"]}\n> Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`;
|
||||
}
|
||||
|
||||
const config = {
|
||||
let config = {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
@@ -26,6 +26,7 @@ class YZJ extends NotificationProvider {
|
||||
};
|
||||
// yzjtype=0 => general robot
|
||||
const url = `${notification.yzjWebHookUrl}?yzjtype=0&yzjtoken=${notification.yzjToken}`;
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
|
||||
const result = await axios.post(url, params, config);
|
||||
if (!result.data?.success) {
|
||||
|
||||
@@ -27,7 +27,8 @@ class ZohoCliq extends NotificationProvider {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
_sendNotification = async (webhookUrl, payload) => {
|
||||
await axios.post(webhookUrl, { text: payload.join("\n") });
|
||||
let config = this.getAxiosConfigWithProxy({});
|
||||
await axios.post(webhookUrl, { text: payload.join("\n") }, config);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
+49
-20
@@ -4,6 +4,7 @@ const Alerta = require("./notification-providers/alerta");
|
||||
const AlertNow = require("./notification-providers/alertnow");
|
||||
const AliyunSms = require("./notification-providers/aliyun-sms");
|
||||
const Apprise = require("./notification-providers/apprise");
|
||||
const Bale = require("./notification-providers/bale");
|
||||
const Bark = require("./notification-providers/bark");
|
||||
const Bitrix24 = require("./notification-providers/bitrix24");
|
||||
const ClickSendSMS = require("./notification-providers/clicksendsms");
|
||||
@@ -13,6 +14,7 @@ const DingDing = require("./notification-providers/dingding");
|
||||
const Discord = require("./notification-providers/discord");
|
||||
const Elks = require("./notification-providers/46elks");
|
||||
const Feishu = require("./notification-providers/feishu");
|
||||
const Notifery = require("./notification-providers/notifery");
|
||||
const FreeMobile = require("./notification-providers/freemobile");
|
||||
const GoogleChat = require("./notification-providers/google-chat");
|
||||
const Gorush = require("./notification-providers/gorush");
|
||||
@@ -27,12 +29,15 @@ const LineNotify = require("./notification-providers/linenotify");
|
||||
const LunaSea = require("./notification-providers/lunasea");
|
||||
const Matrix = require("./notification-providers/matrix");
|
||||
const Mattermost = require("./notification-providers/mattermost");
|
||||
const NextcloudTalk = require("./notification-providers/nextcloudtalk");
|
||||
const Nostr = require("./notification-providers/nostr");
|
||||
const Ntfy = require("./notification-providers/ntfy");
|
||||
const Octopush = require("./notification-providers/octopush");
|
||||
const OneChat = require("./notification-providers/onechat");
|
||||
const OneBot = require("./notification-providers/onebot");
|
||||
const Opsgenie = require("./notification-providers/opsgenie");
|
||||
const PagerDuty = require("./notification-providers/pagerduty");
|
||||
const Pumble = require("./notification-providers/pumble");
|
||||
const FlashDuty = require("./notification-providers/flashduty");
|
||||
const PagerTree = require("./notification-providers/pagertree");
|
||||
const PromoSMS = require("./notification-providers/promosms");
|
||||
@@ -66,15 +71,21 @@ const ZohoCliq = require("./notification-providers/zoho-cliq");
|
||||
const SevenIO = require("./notification-providers/sevenio");
|
||||
const Whapi = require("./notification-providers/whapi");
|
||||
const WAHA = require("./notification-providers/waha");
|
||||
const Evolution = require("./notification-providers/evolution");
|
||||
const GtxMessaging = require("./notification-providers/gtx-messaging");
|
||||
const Cellsynt = require("./notification-providers/cellsynt");
|
||||
const Onesender = require("./notification-providers/onesender");
|
||||
const Wpush = require("./notification-providers/wpush");
|
||||
const SendGrid = require("./notification-providers/send-grid");
|
||||
const Brevo = require("./notification-providers/brevo");
|
||||
const YZJ = require("./notification-providers/yzj");
|
||||
const SMSPlanet = require("./notification-providers/sms-planet");
|
||||
const SpugPush = require("./notification-providers/spugpush");
|
||||
const SMSIR = require("./notification-providers/smsir");
|
||||
const { commandExists } = require("./util-server");
|
||||
const Webpush = require("./notification-providers/Webpush");
|
||||
|
||||
class Notification {
|
||||
|
||||
providerList = {};
|
||||
|
||||
/**
|
||||
@@ -93,6 +104,7 @@ class Notification {
|
||||
new AlertNow(),
|
||||
new AliyunSms(),
|
||||
new Apprise(),
|
||||
new Bale(),
|
||||
new Bark(),
|
||||
new Bitrix24(),
|
||||
new ClickSendSMS(),
|
||||
@@ -116,9 +128,11 @@ class Notification {
|
||||
new LunaSea(),
|
||||
new Matrix(),
|
||||
new Mattermost(),
|
||||
new NextcloudTalk(),
|
||||
new Nostr(),
|
||||
new Ntfy(),
|
||||
new Octopush(),
|
||||
new OneChat(),
|
||||
new OneBot(),
|
||||
new Onesender(),
|
||||
new Opsgenie(),
|
||||
@@ -126,6 +140,7 @@ class Notification {
|
||||
new FlashDuty(),
|
||||
new PagerTree(),
|
||||
new PromoSMS(),
|
||||
new Pumble(),
|
||||
new Pushbullet(),
|
||||
new PushDeer(),
|
||||
new Pushover(),
|
||||
@@ -156,14 +171,21 @@ class Notification {
|
||||
new SevenIO(),
|
||||
new Whapi(),
|
||||
new WAHA(),
|
||||
new Evolution(),
|
||||
new GtxMessaging(),
|
||||
new Cellsynt(),
|
||||
new Wpush(),
|
||||
new Brevo(),
|
||||
new YZJ(),
|
||||
new SMSPlanet(),
|
||||
new SpugPush(),
|
||||
new Notifery(),
|
||||
new SMSIR(),
|
||||
new SendGrid(),
|
||||
new YZJ()
|
||||
new Webpush(),
|
||||
];
|
||||
for (let item of list) {
|
||||
if (! item.name) {
|
||||
if (!item.name) {
|
||||
throw new Error("Notification provider without name");
|
||||
}
|
||||
|
||||
@@ -183,9 +205,19 @@ class Notification {
|
||||
* @returns {Promise<string>} Successful msg
|
||||
* @throws Error with fail msg
|
||||
*/
|
||||
static async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
static async send(
|
||||
notification,
|
||||
msg,
|
||||
monitorJSON = null,
|
||||
heartbeatJSON = null
|
||||
) {
|
||||
if (this.providerList[notification.type]) {
|
||||
return this.providerList[notification.type].send(notification, msg, monitorJSON, heartbeatJSON);
|
||||
return this.providerList[notification.type].send(
|
||||
notification,
|
||||
msg,
|
||||
monitorJSON,
|
||||
heartbeatJSON
|
||||
);
|
||||
} else {
|
||||
throw new Error("Notification type is not supported");
|
||||
}
|
||||
@@ -207,10 +239,9 @@ class Notification {
|
||||
userID,
|
||||
]);
|
||||
|
||||
if (! bean) {
|
||||
if (!bean) {
|
||||
throw new Error("notification not found");
|
||||
}
|
||||
|
||||
} else {
|
||||
bean = R.dispense("notification");
|
||||
}
|
||||
@@ -240,7 +271,7 @@ class Notification {
|
||||
userID,
|
||||
]);
|
||||
|
||||
if (! bean) {
|
||||
if (!bean) {
|
||||
throw new Error("notification not found");
|
||||
}
|
||||
|
||||
@@ -249,14 +280,11 @@ class Notification {
|
||||
|
||||
/**
|
||||
* Check if apprise exists
|
||||
* @returns {boolean} Does the command apprise exist?
|
||||
* @returns {Promise<boolean>} Does the command apprise exist?
|
||||
*/
|
||||
static checkApprise() {
|
||||
let commandExistsSync = require("command-exists").sync;
|
||||
let exists = commandExistsSync("apprise");
|
||||
return exists;
|
||||
static async checkApprise() {
|
||||
return await commandExists("apprise");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -267,16 +295,17 @@ class Notification {
|
||||
*/
|
||||
async function applyNotificationEveryMonitor(notificationID, userID) {
|
||||
let monitors = await R.getAll("SELECT id FROM monitor WHERE user_id = ?", [
|
||||
userID
|
||||
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,
|
||||
]);
|
||||
let checkNotification = await R.findOne(
|
||||
"monitor_notification",
|
||||
" monitor_id = ? AND notification_id = ? ",
|
||||
[ monitors[i].id, notificationID ]
|
||||
);
|
||||
|
||||
if (! checkNotification) {
|
||||
if (!checkNotification) {
|
||||
let relation = R.dispense("monitor_notification");
|
||||
relation.monitor_id = monitors[i].id;
|
||||
relation.notification_id = notificationID;
|
||||
|
||||
@@ -5,10 +5,10 @@ const saltRounds = 10;
|
||||
/**
|
||||
* Hash a password
|
||||
* @param {string} password Password to hash
|
||||
* @returns {string} Hash
|
||||
* @returns {Promise<string>} Hash
|
||||
*/
|
||||
exports.generate = function (password) {
|
||||
return bcrypt.hashSync(password, saltRounds);
|
||||
return bcrypt.hash(password, saltRounds);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user