mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-08-08 06:29:57 +08:00
Merge branch 'louislam:master' into feat/add-auth-header-to-webhook-notification-#1919
This commit is contained in:
@@ -4,7 +4,8 @@
|
||||
const { TimeLogger } = require("../src/util");
|
||||
const { R } = require("redbean-node");
|
||||
const { UptimeKumaServer } = require("./uptime-kuma-server");
|
||||
const io = UptimeKumaServer.getInstance().io;
|
||||
const server = UptimeKumaServer.getInstance();
|
||||
const io = server.io;
|
||||
const { setting } = require("./util-server");
|
||||
const checkVersion = require("./check-version");
|
||||
|
||||
@@ -121,7 +122,9 @@ async function sendInfo(socket) {
|
||||
socket.emit("info", {
|
||||
version: checkVersion.version,
|
||||
latestVersion: checkVersion.latestVersion,
|
||||
primaryBaseURL: await setting("primaryBaseURL")
|
||||
primaryBaseURL: await setting("primaryBaseURL"),
|
||||
serverTimezone: await server.getTimezone(),
|
||||
serverTimezoneOffset: server.getTimezoneOffset(),
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -62,8 +62,10 @@ class Database {
|
||||
"patch-add-clickable-status-page-link.sql": true,
|
||||
"patch-add-sqlserver-monitor.sql": true,
|
||||
"patch-add-other-auth.sql": { parents: [ "patch-monitor-basic-auth.sql" ] },
|
||||
"patch-grpc-monitor.sql": true,
|
||||
"patch-add-radius-monitor.sql": true,
|
||||
"patch-monitor-add-resend-interval.sql": true,
|
||||
"patch-maintenance-table2.sql": true,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@@ -1,8 +1,3 @@
|
||||
const dayjs = require("dayjs");
|
||||
const utc = require("dayjs/plugin/utc");
|
||||
let timezone = require("dayjs/plugin/timezone");
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||
|
||||
/**
|
||||
@@ -10,6 +5,7 @@ const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||
* 0 = DOWN
|
||||
* 1 = UP
|
||||
* 2 = PENDING
|
||||
* 3 = MAINTENANCE
|
||||
*/
|
||||
class Heartbeat extends BeanModel {
|
||||
|
||||
|
215
server/model/maintenance.js
Normal file
215
server/model/maintenance.js
Normal file
@@ -0,0 +1,215 @@
|
||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||
const { parseTimeObject, parseTimeFromTimeObject, utcToLocal, localToUTC, log } = require("../../src/util");
|
||||
const { timeObjectToUTC, timeObjectToLocal } = require("../util-server");
|
||||
const { R } = require("redbean-node");
|
||||
const dayjs = require("dayjs");
|
||||
|
||||
class Maintenance extends BeanModel {
|
||||
|
||||
/**
|
||||
* Return an object that ready to parse to JSON for public
|
||||
* Only show necessary data to public
|
||||
* @returns {Object}
|
||||
*/
|
||||
async toPublicJSON() {
|
||||
|
||||
let dateRange = [];
|
||||
if (this.start_date) {
|
||||
dateRange.push(utcToLocal(this.start_date));
|
||||
if (this.end_date) {
|
||||
dateRange.push(utcToLocal(this.end_date));
|
||||
}
|
||||
}
|
||||
|
||||
let timeRange = [];
|
||||
let startTime = timeObjectToLocal(parseTimeObject(this.start_time));
|
||||
timeRange.push(startTime);
|
||||
let endTime = timeObjectToLocal(parseTimeObject(this.end_time));
|
||||
timeRange.push(endTime);
|
||||
|
||||
let obj = {
|
||||
id: this.id,
|
||||
title: this.title,
|
||||
description: this.description,
|
||||
strategy: this.strategy,
|
||||
intervalDay: this.interval_day,
|
||||
active: !!this.active,
|
||||
dateRange: dateRange,
|
||||
timeRange: timeRange,
|
||||
weekdays: (this.weekdays) ? JSON.parse(this.weekdays) : [],
|
||||
daysOfMonth: (this.days_of_month) ? JSON.parse(this.days_of_month) : [],
|
||||
timeslotList: [],
|
||||
};
|
||||
|
||||
const timeslotList = await this.getTimeslotList();
|
||||
|
||||
for (let timeslot of timeslotList) {
|
||||
obj.timeslotList.push(await timeslot.toPublicJSON());
|
||||
}
|
||||
|
||||
if (!Array.isArray(obj.weekdays)) {
|
||||
obj.weekdays = [];
|
||||
}
|
||||
|
||||
if (!Array.isArray(obj.daysOfMonth)) {
|
||||
obj.daysOfMonth = [];
|
||||
}
|
||||
|
||||
// Maintenance Status
|
||||
if (!obj.active) {
|
||||
obj.status = "inactive";
|
||||
} else if (obj.strategy === "manual") {
|
||||
obj.status = "under-maintenance";
|
||||
} else if (obj.timeslotList.length > 0) {
|
||||
let currentTimestamp = dayjs().unix();
|
||||
|
||||
for (let timeslot of obj.timeslotList) {
|
||||
if (dayjs.utc(timeslot.startDate).unix() <= currentTimestamp && dayjs.utc(timeslot.endDate).unix() >= currentTimestamp) {
|
||||
log.debug("timeslot", "Timeslot ID: " + timeslot.id);
|
||||
log.debug("timeslot", "currentTimestamp:" + currentTimestamp);
|
||||
log.debug("timeslot", "timeslot.start_date:" + dayjs.utc(timeslot.startDate).unix());
|
||||
log.debug("timeslot", "timeslot.end_date:" + dayjs.utc(timeslot.endDate).unix());
|
||||
|
||||
obj.status = "under-maintenance";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!obj.status) {
|
||||
obj.status = "scheduled";
|
||||
}
|
||||
} else if (obj.timeslotList.length === 0) {
|
||||
obj.status = "ended";
|
||||
} else {
|
||||
obj.status = "unknown";
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only get future or current timeslots only
|
||||
* @returns {Promise<[]>}
|
||||
*/
|
||||
async getTimeslotList() {
|
||||
return R.convertToBeans("maintenance_timeslot", await R.getAll(`
|
||||
SELECT maintenance_timeslot.*
|
||||
FROM maintenance_timeslot, maintenance
|
||||
WHERE maintenance_timeslot.maintenance_id = maintenance.id
|
||||
AND maintenance.id = ?
|
||||
AND ${Maintenance.getActiveAndFutureMaintenanceSQLCondition()}
|
||||
`, [
|
||||
this.id
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an object that ready to parse to JSON
|
||||
* @param {string} timezone If not specified, the timeRange will be in UTC
|
||||
* @returns {Object}
|
||||
*/
|
||||
async toJSON(timezone = null) {
|
||||
return this.toPublicJSON(timezone);
|
||||
}
|
||||
|
||||
getDayOfWeekList() {
|
||||
log.debug("timeslot", "List: " + this.weekdays);
|
||||
return JSON.parse(this.weekdays).sort(function (a, b) {
|
||||
return a - b;
|
||||
});
|
||||
}
|
||||
|
||||
getDayOfMonthList() {
|
||||
return JSON.parse(this.days_of_month).sort(function (a, b) {
|
||||
return a - b;
|
||||
});
|
||||
}
|
||||
|
||||
getStartDateTime() {
|
||||
let startOfTheDay = dayjs.utc(this.start_date).format("HH:mm");
|
||||
log.debug("timeslot", "startOfTheDay: " + startOfTheDay);
|
||||
|
||||
// Start Time
|
||||
let startTimeSecond = dayjs.utc(this.start_time, "HH:mm").diff(dayjs.utc(startOfTheDay, "HH:mm"), "second");
|
||||
log.debug("timeslot", "startTime: " + startTimeSecond);
|
||||
|
||||
// Bake StartDate + StartTime = Start DateTime
|
||||
return dayjs.utc(this.start_date).add(startTimeSecond, "second");
|
||||
}
|
||||
|
||||
getDuration() {
|
||||
let duration = dayjs.utc(this.end_time, "HH:mm").diff(dayjs.utc(this.start_time, "HH:mm"), "second");
|
||||
// Add 24hours if it is across day
|
||||
if (duration < 0) {
|
||||
duration += 24 * 3600;
|
||||
}
|
||||
return duration;
|
||||
}
|
||||
|
||||
static jsonToBean(bean, obj) {
|
||||
if (obj.id) {
|
||||
bean.id = obj.id;
|
||||
}
|
||||
|
||||
// Apply timezone offset to timeRange, as it cannot apply automatically.
|
||||
if (obj.timeRange[0]) {
|
||||
timeObjectToUTC(obj.timeRange[0]);
|
||||
if (obj.timeRange[1]) {
|
||||
timeObjectToUTC(obj.timeRange[1]);
|
||||
}
|
||||
}
|
||||
|
||||
bean.title = obj.title;
|
||||
bean.description = obj.description;
|
||||
bean.strategy = obj.strategy;
|
||||
bean.interval_day = obj.intervalDay;
|
||||
bean.active = obj.active;
|
||||
|
||||
if (obj.dateRange[0]) {
|
||||
bean.start_date = localToUTC(obj.dateRange[0]);
|
||||
|
||||
if (obj.dateRange[1]) {
|
||||
bean.end_date = localToUTC(obj.dateRange[1]);
|
||||
}
|
||||
}
|
||||
|
||||
bean.start_time = parseTimeFromTimeObject(obj.timeRange[0]);
|
||||
bean.end_time = parseTimeFromTimeObject(obj.timeRange[1]);
|
||||
|
||||
bean.weekdays = JSON.stringify(obj.weekdays);
|
||||
bean.days_of_month = JSON.stringify(obj.daysOfMonth);
|
||||
|
||||
return bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL conditions for active maintenance
|
||||
* @returns {string}
|
||||
*/
|
||||
static getActiveMaintenanceSQLCondition() {
|
||||
return `
|
||||
|
||||
(maintenance_timeslot.start_date <= DATETIME('now')
|
||||
AND maintenance_timeslot.end_date >= DATETIME('now')
|
||||
AND maintenance.active = 1)
|
||||
OR
|
||||
(maintenance.strategy = 'manual' AND active = 1)
|
||||
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL conditions for active and future maintenance
|
||||
* @returns {string}
|
||||
*/
|
||||
static getActiveAndFutureMaintenanceSQLCondition() {
|
||||
return `
|
||||
((maintenance_timeslot.end_date >= DATETIME('now')
|
||||
AND maintenance.active = 1)
|
||||
OR
|
||||
(maintenance.strategy = 'manual' AND active = 1))
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Maintenance;
|
189
server/model/maintenance_timeslot.js
Normal file
189
server/model/maintenance_timeslot.js
Normal file
@@ -0,0 +1,189 @@
|
||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||
const { R } = require("redbean-node");
|
||||
const dayjs = require("dayjs");
|
||||
const { log, utcToLocal, SQL_DATETIME_FORMAT_WITHOUT_SECOND, localToUTC } = require("../../src/util");
|
||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||
|
||||
class MaintenanceTimeslot extends BeanModel {
|
||||
|
||||
async toPublicJSON() {
|
||||
const serverTimezoneOffset = UptimeKumaServer.getInstance().getTimezoneOffset();
|
||||
|
||||
const obj = {
|
||||
id: this.id,
|
||||
startDate: this.start_date,
|
||||
endDate: this.end_date,
|
||||
startDateServerTimezone: utcToLocal(this.start_date, SQL_DATETIME_FORMAT_WITHOUT_SECOND),
|
||||
endDateServerTimezone: utcToLocal(this.end_date, SQL_DATETIME_FORMAT_WITHOUT_SECOND),
|
||||
serverTimezoneOffset,
|
||||
};
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
async toJSON() {
|
||||
return await this.toPublicJSON();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Maintenance} maintenance
|
||||
* @param {dayjs} minDate (For recurring type only) Generate a next timeslot from this date.
|
||||
* @param {boolean} removeExist Remove existing timeslot before create
|
||||
* @returns {Promise<MaintenanceTimeslot>}
|
||||
*/
|
||||
static async generateTimeslot(maintenance, minDate = null, removeExist = false) {
|
||||
if (removeExist) {
|
||||
await R.exec("DELETE FROM maintenance_timeslot WHERE maintenance_id = ? ", [
|
||||
maintenance.id
|
||||
]);
|
||||
}
|
||||
|
||||
if (maintenance.strategy === "manual") {
|
||||
log.debug("maintenance", "No need to generate timeslot for manual type");
|
||||
|
||||
} else if (maintenance.strategy === "single") {
|
||||
let bean = R.dispense("maintenance_timeslot");
|
||||
bean.maintenance_id = maintenance.id;
|
||||
bean.start_date = maintenance.start_date;
|
||||
bean.end_date = maintenance.end_date;
|
||||
bean.generated_next = true;
|
||||
return await R.store(bean);
|
||||
|
||||
} else if (maintenance.strategy === "recurring-interval") {
|
||||
// Prevent dead loop, in case interval_day is not set
|
||||
if (!maintenance.interval_day || maintenance.interval_day <= 0) {
|
||||
maintenance.interval_day = 1;
|
||||
}
|
||||
|
||||
return await this.handleRecurringType(maintenance, minDate, (startDateTime) => {
|
||||
return startDateTime.add(maintenance.interval_day, "day");
|
||||
}, () => {
|
||||
return true;
|
||||
});
|
||||
|
||||
} else if (maintenance.strategy === "recurring-weekday") {
|
||||
let dayOfWeekList = maintenance.getDayOfWeekList();
|
||||
log.debug("timeslot", dayOfWeekList);
|
||||
|
||||
if (dayOfWeekList.length <= 0) {
|
||||
log.debug("timeslot", "No weekdays selected?");
|
||||
return null;
|
||||
}
|
||||
|
||||
const isValid = (startDateTime) => {
|
||||
log.debug("timeslot", "nextDateTime: " + startDateTime);
|
||||
|
||||
let day = startDateTime.local().day();
|
||||
log.debug("timeslot", "nextDateTime.day(): " + day);
|
||||
|
||||
return dayOfWeekList.includes(day);
|
||||
};
|
||||
|
||||
return await this.handleRecurringType(maintenance, minDate, (startDateTime) => {
|
||||
while (true) {
|
||||
startDateTime = startDateTime.add(1, "day");
|
||||
|
||||
if (isValid(startDateTime)) {
|
||||
return startDateTime;
|
||||
}
|
||||
}
|
||||
}, isValid);
|
||||
|
||||
} else if (maintenance.strategy === "recurring-day-of-month") {
|
||||
let dayOfMonthList = maintenance.getDayOfMonthList();
|
||||
if (dayOfMonthList.length <= 0) {
|
||||
log.debug("timeslot", "No day selected?");
|
||||
return null;
|
||||
}
|
||||
|
||||
const isValid = (startDateTime) => {
|
||||
let day = parseInt(startDateTime.local().format("D"));
|
||||
|
||||
log.debug("timeslot", "day: " + day);
|
||||
|
||||
// Check 1-31
|
||||
if (dayOfMonthList.includes(day)) {
|
||||
return startDateTime;
|
||||
}
|
||||
|
||||
// Check "lastDay1","lastDay2"...
|
||||
let daysInMonth = startDateTime.daysInMonth();
|
||||
let lastDayList = [];
|
||||
|
||||
// Small first, e.g. 28 > 29 > 30 > 31
|
||||
for (let i = 4; i >= 1; i--) {
|
||||
if (dayOfMonthList.includes("lastDay" + i)) {
|
||||
lastDayList.push(daysInMonth - i + 1);
|
||||
}
|
||||
}
|
||||
log.debug("timeslot", lastDayList);
|
||||
return lastDayList.includes(day);
|
||||
};
|
||||
|
||||
return await this.handleRecurringType(maintenance, minDate, (startDateTime) => {
|
||||
while (true) {
|
||||
startDateTime = startDateTime.add(1, "day");
|
||||
if (isValid(startDateTime)) {
|
||||
return startDateTime;
|
||||
}
|
||||
}
|
||||
}, isValid);
|
||||
} else {
|
||||
throw new Error("Unknown maintenance strategy");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a next timeslot for all recurring types
|
||||
* @param maintenance
|
||||
* @param minDate
|
||||
* @param {function} nextDayCallback The logic how to get the next possible day
|
||||
* @param {function} isValidCallback Check the day whether is matched the current strategy
|
||||
* @returns {Promise<null|MaintenanceTimeslot>}
|
||||
*/
|
||||
static async handleRecurringType(maintenance, minDate, nextDayCallback, isValidCallback) {
|
||||
let bean = R.dispense("maintenance_timeslot");
|
||||
|
||||
let duration = maintenance.getDuration();
|
||||
let startDateTime = maintenance.getStartDateTime();
|
||||
let endDateTime;
|
||||
|
||||
// Keep generating from the first possible date, until it is ok
|
||||
while (true) {
|
||||
log.debug("timeslot", "startDateTime: " + startDateTime.format());
|
||||
|
||||
// Handling out of effective date range
|
||||
if (startDateTime.diff(dayjs.utc(maintenance.end_date)) > 0) {
|
||||
log.debug("timeslot", "Out of effective date range");
|
||||
return null;
|
||||
}
|
||||
|
||||
endDateTime = startDateTime.add(duration, "second");
|
||||
|
||||
// If endDateTime is out of effective date range, use the end datetime from effective date range
|
||||
if (endDateTime.diff(dayjs.utc(maintenance.end_date)) > 0) {
|
||||
endDateTime = dayjs.utc(maintenance.end_date);
|
||||
}
|
||||
|
||||
// If minDate is set, the endDateTime must be bigger than it.
|
||||
// And the endDateTime must be bigger current time
|
||||
// Is valid under current recurring strategy
|
||||
if (
|
||||
(!minDate || endDateTime.diff(minDate) > 0) &&
|
||||
endDateTime.diff(dayjs()) > 0 &&
|
||||
isValidCallback(startDateTime)
|
||||
) {
|
||||
break;
|
||||
}
|
||||
startDateTime = nextDayCallback(startDateTime);
|
||||
}
|
||||
|
||||
bean.maintenance_id = maintenance.id;
|
||||
bean.start_date = localToUTC(startDateTime);
|
||||
bean.end_date = localToUTC(endDateTime);
|
||||
bean.generated_next = false;
|
||||
return await R.store(bean);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MaintenanceTimeslot;
|
@@ -1,13 +1,9 @@
|
||||
const https = require("https");
|
||||
const dayjs = require("dayjs");
|
||||
const utc = require("dayjs/plugin/utc");
|
||||
let timezone = require("dayjs/plugin/timezone");
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
const axios = require("axios");
|
||||
const { Prometheus } = require("../prometheus");
|
||||
const { log, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util");
|
||||
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mqttAsync, setSetting, httpNtlm, radius } = require("../util-server");
|
||||
const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, TimeLogger } = require("../../src/util");
|
||||
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mqttAsync, setSetting, httpNtlm, radius, grpcQuery } = require("../util-server");
|
||||
const { R } = require("redbean-node");
|
||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||
const { Notification } = require("../notification");
|
||||
@@ -18,12 +14,14 @@ const apicache = require("../modules/apicache");
|
||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||
const { CacheableDnsHttpAgent } = require("../cacheable-dns-http-agent");
|
||||
const { DockerHost } = require("../docker");
|
||||
const Maintenance = require("./maintenance");
|
||||
|
||||
/**
|
||||
* status:
|
||||
* 0 = DOWN
|
||||
* 1 = UP
|
||||
* 2 = PENDING
|
||||
* 3 = MAINTENANCE
|
||||
*/
|
||||
class Monitor extends BeanModel {
|
||||
|
||||
@@ -37,6 +35,7 @@ class Monitor extends BeanModel {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
sendUrl: this.sendUrl,
|
||||
maintenance: await Monitor.isUnderMaintenance(this.id),
|
||||
};
|
||||
|
||||
if (this.sendUrl) {
|
||||
@@ -96,6 +95,7 @@ class Monitor extends BeanModel {
|
||||
proxyId: this.proxy_id,
|
||||
notificationIDList,
|
||||
tags: tags,
|
||||
maintenance: await Monitor.isUnderMaintenance(this.id),
|
||||
mqttUsername: this.mqttUsername,
|
||||
mqttPassword: this.mqttPassword,
|
||||
mqttTopic: this.mqttTopic,
|
||||
@@ -105,6 +105,11 @@ class Monitor extends BeanModel {
|
||||
authMethod: this.authMethod,
|
||||
authWorkstation: this.authWorkstation,
|
||||
authDomain: this.authDomain,
|
||||
grpcUrl: this.grpcUrl,
|
||||
grpcProtobuf: this.grpcProtobuf,
|
||||
grpcMethod: this.grpcMethod,
|
||||
grpcServiceName: this.grpcServiceName,
|
||||
grpcEnableTls: this.getGrpcEnableTls(),
|
||||
radiusUsername: this.radiusUsername,
|
||||
radiusPassword: this.radiusPassword,
|
||||
radiusCalledStationId: this.radiusCalledStationId,
|
||||
@@ -117,6 +122,8 @@ class Monitor extends BeanModel {
|
||||
...data,
|
||||
headers: this.headers,
|
||||
body: this.body,
|
||||
grpcBody: this.grpcBody,
|
||||
grpcMetadata: this.grpcMetadata,
|
||||
basic_auth_user: this.basic_auth_user,
|
||||
basic_auth_pass: this.basic_auth_pass,
|
||||
pushToken: this.pushToken,
|
||||
@@ -167,6 +174,14 @@ class Monitor extends BeanModel {
|
||||
return Boolean(this.upsideDown);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse to boolean
|
||||
* @returns {boolean}
|
||||
*/
|
||||
getGrpcEnableTls() {
|
||||
return Boolean(this.grpcEnableTls);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get accepted status codes
|
||||
* @returns {Object}
|
||||
@@ -230,7 +245,10 @@ class Monitor extends BeanModel {
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.type === "http" || this.type === "keyword") {
|
||||
if (await Monitor.isUnderMaintenance(this.id)) {
|
||||
bean.msg = "Monitor under maintenance";
|
||||
bean.status = MAINTENANCE;
|
||||
} else if (this.type === "http" || this.type === "keyword") {
|
||||
// Do not do any queries/high loading things before the "bean.ping"
|
||||
let startTime = dayjs().valueOf();
|
||||
|
||||
@@ -524,6 +542,37 @@ class Monitor extends BeanModel {
|
||||
bean.msg = "";
|
||||
bean.status = UP;
|
||||
bean.ping = dayjs().valueOf() - startTime;
|
||||
} else if (this.type === "grpc-keyword") {
|
||||
let startTime = dayjs().valueOf();
|
||||
const options = {
|
||||
grpcUrl: this.grpcUrl,
|
||||
grpcProtobufData: this.grpcProtobuf,
|
||||
grpcServiceName: this.grpcServiceName,
|
||||
grpcEnableTls: this.grpcEnableTls,
|
||||
grpcMethod: this.grpcMethod,
|
||||
grpcBody: this.grpcBody,
|
||||
keyword: this.keyword
|
||||
};
|
||||
const response = await grpcQuery(options);
|
||||
bean.ping = dayjs().valueOf() - startTime;
|
||||
log.debug("monitor:", `gRPC response: ${JSON.stringify(response)}`);
|
||||
let responseData = response.data;
|
||||
if (responseData.length > 50) {
|
||||
responseData = response.substring(0, 47) + "...";
|
||||
}
|
||||
if (response.code !== 1) {
|
||||
bean.status = DOWN;
|
||||
bean.msg = `Error in send gRPC ${response.code} ${response.errorMessage}`;
|
||||
} else {
|
||||
if (response.data.toString().includes(this.keyword)) {
|
||||
bean.status = UP;
|
||||
bean.msg = `${responseData}, keyword [${this.keyword}] is found`;
|
||||
} else {
|
||||
log.debug("monitor:", `GRPC response [${response.data}] + ", but keyword [${this.keyword}] is not in [" + ${response.data} + "]"`);
|
||||
bean.status = DOWN;
|
||||
bean.msg = `, but keyword [${this.keyword}] is not in [" + ${responseData} + "]`;
|
||||
}
|
||||
}
|
||||
} else if (this.type === "postgres") {
|
||||
let startTime = dayjs().valueOf();
|
||||
|
||||
@@ -534,6 +583,17 @@ class Monitor extends BeanModel {
|
||||
bean.ping = dayjs().valueOf() - startTime;
|
||||
} else if (this.type === "radius") {
|
||||
let startTime = dayjs().valueOf();
|
||||
|
||||
// Handle monitors that were created before the
|
||||
// update and as such don't have a value for
|
||||
// this.port.
|
||||
let port;
|
||||
if (this.port == null) {
|
||||
port = 1812;
|
||||
} else {
|
||||
port = this.port;
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await radius(
|
||||
this.hostname,
|
||||
@@ -541,7 +601,8 @@ class Monitor extends BeanModel {
|
||||
this.radiusPassword,
|
||||
this.radiusCalledStationId,
|
||||
this.radiusCallingStationId,
|
||||
this.radiusSecret
|
||||
this.radiusSecret,
|
||||
port
|
||||
);
|
||||
if (resp.code) {
|
||||
bean.msg = resp.code;
|
||||
@@ -594,8 +655,12 @@ class Monitor extends BeanModel {
|
||||
if (isImportant) {
|
||||
bean.important = true;
|
||||
|
||||
log.debug("monitor", `[${this.name}] sendNotification`);
|
||||
await Monitor.sendNotification(isFirstBeat, this, bean);
|
||||
if (Monitor.isImportantForNotification(isFirstBeat, previousBeat?.status, bean.status)) {
|
||||
log.debug("monitor", `[${this.name}] sendNotification`);
|
||||
await Monitor.sendNotification(isFirstBeat, this, bean);
|
||||
} else {
|
||||
log.debug("monitor", `[${this.name}] will not sendNotification because it is (or was) under maintenance`);
|
||||
}
|
||||
|
||||
// Reset down count
|
||||
bean.downCount = 0;
|
||||
@@ -604,6 +669,8 @@ class Monitor extends BeanModel {
|
||||
log.debug("monitor", `[${this.name}] apicache clear`);
|
||||
apicache.clear();
|
||||
|
||||
UptimeKumaServer.getInstance().sendMaintenanceListByUserID(this.user_id);
|
||||
|
||||
} else {
|
||||
bean.important = false;
|
||||
|
||||
@@ -627,6 +694,8 @@ class Monitor extends BeanModel {
|
||||
beatInterval = this.retryInterval;
|
||||
}
|
||||
log.warn("monitor", `Monitor #${this.id} '${this.name}': Pending: ${bean.msg} | Max retries: ${this.maxretries} | Retry: ${retries} | Retry Interval: ${beatInterval} seconds | Type: ${this.type}`);
|
||||
} else if (bean.status === MAINTENANCE) {
|
||||
log.warn("monitor", `Monitor #${this.id} '${this.name}': Under Maintenance | Type: ${this.type}`);
|
||||
} else {
|
||||
log.warn("monitor", `Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Interval: ${beatInterval} seconds | Type: ${this.type} | Down Count: ${bean.downCount} | Resend Interval: ${this.resendInterval}`);
|
||||
}
|
||||
@@ -837,7 +906,7 @@ class Monitor extends BeanModel {
|
||||
-- SUM all uptime duration, also trim off the beat out of time window
|
||||
SUM(
|
||||
CASE
|
||||
WHEN (status = 1)
|
||||
WHEN (status = 1 OR status = 3)
|
||||
THEN
|
||||
CASE
|
||||
WHEN (JULIANDAY(\`time\`) - JULIANDAY(?)) * 86400 < duration
|
||||
@@ -908,11 +977,49 @@ class Monitor extends BeanModel {
|
||||
// DOWN -> PENDING = this case not exists
|
||||
// DOWN -> DOWN = not important
|
||||
// * DOWN -> UP = important
|
||||
let isImportant = isFirstBeat ||
|
||||
// MAINTENANCE -> MAINTENANCE = not important
|
||||
// * MAINTENANCE -> UP = important
|
||||
// * MAINTENANCE -> DOWN = important
|
||||
// * DOWN -> MAINTENANCE = important
|
||||
// * UP -> MAINTENANCE = important
|
||||
return isFirstBeat ||
|
||||
(previousBeatStatus === DOWN && currentBeatStatus === MAINTENANCE) ||
|
||||
(previousBeatStatus === UP && currentBeatStatus === MAINTENANCE) ||
|
||||
(previousBeatStatus === MAINTENANCE && currentBeatStatus === DOWN) ||
|
||||
(previousBeatStatus === MAINTENANCE && currentBeatStatus === UP) ||
|
||||
(previousBeatStatus === UP && currentBeatStatus === DOWN) ||
|
||||
(previousBeatStatus === DOWN && currentBeatStatus === UP) ||
|
||||
(previousBeatStatus === PENDING && currentBeatStatus === DOWN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this beat important for notifications?
|
||||
* @param {boolean} isFirstBeat Is this the first beat of this monitor?
|
||||
* @param {const} previousBeatStatus Status of the previous beat
|
||||
* @param {const} currentBeatStatus Status of the current beat
|
||||
* @returns {boolean} True if is an important beat else false
|
||||
*/
|
||||
static isImportantForNotification(isFirstBeat, previousBeatStatus, currentBeatStatus) {
|
||||
// * ? -> ANY STATUS = important [isFirstBeat]
|
||||
// UP -> PENDING = not important
|
||||
// * UP -> DOWN = important
|
||||
// UP -> UP = not important
|
||||
// PENDING -> PENDING = not important
|
||||
// * PENDING -> DOWN = important
|
||||
// PENDING -> UP = not important
|
||||
// DOWN -> PENDING = this case not exists
|
||||
// DOWN -> DOWN = not important
|
||||
// * DOWN -> UP = important
|
||||
// MAINTENANCE -> MAINTENANCE = not important
|
||||
// MAINTENANCE -> UP = not important
|
||||
// * MAINTENANCE -> DOWN = important
|
||||
// DOWN -> MAINTENANCE = not important
|
||||
// UP -> MAINTENANCE = not important
|
||||
return isFirstBeat ||
|
||||
(previousBeatStatus === MAINTENANCE && currentBeatStatus === DOWN) ||
|
||||
(previousBeatStatus === UP && currentBeatStatus === DOWN) ||
|
||||
(previousBeatStatus === DOWN && currentBeatStatus === UP) ||
|
||||
(previousBeatStatus === PENDING && currentBeatStatus === DOWN);
|
||||
return isImportant;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1049,6 +1156,26 @@ class Monitor extends BeanModel {
|
||||
monitorID
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if monitor is under maintenance
|
||||
* @param {number} monitorID ID of monitor to check
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
static async isUnderMaintenance(monitorID) {
|
||||
let activeCondition = Maintenance.getActiveMaintenanceSQLCondition();
|
||||
const maintenance = await R.getRow(`
|
||||
SELECT COUNT(*) AS count
|
||||
FROM monitor_maintenance mm
|
||||
JOIN maintenance
|
||||
ON mm.maintenance_id = maintenance.id
|
||||
AND mm.monitor_id = ?
|
||||
LEFT JOIN maintenance_timeslot
|
||||
ON maintenance_timeslot.maintenance_id = maintenance.id
|
||||
WHERE ${activeCondition}
|
||||
LIMIT 1`, [ monitorID ]);
|
||||
return maintenance.count !== 0;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Monitor;
|
||||
|
@@ -2,6 +2,8 @@ const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||
const { R } = require("redbean-node");
|
||||
const cheerio = require("cheerio");
|
||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||
const jsesc = require("jsesc");
|
||||
const Maintenance = require("./maintenance");
|
||||
|
||||
class StatusPage extends BeanModel {
|
||||
|
||||
@@ -36,7 +38,7 @@ class StatusPage extends BeanModel {
|
||||
*/
|
||||
static async renderHTML(indexHTML, statusPage) {
|
||||
const $ = cheerio.load(indexHTML);
|
||||
const description155 = statusPage.description?.substring(0, 155);
|
||||
const description155 = statusPage.description?.substring(0, 155) ?? "";
|
||||
|
||||
$("title").text(statusPage.title);
|
||||
$("meta[name=description]").attr("content", description155);
|
||||
@@ -56,13 +58,19 @@ class StatusPage extends BeanModel {
|
||||
head.append(`<meta property="og:description" content="${description155}" />`);
|
||||
|
||||
// Preload data
|
||||
const json = JSON.stringify(await StatusPage.getStatusPageData(statusPage));
|
||||
head.append(`
|
||||
<script>
|
||||
window.preloadData = ${json}
|
||||
// Add jsesc, fix https://github.com/louislam/uptime-kuma/issues/2186
|
||||
const escapedJSONObject = jsesc(await StatusPage.getStatusPageData(statusPage), {
|
||||
"isScriptContext": true
|
||||
});
|
||||
|
||||
const script = $(`
|
||||
<script id="preload-data" data-json="{}">
|
||||
window.preloadData = ${escapedJSONObject};
|
||||
</script>
|
||||
`);
|
||||
|
||||
head.append(script);
|
||||
|
||||
// manifest.json
|
||||
$("link[rel=manifest]").attr("href", `/api/status-page/${statusPage.slug}/manifest.json`);
|
||||
|
||||
@@ -83,6 +91,8 @@ class StatusPage extends BeanModel {
|
||||
incident = incident.toPublicJSON();
|
||||
}
|
||||
|
||||
let maintenanceList = await StatusPage.getMaintenanceList(statusPage.id);
|
||||
|
||||
// Public Group List
|
||||
const publicGroupList = [];
|
||||
const showTags = !!statusPage.show_tags;
|
||||
@@ -100,7 +110,8 @@ class StatusPage extends BeanModel {
|
||||
return {
|
||||
config: await statusPage.toPublicJSON(),
|
||||
incident,
|
||||
publicGroupList
|
||||
publicGroupList,
|
||||
maintenanceList,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -259,6 +270,36 @@ class StatusPage extends BeanModel {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of maintenances
|
||||
* @param {number} statusPageId ID of status page to get maintenance for
|
||||
* @returns {Object} Object representing maintenances sanitized for public
|
||||
*/
|
||||
static async getMaintenanceList(statusPageId) {
|
||||
try {
|
||||
const publicMaintenanceList = [];
|
||||
|
||||
let activeCondition = Maintenance.getActiveMaintenanceSQLCondition();
|
||||
let maintenanceBeanList = R.convertToBeans("maintenance", await R.getAll(`
|
||||
SELECT maintenance.*
|
||||
FROM maintenance, maintenance_status_page msp, maintenance_timeslot
|
||||
WHERE msp.maintenance_id = maintenance.id
|
||||
AND maintenance_timeslot.maintenance_id = maintenance.id
|
||||
AND msp.status_page_id = ?
|
||||
AND ${activeCondition}
|
||||
ORDER BY maintenance.end_date
|
||||
`, [ statusPageId ]));
|
||||
|
||||
for (const bean of maintenanceBeanList) {
|
||||
publicMaintenanceList.push(await bean.toPublicJSON());
|
||||
}
|
||||
|
||||
return publicMaintenanceList;
|
||||
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = StatusPage;
|
||||
|
@@ -9,7 +9,7 @@ class Ntfy extends NotificationProvider {
|
||||
let okMsg = "Sent Successfully.";
|
||||
try {
|
||||
let headers = {};
|
||||
if (notification.ntfyusername.length > 0) {
|
||||
if (notification.ntfyusername) {
|
||||
headers = {
|
||||
"Authorization": "Basic " + Buffer.from(notification.ntfyusername + ":" + notification.ntfypassword).toString("base64"),
|
||||
};
|
||||
@@ -20,6 +20,11 @@ class Ntfy extends NotificationProvider {
|
||||
"priority": notification.ntfyPriority || 4,
|
||||
"title": "Uptime-Kuma",
|
||||
};
|
||||
|
||||
if (notification.ntfyIcon) {
|
||||
data.icon = notification.ntfyIcon;
|
||||
}
|
||||
|
||||
await axios.post(`${notification.ntfyserverurl}`, data, { headers: headers });
|
||||
|
||||
return okMsg;
|
||||
|
@@ -19,26 +19,26 @@ class Pushbullet extends NotificationProvider {
|
||||
}
|
||||
};
|
||||
if (heartbeatJSON == null) {
|
||||
let testdata = {
|
||||
let data = {
|
||||
"type": "note",
|
||||
"title": "Uptime Kuma Alert",
|
||||
"body": "Testing Successful.",
|
||||
"body": msg,
|
||||
};
|
||||
await axios.post(pushbulletUrl, testdata, config);
|
||||
await axios.post(pushbulletUrl, data, config);
|
||||
} else if (heartbeatJSON["status"] === DOWN) {
|
||||
let downdata = {
|
||||
let downData = {
|
||||
"type": "note",
|
||||
"title": "UptimeKuma Alert: " + monitorJSON["name"],
|
||||
"body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"],
|
||||
};
|
||||
await axios.post(pushbulletUrl, downdata, config);
|
||||
await axios.post(pushbulletUrl, downData, config);
|
||||
} else if (heartbeatJSON["status"] === UP) {
|
||||
let updata = {
|
||||
let upData = {
|
||||
"type": "note",
|
||||
"title": "UptimeKuma Alert: " + monitorJSON["name"],
|
||||
"body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"],
|
||||
};
|
||||
await axios.post(pushbulletUrl, updata, config);
|
||||
await axios.post(pushbulletUrl, upData, config);
|
||||
}
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
|
71
server/notification-providers/smseagle.js
Normal file
71
server/notification-providers/smseagle.js
Normal file
@@ -0,0 +1,71 @@
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
|
||||
class SMSEagle extends NotificationProvider {
|
||||
|
||||
name = "SMSEagle";
|
||||
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
let okMsg = "Sent Successfully.";
|
||||
|
||||
try {
|
||||
let config = {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
};
|
||||
|
||||
let postData;
|
||||
let sendMethod;
|
||||
let recipientType;
|
||||
|
||||
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)}`;
|
||||
} else {
|
||||
error = "SMSEagle API returned an unexpected response";
|
||||
}
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SMSEagle;
|
@@ -32,6 +32,7 @@ const RocketChat = require("./notification-providers/rocket-chat");
|
||||
const SerwerSMS = require("./notification-providers/serwersms");
|
||||
const Signal = require("./notification-providers/signal");
|
||||
const Slack = require("./notification-providers/slack");
|
||||
const SMSEagle = require("./notification-providers/smseagle");
|
||||
const SMTP = require("./notification-providers/smtp");
|
||||
const Squadcast = require("./notification-providers/squadcast");
|
||||
const Stackfield = require("./notification-providers/stackfield");
|
||||
@@ -89,6 +90,7 @@ class Notification {
|
||||
new Signal(),
|
||||
new SMSManager(),
|
||||
new Slack(),
|
||||
new SMSEagle(),
|
||||
new SMTP(),
|
||||
new Squadcast(),
|
||||
new Stackfield(),
|
||||
|
@@ -4,7 +4,7 @@ const { R } = require("redbean-node");
|
||||
const apicache = require("../modules/apicache");
|
||||
const Monitor = require("../model/monitor");
|
||||
const dayjs = require("dayjs");
|
||||
const { UP, DOWN, flipStatus, log } = require("../../src/util");
|
||||
const { UP, MAINTENANCE, DOWN, flipStatus, log } = require("../../src/util");
|
||||
const StatusPage = require("../model/status_page");
|
||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||
const { makeBadge } = require("badge-maker");
|
||||
@@ -67,6 +67,11 @@ router.get("/api/push/:pushToken", async (request, response) => {
|
||||
duration = dayjs(bean.time).diff(dayjs(previousHeartbeat.time), "second");
|
||||
}
|
||||
|
||||
if (await Monitor.isUnderMaintenance(monitor.id)) {
|
||||
msg = "Monitor under maintenance";
|
||||
status = MAINTENANCE;
|
||||
}
|
||||
|
||||
log.debug("router", `/api/push/ called at ${dayjs().format("YYYY-MM-DD HH:mm:ss.SSS")}`);
|
||||
log.debug("router", "PreviousStatus: " + previousStatus);
|
||||
log.debug("router", "Current Status: " + status);
|
||||
@@ -87,7 +92,7 @@ router.get("/api/push/:pushToken", async (request, response) => {
|
||||
ok: true,
|
||||
});
|
||||
|
||||
if (bean.important) {
|
||||
if (Monitor.isImportantForNotification(isFirstBeat, previousStatus, status)) {
|
||||
await Monitor.sendNotification(isFirstBeat, monitor, bean);
|
||||
}
|
||||
|
||||
|
@@ -5,6 +5,12 @@
|
||||
*/
|
||||
console.log("Welcome to Uptime Kuma");
|
||||
|
||||
// As the log function need to use dayjs, it should be very top
|
||||
const dayjs = require("dayjs");
|
||||
dayjs.extend(require("dayjs/plugin/utc"));
|
||||
dayjs.extend(require("dayjs/plugin/timezone"));
|
||||
dayjs.extend(require("dayjs/plugin/customParseFormat"));
|
||||
|
||||
// Check Node.js Version
|
||||
const nodeVersion = parseInt(process.versions.node.split(".")[0]);
|
||||
const requiredVersion = 14;
|
||||
@@ -33,6 +39,7 @@ log.info("server", "Importing Node libraries");
|
||||
const fs = require("fs");
|
||||
|
||||
log.info("server", "Importing 3rd-party libraries");
|
||||
|
||||
log.debug("server", "Importing express");
|
||||
const express = require("express");
|
||||
const expressStaticGzip = require("express-static-gzip");
|
||||
@@ -127,6 +134,8 @@ const StatusPage = require("./model/status_page");
|
||||
const { cloudflaredSocketHandler, autoStart: cloudflaredAutoStart, stop: cloudflaredStop } = require("./socket-handlers/cloudflared-socket-handler");
|
||||
const { proxySocketHandler } = require("./socket-handlers/proxy-socket-handler");
|
||||
const { dockerSocketHandler } = require("./socket-handlers/docker-socket-handler");
|
||||
const { maintenanceSocketHandler } = require("./socket-handlers/maintenance-socket-handler");
|
||||
const { Settings } = require("./settings");
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
@@ -154,8 +163,9 @@ let needSetup = false;
|
||||
(async () => {
|
||||
Database.init(args);
|
||||
await initDatabase(testMode);
|
||||
await server.initAfterDatabaseReady();
|
||||
|
||||
exports.entryPage = await setting("entryPage");
|
||||
server.entryPage = await Settings.get("entryPage");
|
||||
await StatusPage.loadDomainMappingList();
|
||||
|
||||
log.info("server", "Adding route");
|
||||
@@ -176,14 +186,15 @@ let needSetup = false;
|
||||
|
||||
log.debug("entry", `Request Domain: ${hostname}`);
|
||||
|
||||
const uptimeKumaEntryPage = server.entryPage;
|
||||
if (hostname in StatusPage.domainMappingList) {
|
||||
log.debug("entry", "This is a status page domain");
|
||||
|
||||
let slug = StatusPage.domainMappingList[hostname];
|
||||
await StatusPage.handleStatusPageResponse(response, server.indexHTML, slug);
|
||||
|
||||
} else if (exports.entryPage && exports.entryPage.startsWith("statusPage-")) {
|
||||
response.redirect("/status/" + exports.entryPage.replace("statusPage-", ""));
|
||||
} else if (uptimeKumaEntryPage && uptimeKumaEntryPage.startsWith("statusPage-")) {
|
||||
response.redirect("/status/" + uptimeKumaEntryPage.replace("statusPage-", ""));
|
||||
|
||||
} else {
|
||||
response.redirect("/dashboard");
|
||||
@@ -200,7 +211,7 @@ let needSetup = false;
|
||||
// Robots.txt
|
||||
app.get("/robots.txt", async (_request, response) => {
|
||||
let txt = "User-agent: *\nDisallow:";
|
||||
if (! await setting("searchEngineIndex")) {
|
||||
if (!await setting("searchEngineIndex")) {
|
||||
txt += " /";
|
||||
}
|
||||
response.setHeader("Content-Type", "text/plain");
|
||||
@@ -695,6 +706,12 @@ let needSetup = false;
|
||||
bean.authMethod = monitor.authMethod;
|
||||
bean.authWorkstation = monitor.authWorkstation;
|
||||
bean.authDomain = monitor.authDomain;
|
||||
bean.grpcUrl = monitor.grpcUrl;
|
||||
bean.grpcProtobuf = monitor.grpcProtobuf;
|
||||
bean.grpcMethod = monitor.grpcMethod;
|
||||
bean.grpcBody = monitor.grpcBody;
|
||||
bean.grpcMetadata = monitor.grpcMetadata;
|
||||
bean.grpcEnableTls = monitor.grpcEnableTls;
|
||||
bean.radiusUsername = monitor.radiusUsername;
|
||||
bean.radiusPassword = monitor.radiusPassword;
|
||||
bean.radiusCalledStationId = monitor.radiusCalledStationId;
|
||||
@@ -1055,10 +1072,15 @@ let needSetup = false;
|
||||
socket.on("getSettings", async (callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
const data = await getSettings("general");
|
||||
|
||||
if (!data.serverTimezone) {
|
||||
data.serverTimezone = await server.getTimezone();
|
||||
}
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
data: await getSettings("general"),
|
||||
data: data,
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
@@ -1084,7 +1106,12 @@ let needSetup = false;
|
||||
}
|
||||
|
||||
await setSettings("general", data);
|
||||
exports.entryPage = data.entryPage;
|
||||
server.entryPage = data.entryPage;
|
||||
|
||||
// Also need to apply timezone globally
|
||||
if (data.serverTimezone) {
|
||||
await server.setTimezone(data.serverTimezone);
|
||||
}
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
@@ -1092,6 +1119,7 @@ let needSetup = false;
|
||||
});
|
||||
|
||||
sendInfo(socket);
|
||||
server.sendMaintenanceList(socket);
|
||||
|
||||
} catch (e) {
|
||||
callback({
|
||||
@@ -1450,6 +1478,7 @@ let needSetup = false;
|
||||
databaseSocketHandler(socket);
|
||||
proxySocketHandler(socket);
|
||||
dockerSocketHandler(socket);
|
||||
maintenanceSocketHandler(socket);
|
||||
|
||||
log.debug("server", "added all socket handlers");
|
||||
|
||||
@@ -1552,6 +1581,7 @@ async function afterLogin(socket, user) {
|
||||
socket.join(user.id);
|
||||
|
||||
let monitorList = await server.sendMonitorList(socket);
|
||||
server.sendMaintenanceList(socket);
|
||||
sendNotificationList(socket);
|
||||
sendProxyList(socket);
|
||||
sendDockerHostList(socket);
|
||||
@@ -1697,6 +1727,8 @@ async function shutdownFunction(signal) {
|
||||
log.info("server", "Shutdown requested");
|
||||
log.info("server", "Called signal: " + signal);
|
||||
|
||||
await server.stop();
|
||||
|
||||
log.info("server", "Stopping all monitors");
|
||||
for (let id in server.monitorList) {
|
||||
let monitor = server.monitorList[id];
|
||||
|
311
server/socket-handlers/maintenance-socket-handler.js
Normal file
311
server/socket-handlers/maintenance-socket-handler.js
Normal file
@@ -0,0 +1,311 @@
|
||||
const { checkLogin } = require("../util-server");
|
||||
const { log } = require("../../src/util");
|
||||
const { R } = require("redbean-node");
|
||||
const apicache = require("../modules/apicache");
|
||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||
const Maintenance = require("../model/maintenance");
|
||||
const server = UptimeKumaServer.getInstance();
|
||||
const MaintenanceTimeslot = require("../model/maintenance_timeslot");
|
||||
|
||||
/**
|
||||
* Handlers for Maintenance
|
||||
* @param {Socket} socket Socket.io instance
|
||||
*/
|
||||
module.exports.maintenanceSocketHandler = (socket) => {
|
||||
// Add a new maintenance
|
||||
socket.on("addMaintenance", async (maintenance, callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
|
||||
log.debug("maintenance", maintenance);
|
||||
|
||||
let bean = Maintenance.jsonToBean(R.dispense("maintenance"), maintenance);
|
||||
bean.user_id = socket.userID;
|
||||
let maintenanceID = await R.store(bean);
|
||||
await MaintenanceTimeslot.generateTimeslot(bean);
|
||||
|
||||
await server.sendMaintenanceList(socket);
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Added Successfully.",
|
||||
maintenanceID,
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
callback({
|
||||
ok: false,
|
||||
msg: e.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Edit a maintenance
|
||||
socket.on("editMaintenance", async (maintenance, callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
|
||||
let bean = await R.findOne("maintenance", " id = ? ", [ maintenance.id ]);
|
||||
|
||||
if (bean.user_id !== socket.userID) {
|
||||
throw new Error("Permission denied.");
|
||||
}
|
||||
|
||||
Maintenance.jsonToBean(bean, maintenance);
|
||||
|
||||
await R.store(bean);
|
||||
await MaintenanceTimeslot.generateTimeslot(bean, null, true);
|
||||
|
||||
await server.sendMaintenanceList(socket);
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Saved.",
|
||||
maintenanceID: bean.id,
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
callback({
|
||||
ok: false,
|
||||
msg: e.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add a new monitor_maintenance
|
||||
socket.on("addMonitorMaintenance", async (maintenanceID, monitors, callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
|
||||
await R.exec("DELETE FROM monitor_maintenance WHERE maintenance_id = ?", [
|
||||
maintenanceID
|
||||
]);
|
||||
|
||||
for await (const monitor of monitors) {
|
||||
let bean = R.dispense("monitor_maintenance");
|
||||
|
||||
bean.import({
|
||||
monitor_id: monitor.id,
|
||||
maintenance_id: maintenanceID
|
||||
});
|
||||
await R.store(bean);
|
||||
}
|
||||
|
||||
apicache.clear();
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Added Successfully.",
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
callback({
|
||||
ok: false,
|
||||
msg: e.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add a new monitor_maintenance
|
||||
socket.on("addMaintenanceStatusPage", async (maintenanceID, statusPages, callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
|
||||
await R.exec("DELETE FROM maintenance_status_page WHERE maintenance_id = ?", [
|
||||
maintenanceID
|
||||
]);
|
||||
|
||||
for await (const statusPage of statusPages) {
|
||||
let bean = R.dispense("maintenance_status_page");
|
||||
|
||||
bean.import({
|
||||
status_page_id: statusPage.id,
|
||||
maintenance_id: maintenanceID
|
||||
});
|
||||
await R.store(bean);
|
||||
}
|
||||
|
||||
apicache.clear();
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Added Successfully.",
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
callback({
|
||||
ok: false,
|
||||
msg: e.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("getMaintenance", async (maintenanceID, callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
|
||||
log.debug("maintenance", `Get Maintenance: ${maintenanceID} User ID: ${socket.userID}`);
|
||||
|
||||
let bean = await R.findOne("maintenance", " id = ? AND user_id = ? ", [
|
||||
maintenanceID,
|
||||
socket.userID,
|
||||
]);
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
maintenance: await bean.toJSON(),
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
callback({
|
||||
ok: false,
|
||||
msg: e.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("getMaintenanceList", async (callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
await server.sendMaintenanceList(socket);
|
||||
callback({
|
||||
ok: true,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
callback({
|
||||
ok: false,
|
||||
msg: e.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("getMonitorMaintenance", async (maintenanceID, callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
|
||||
log.debug("maintenance", `Get Monitors for Maintenance: ${maintenanceID} User ID: ${socket.userID}`);
|
||||
|
||||
let monitors = await R.getAll("SELECT monitor.id, monitor.name FROM monitor_maintenance mm JOIN monitor ON mm.monitor_id = monitor.id WHERE mm.maintenance_id = ? ", [
|
||||
maintenanceID,
|
||||
]);
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
monitors,
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
callback({
|
||||
ok: false,
|
||||
msg: e.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("getMaintenanceStatusPage", async (maintenanceID, callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
|
||||
log.debug("maintenance", `Get Status Pages for Maintenance: ${maintenanceID} User ID: ${socket.userID}`);
|
||||
|
||||
let statusPages = await R.getAll("SELECT status_page.id, status_page.title FROM maintenance_status_page msp JOIN status_page ON msp.status_page_id = status_page.id WHERE msp.maintenance_id = ? ", [
|
||||
maintenanceID,
|
||||
]);
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
statusPages,
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
callback({
|
||||
ok: false,
|
||||
msg: e.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("deleteMaintenance", async (maintenanceID, callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
|
||||
log.debug("maintenance", `Delete Maintenance: ${maintenanceID} User ID: ${socket.userID}`);
|
||||
|
||||
if (maintenanceID in server.maintenanceList) {
|
||||
delete server.maintenanceList[maintenanceID];
|
||||
}
|
||||
|
||||
await R.exec("DELETE FROM maintenance WHERE id = ? AND user_id = ? ", [
|
||||
maintenanceID,
|
||||
socket.userID,
|
||||
]);
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Deleted Successfully.",
|
||||
});
|
||||
|
||||
await server.sendMaintenanceList(socket);
|
||||
|
||||
} catch (e) {
|
||||
callback({
|
||||
ok: false,
|
||||
msg: e.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("pauseMaintenance", async (maintenanceID, callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
|
||||
log.debug("maintenance", `Pause Maintenance: ${maintenanceID} User ID: ${socket.userID}`);
|
||||
|
||||
await R.exec("UPDATE maintenance SET active = 0 WHERE id = ? ", [
|
||||
maintenanceID,
|
||||
]);
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Paused Successfully.",
|
||||
});
|
||||
|
||||
await server.sendMaintenanceList(socket);
|
||||
|
||||
} catch (e) {
|
||||
callback({
|
||||
ok: false,
|
||||
msg: e.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("resumeMaintenance", async (maintenanceID, callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
|
||||
log.debug("maintenance", `Resume Maintenance: ${maintenanceID} User ID: ${socket.userID}`);
|
||||
|
||||
await R.exec("UPDATE maintenance SET active = 1 WHERE id = ? ", [
|
||||
maintenanceID,
|
||||
]);
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Resume Successfully",
|
||||
});
|
||||
|
||||
await server.sendMaintenanceList(socket);
|
||||
|
||||
} catch (e) {
|
||||
callback({
|
||||
ok: false,
|
||||
msg: e.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
@@ -9,6 +9,8 @@ const Database = require("./database");
|
||||
const util = require("util");
|
||||
const { CacheableDnsHttpAgent } = require("./cacheable-dns-http-agent");
|
||||
const { Settings } = require("./settings");
|
||||
const dayjs = require("dayjs");
|
||||
// DO NOT IMPORT HERE IF THE MODULES USED `UptimeKumaServer.getInstance()`
|
||||
|
||||
/**
|
||||
* `module.exports` (alias: `server`) should be inside this class, in order to avoid circular dependency issue.
|
||||
@@ -26,6 +28,13 @@ class UptimeKumaServer {
|
||||
* @type {{}}
|
||||
*/
|
||||
monitorList = {};
|
||||
|
||||
/**
|
||||
* Main maintenance list
|
||||
* @type {{}}
|
||||
*/
|
||||
maintenanceList = {};
|
||||
|
||||
entryPage = "dashboard";
|
||||
app = undefined;
|
||||
httpServer = undefined;
|
||||
@@ -37,6 +46,8 @@ class UptimeKumaServer {
|
||||
*/
|
||||
indexHTML = "";
|
||||
|
||||
generateMaintenanceTimeslotsInterval = undefined;
|
||||
|
||||
static getInstance(args) {
|
||||
if (UptimeKumaServer.instance == null) {
|
||||
UptimeKumaServer.instance = new UptimeKumaServer(args);
|
||||
@@ -77,6 +88,16 @@ class UptimeKumaServer {
|
||||
this.io = new Server(this.httpServer);
|
||||
}
|
||||
|
||||
async initAfterDatabaseReady() {
|
||||
process.env.TZ = await this.getTimezone();
|
||||
dayjs.tz.setDefault(process.env.TZ);
|
||||
log.debug("DEBUG", "Timezone: " + process.env.TZ);
|
||||
log.debug("DEBUG", "Current Time: " + dayjs.tz().format());
|
||||
|
||||
await this.generateMaintenanceTimeslots();
|
||||
this.generateMaintenanceTimeslotsInterval = setInterval(this.generateMaintenanceTimeslots, 60 * 1000);
|
||||
}
|
||||
|
||||
async sendMonitorList(socket) {
|
||||
let list = await this.getMonitorJSONList(socket.userID);
|
||||
this.io.to(socket.userID).emit("monitorList", list);
|
||||
@@ -104,6 +125,40 @@ class UptimeKumaServer {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send maintenance list to client
|
||||
* @param {Socket} socket Socket.io instance to send to
|
||||
* @returns {Object}
|
||||
*/
|
||||
async sendMaintenanceList(socket) {
|
||||
return await this.sendMaintenanceListByUserID(socket.userID);
|
||||
}
|
||||
|
||||
async sendMaintenanceListByUserID(userID) {
|
||||
let list = await this.getMaintenanceJSONList(userID);
|
||||
this.io.to(userID).emit("maintenanceList", list);
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of maintenances for the given user.
|
||||
* @param {string} userID - The ID of the user to get maintenances for.
|
||||
* @returns {Promise<Object>} A promise that resolves to an object with maintenance IDs as keys and maintenances objects as values.
|
||||
*/
|
||||
async getMaintenanceJSONList(userID) {
|
||||
let result = {};
|
||||
|
||||
let maintenanceList = await R.find("maintenance", " user_id = ? ORDER BY end_date DESC, title", [
|
||||
userID,
|
||||
]);
|
||||
|
||||
for (let maintenance of maintenanceList) {
|
||||
result[maintenance.id] = await maintenance.toJSON();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write error to log file
|
||||
* @param {any} error The error to write
|
||||
@@ -138,15 +193,58 @@ class UptimeKumaServer {
|
||||
}
|
||||
|
||||
if (await Settings.get("trustProxy")) {
|
||||
return socket.client.conn.request.headers["x-forwarded-for"]
|
||||
const forwardedFor = socket.client.conn.request.headers["x-forwarded-for"];
|
||||
|
||||
return (typeof forwardedFor === "string" ? forwardedFor.split(",")[0].trim() : null)
|
||||
|| socket.client.conn.request.headers["x-real-ip"]
|
||||
|| clientIP.replace(/^.*:/, "");
|
||||
} else {
|
||||
return clientIP.replace(/^.*:/, "");
|
||||
}
|
||||
}
|
||||
|
||||
async getTimezone() {
|
||||
let timezone = await Settings.get("serverTimezone");
|
||||
if (timezone) {
|
||||
return timezone;
|
||||
} else if (process.env.TZ) {
|
||||
return process.env.TZ;
|
||||
} else {
|
||||
return dayjs.tz.guess();
|
||||
}
|
||||
}
|
||||
|
||||
getTimezoneOffset() {
|
||||
return dayjs().format("Z");
|
||||
}
|
||||
|
||||
async setTimezone(timezone) {
|
||||
await Settings.set("serverTimezone", timezone, "general");
|
||||
process.env.TZ = timezone;
|
||||
dayjs.tz.setDefault(timezone);
|
||||
}
|
||||
|
||||
async generateMaintenanceTimeslots() {
|
||||
|
||||
let list = await R.find("maintenance_timeslot", " generated_next = 0 AND start_date <= DATETIME('now') ");
|
||||
|
||||
for (let maintenanceTimeslot of list) {
|
||||
let maintenance = await maintenanceTimeslot.maintenance;
|
||||
await MaintenanceTimeslot.generateTimeslot(maintenance, maintenanceTimeslot.end_date, false);
|
||||
maintenanceTimeslot.generated_next = true;
|
||||
await R.store(maintenanceTimeslot);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async stop() {
|
||||
clearTimeout(this.generateMaintenanceTimeslotsInterval);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
UptimeKumaServer
|
||||
};
|
||||
|
||||
// Must be at the end
|
||||
const MaintenanceTimeslot = require("./model/maintenance_timeslot");
|
||||
|
@@ -15,12 +15,15 @@ const { Client } = require("pg");
|
||||
const postgresConParse = require("pg-connection-string").parse;
|
||||
const { NtlmClient } = require("axios-ntlm");
|
||||
const { Settings } = require("./settings");
|
||||
const grpc = require("@grpc/grpc-js");
|
||||
const protojs = require("protobufjs");
|
||||
const radiusClient = require("node-radius-client");
|
||||
const {
|
||||
dictionaries: {
|
||||
rfc2865: { file, attributes },
|
||||
},
|
||||
} = require("node-radius-utils");
|
||||
const dayjs = require("dayjs");
|
||||
|
||||
// From ping-lite
|
||||
exports.WIN = /^win/.test(process.platform);
|
||||
@@ -291,6 +294,17 @@ exports.postgresQuery = function (connectionString, query) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Query radius server
|
||||
* @param {string} hostname Hostname of radius server
|
||||
* @param {string} username Username to use
|
||||
* @param {string} password Password to use
|
||||
* @param {string} calledStationId ID of called station
|
||||
* @param {string} callingStationId ID of calling station
|
||||
* @param {string} secret Secret to use
|
||||
* @param {number} [port=1812] Port to contact radius server on
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
exports.radius = function (
|
||||
hostname,
|
||||
username,
|
||||
@@ -298,9 +312,11 @@ exports.radius = function (
|
||||
calledStationId,
|
||||
callingStationId,
|
||||
secret,
|
||||
port = 1812,
|
||||
) {
|
||||
const client = new radiusClient({
|
||||
host: hostname,
|
||||
hostPort: port,
|
||||
dictionaries: [ file ],
|
||||
});
|
||||
|
||||
@@ -645,3 +661,112 @@ module.exports.send403 = (res, msg = "") => {
|
||||
"msg": msg,
|
||||
});
|
||||
};
|
||||
|
||||
function timeObjectConvertTimezone(obj, timezone, timeObjectToUTC = true) {
|
||||
let offsetString;
|
||||
|
||||
if (timezone) {
|
||||
offsetString = dayjs().tz(timezone).format("Z");
|
||||
} else {
|
||||
offsetString = dayjs().format("Z");
|
||||
}
|
||||
|
||||
let hours = parseInt(offsetString.substring(1, 3));
|
||||
let minutes = parseInt(offsetString.substring(4, 6));
|
||||
|
||||
if (
|
||||
(timeObjectToUTC && offsetString.startsWith("+")) ||
|
||||
(!timeObjectToUTC && offsetString.startsWith("-"))
|
||||
) {
|
||||
hours *= -1;
|
||||
minutes *= -1;
|
||||
}
|
||||
|
||||
obj.hours += hours;
|
||||
obj.minutes += minutes;
|
||||
|
||||
// Handle out of bound
|
||||
if (obj.minutes < 0) {
|
||||
obj.minutes += 60;
|
||||
obj.hours--;
|
||||
} else if (obj.minutes > 60) {
|
||||
obj.minutes -= 60;
|
||||
obj.hours++;
|
||||
}
|
||||
|
||||
if (obj.hours < 0) {
|
||||
obj.hours += 24;
|
||||
} else if (obj.hours > 24) {
|
||||
obj.hours -= 24;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} obj
|
||||
* @param {string} timezone
|
||||
* @returns {object}
|
||||
*/
|
||||
module.exports.timeObjectToUTC = (obj, timezone = undefined) => {
|
||||
return timeObjectConvertTimezone(obj, timezone, true);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} obj
|
||||
* @param {string} timezone
|
||||
* @returns {object}
|
||||
*/
|
||||
module.exports.timeObjectToLocal = (obj, timezone = undefined) => {
|
||||
return timeObjectConvertTimezone(obj, timezone, false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create gRPC client stib
|
||||
* @param {Object} options from gRPC client
|
||||
*/
|
||||
module.exports.grpcQuery = async (options) => {
|
||||
const { grpcUrl, grpcProtobufData, grpcServiceName, grpcEnableTls, grpcMethod, grpcBody } = options;
|
||||
const protocObject = protojs.parse(grpcProtobufData);
|
||||
const protoServiceObject = protocObject.root.lookupService(grpcServiceName);
|
||||
const Client = grpc.makeGenericClientConstructor({});
|
||||
const credentials = grpcEnableTls ? grpc.credentials.createSsl() : grpc.credentials.createInsecure();
|
||||
const client = new Client(
|
||||
grpcUrl,
|
||||
credentials
|
||||
);
|
||||
const grpcService = protoServiceObject.create(function (method, requestData, cb) {
|
||||
const fullServiceName = method.fullName;
|
||||
const serviceFQDN = fullServiceName.split(".");
|
||||
const serviceMethod = serviceFQDN.pop();
|
||||
const serviceMethodClientImpl = `/${serviceFQDN.slice(1).join(".")}/${serviceMethod}`;
|
||||
log.debug("monitor", `gRPC method ${serviceMethodClientImpl}`);
|
||||
client.makeUnaryRequest(
|
||||
serviceMethodClientImpl,
|
||||
arg => arg,
|
||||
arg => arg,
|
||||
requestData,
|
||||
cb);
|
||||
}, false, false);
|
||||
return new Promise((resolve, _) => {
|
||||
return grpcService[`${grpcMethod}`](JSON.parse(grpcBody), function (err, response) {
|
||||
const responseData = JSON.stringify(response);
|
||||
if (err) {
|
||||
return resolve({
|
||||
code: err.code,
|
||||
errorMessage: err.details,
|
||||
data: ""
|
||||
});
|
||||
} else {
|
||||
log.debug("monitor:", `gRPC response: ${response}`);
|
||||
return resolve({
|
||||
code: 1,
|
||||
errorMessage: "",
|
||||
data: responseData
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
Reference in New Issue
Block a user