mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-02-26 13:35:56 +00:00
Compare commits
8 commits
3f985d3628
...
883617ff5c
Author | SHA1 | Date | |
---|---|---|---|
|
883617ff5c | ||
|
46d8744fa4 | ||
|
7d8dc55dbe | ||
|
94af1d98d7 | ||
|
16a41d53c1 | ||
|
a3376069d0 | ||
|
90b9173cfb | ||
|
50d6ea36a4 |
9 changed files with 186 additions and 12 deletions
|
@ -0,0 +1,12 @@
|
|||
exports.up = function (knex) {
|
||||
return knex.schema
|
||||
.alterTable("notification", function (table) {
|
||||
table.boolean("use_path_as_name").notNullable().defaultTo(true);
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function (knex) {
|
||||
return knex.schema.alterTable("notification", function (table) {
|
||||
table.dropColumn("use_path_as_name");
|
||||
});
|
||||
};
|
|
@ -27,7 +27,6 @@ RUN mkdir ./data
|
|||
# ⭐ Main Image
|
||||
############################################
|
||||
FROM $BASE_IMAGE AS release
|
||||
USER node
|
||||
WORKDIR /app
|
||||
|
||||
LABEL org.opencontainers.image.source="https://github.com/louislam/uptime-kuma"
|
||||
|
@ -46,6 +45,7 @@ CMD ["node", "server/server.js"]
|
|||
# Rootless Image
|
||||
############################################
|
||||
FROM release AS rootless
|
||||
USER node
|
||||
|
||||
############################################
|
||||
# Mark as Nightly
|
||||
|
|
|
@ -26,7 +26,15 @@ async function sendNotificationList(socket) {
|
|||
for (let bean of list) {
|
||||
let notificationObject = bean.export();
|
||||
notificationObject.isDefault = (notificationObject.isDefault === 1);
|
||||
notificationObject.usePathAsName = (notificationObject.usePathAsName === 1);
|
||||
notificationObject.active = (notificationObject.active === 1);
|
||||
|
||||
const configObject = JSON.parse(notificationObject.config);
|
||||
if ( !("usePathAsName" in configObject)) {
|
||||
configObject.usePathAsName = notificationObject.usePathAsName;
|
||||
notificationObject.config = JSON.stringify(configObject);
|
||||
}
|
||||
|
||||
result.push(notificationObject);
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ const mysql = require("mysql2/promise");
|
|||
const { Settings } = require("./settings");
|
||||
const { UptimeCalculator } = require("./uptime-calculator");
|
||||
const dayjs = require("dayjs");
|
||||
const { SimpleMigrationServer } = require("./utils/simple-migration-server");
|
||||
|
||||
/**
|
||||
* Database & App Data Folder
|
||||
|
@ -382,9 +383,11 @@ class Database {
|
|||
|
||||
/**
|
||||
* Patch the database
|
||||
* @param {number} port Start the migration server for aggregate tables on this port if provided
|
||||
* @param {string} hostname Start the migration server for aggregate tables on this hostname if provided
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async patch() {
|
||||
static async patch(port = undefined, hostname = undefined) {
|
||||
// Still need to keep this for old versions of Uptime Kuma
|
||||
if (Database.dbConfig.type === "sqlite") {
|
||||
await this.patchSqlite();
|
||||
|
@ -409,7 +412,7 @@ class Database {
|
|||
await R.exec("PRAGMA foreign_keys = ON");
|
||||
}
|
||||
|
||||
await this.migrateAggregateTable();
|
||||
await this.migrateAggregateTable(port, hostname);
|
||||
|
||||
} catch (e) {
|
||||
// Allow missing patch files for downgrade or testing pr.
|
||||
|
@ -735,9 +738,11 @@ class Database {
|
|||
* Normally, it should be in transaction, but UptimeCalculator wasn't designed to be in transaction before that.
|
||||
* I don't want to heavily modify the UptimeCalculator, so it is not in transaction.
|
||||
* Run `npm run reset-migrate-aggregate-table-state` to reset, in case the migration is interrupted.
|
||||
* @param {number} port Start the migration server on this port if provided
|
||||
* @param {string} hostname Start the migration server on this hostname if provided
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async migrateAggregateTable() {
|
||||
static async migrateAggregateTable(port, hostname = undefined) {
|
||||
log.debug("db", "Enter Migrate Aggregate Table function");
|
||||
|
||||
// Add a setting for 2.0.0-dev users to skip this migration
|
||||
|
@ -758,6 +763,18 @@ class Database {
|
|||
throw new Error("Aggregate table migration is already in progress");
|
||||
}
|
||||
|
||||
/**
|
||||
* Start migration server for displaying the migration status
|
||||
* @type {SimpleMigrationServer}
|
||||
*/
|
||||
let migrationServer;
|
||||
let msg;
|
||||
|
||||
if (port) {
|
||||
migrationServer = new SimpleMigrationServer();
|
||||
await migrationServer.start(port, hostname);
|
||||
}
|
||||
|
||||
await Settings.set("migrateAggregateTableState", "migrating");
|
||||
|
||||
log.info("db", "Migrating Aggregate Table");
|
||||
|
@ -777,6 +794,7 @@ class Database {
|
|||
let count = countResult.count;
|
||||
if (count > 0) {
|
||||
log.warn("db", `Aggregate table ${table} is not empty, migration will not be started (Maybe you were using 2.0.0-dev?)`);
|
||||
await migrationServer?.stop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -811,7 +829,9 @@ class Database {
|
|||
`, [ monitor.monitor_id, date.date ]);
|
||||
|
||||
if (heartbeats.length > 0) {
|
||||
log.info("db", `[DON'T STOP] Migrating monitor data ${monitor.monitor_id} - ${date.date} [${progressPercent.toFixed(2)}%][${i}/${monitors.length}]`);
|
||||
msg = `[DON'T STOP] Migrating monitor data ${monitor.monitor_id} - ${date.date} [${progressPercent.toFixed(2)}%][${i}/${monitors.length}]`;
|
||||
log.info("db", msg);
|
||||
migrationServer?.update(msg);
|
||||
}
|
||||
|
||||
for (let heartbeat of heartbeats) {
|
||||
|
@ -829,9 +849,13 @@ class Database {
|
|||
i++;
|
||||
}
|
||||
|
||||
await Database.clearHeartbeatData(true);
|
||||
msg = "Clearing non-important heartbeats";
|
||||
log.info("db", msg);
|
||||
migrationServer?.update(msg);
|
||||
|
||||
await Database.clearHeartbeatData(true);
|
||||
await Settings.set("migrateAggregateTableState", "migrated");
|
||||
await migrationServer?.stop();
|
||||
|
||||
if (monitors.length > 0) {
|
||||
log.info("db", "Aggregate Table Migration Completed");
|
||||
|
|
|
@ -385,7 +385,8 @@ class Monitor extends BeanModel {
|
|||
|
||||
if (children.length > 0) {
|
||||
bean.status = UP;
|
||||
bean.msg = "All children up and running";
|
||||
bean.msg = "";
|
||||
let errorChildNames = [];
|
||||
for (const child of children) {
|
||||
if (!child.active) {
|
||||
// Ignore inactive childs
|
||||
|
@ -402,10 +403,22 @@ class Monitor extends BeanModel {
|
|||
} else if (bean.status === PENDING && lastBeat.status === DOWN) {
|
||||
bean.status = lastBeat.status;
|
||||
}
|
||||
|
||||
if (lastBeat && (lastBeat.status === PENDING || lastBeat.status === DOWN)) {
|
||||
const childMonitor = await Monitor.getMonitor(lastBeat.monitor_id);
|
||||
if (errorChildNames.length > 0) {
|
||||
bean.msg += "\r\n";
|
||||
}
|
||||
|
||||
bean.msg += "- " + childMonitor.name + ":\r\n" + lastBeat.msg.trim().replace(/^/gm, " ");
|
||||
errorChildNames.push(childMonitor.name);
|
||||
}
|
||||
}
|
||||
|
||||
if (bean.status !== UP) {
|
||||
bean.msg = "Child inaccessible";
|
||||
if (bean.status === UP) {
|
||||
bean.msg = "All children up and running";
|
||||
} else if (bean.status === PENDING || bean.status === DOWN) {
|
||||
bean.msg = "Some Children are having problems (" + errorChildNames.join(", ") + ")\r\n" + bean.msg;
|
||||
}
|
||||
} else {
|
||||
// Set status pending if group is empty
|
||||
|
@ -1326,8 +1339,10 @@ class Monitor extends BeanModel {
|
|||
for (let notification of notificationList) {
|
||||
try {
|
||||
const heartbeatJSON = bean.toJSON();
|
||||
const monitorData = [{ id: monitor.id,
|
||||
active: monitor.active
|
||||
const monitorData = [{
|
||||
id: monitor.id,
|
||||
name: monitor.name,
|
||||
active: monitor.active,
|
||||
}];
|
||||
const preloadData = await Monitor.preparePreloadData(monitorData);
|
||||
// Prevent if the msg is undefined, notifications such as Discord cannot send out.
|
||||
|
@ -1604,6 +1619,20 @@ class Monitor extends BeanModel {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets Monitor with specific ID
|
||||
* @param {number} monitorID ID of monitor to get
|
||||
* @returns {Promise<LooseObject<any>>} Children
|
||||
*/
|
||||
static async getMonitor(monitorID) {
|
||||
return await R.getRow(`
|
||||
SELECT * FROM monitor
|
||||
WHERE id = ?
|
||||
`, [
|
||||
monitorID,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets Parent of the monitor
|
||||
* @param {number} monitorID ID of monitor to get
|
||||
|
|
|
@ -178,6 +178,9 @@ class Notification {
|
|||
* @throws Error with fail msg
|
||||
*/
|
||||
static async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
if (notification.usePathAsName && monitorJSON) {
|
||||
monitorJSON["name"] = monitorJSON["pathName"];
|
||||
}
|
||||
if (this.providerList[notification.type]) {
|
||||
return this.providerList[notification.type].send(notification, msg, monitorJSON, heartbeatJSON);
|
||||
} else {
|
||||
|
@ -213,6 +216,8 @@ class Notification {
|
|||
bean.user_id = userID;
|
||||
bean.config = JSON.stringify(notification);
|
||||
bean.is_default = notification.isDefault || false;
|
||||
bean.use_path_as_name = notification.usePathAsName;
|
||||
|
||||
await R.store(bean);
|
||||
|
||||
if (notification.applyExisting) {
|
||||
|
|
|
@ -1716,7 +1716,7 @@ async function initDatabase(testMode = false) {
|
|||
log.info("server", "Connected to the database");
|
||||
|
||||
// Patch the database
|
||||
await Database.patch();
|
||||
await Database.patch(port, hostname);
|
||||
|
||||
let jwtSecretBean = await R.findOne("setting", " `key` = ? ", [
|
||||
"jwtSecret",
|
||||
|
|
84
server/utils/simple-migration-server.js
Normal file
84
server/utils/simple-migration-server.js
Normal file
|
@ -0,0 +1,84 @@
|
|||
const express = require("express");
|
||||
const http = require("node:http");
|
||||
const { log } = require("../../src/util");
|
||||
|
||||
/**
|
||||
* SimpleMigrationServer
|
||||
* For displaying the migration status of the server
|
||||
* Also, it is used to let Docker healthcheck know the status of the server, as the main server is not started yet, healthcheck will think the server is down incorrectly.
|
||||
*/
|
||||
class SimpleMigrationServer {
|
||||
/**
|
||||
* Express app instance
|
||||
* @type {?Express}
|
||||
*/
|
||||
app;
|
||||
|
||||
/**
|
||||
* Server instance
|
||||
* @type {?Server}
|
||||
*/
|
||||
server;
|
||||
|
||||
/**
|
||||
* Response object
|
||||
* @type {?Response}
|
||||
*/
|
||||
response;
|
||||
|
||||
/**
|
||||
* Start the server
|
||||
* @param {number} port Port
|
||||
* @param {string} hostname Hostname
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
start(port, hostname) {
|
||||
this.app = express();
|
||||
this.server = http.createServer(this.app);
|
||||
|
||||
this.app.get("/", (req, res) => {
|
||||
res.set("Content-Type", "text/plain");
|
||||
res.write("Migration is in progress, listening message...\n");
|
||||
if (this.response) {
|
||||
this.response.write("Disconnected\n");
|
||||
this.response.end();
|
||||
}
|
||||
this.response = res;
|
||||
// never ending response
|
||||
});
|
||||
|
||||
return new Promise((resolve) => {
|
||||
this.server.listen(port, hostname, () => {
|
||||
if (hostname) {
|
||||
log.info("migration", `Migration server is running on http://${hostname}:${port}`);
|
||||
} else {
|
||||
log.info("migration", `Migration server is running on http://localhost:${port}`);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the message
|
||||
* @param {string} msg Message to update
|
||||
* @returns {void}
|
||||
*/
|
||||
update(msg) {
|
||||
this.response?.write(msg + "\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the server
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async stop() {
|
||||
this.response?.write("Finished, please refresh this page.\n");
|
||||
this.response?.end();
|
||||
await this.server?.close();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
SimpleMigrationServer,
|
||||
};
|
|
@ -41,6 +41,16 @@
|
|||
|
||||
<br>
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<input v-model="notification.usePathAsName" class="form-check-input" type="checkbox">
|
||||
<label class="form-check-label">Use Monitor Path as Name</label>
|
||||
</div>
|
||||
<div class="form-text">
|
||||
If your monitor is inside a group, the name will show the full path of the monitor. For example "Group A / Monitor 1".
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<input v-model="notification.applyExisting" class="form-check-input" type="checkbox">
|
||||
<label class="form-check-label">{{ $t("Apply on all existing monitors") }}</label>
|
||||
|
@ -95,6 +105,7 @@ export default {
|
|||
/** @type { null | keyof NotificationFormList } */
|
||||
type: null,
|
||||
isDefault: false,
|
||||
usePathAsName: false,
|
||||
// Do not set default value here, please scroll to show()
|
||||
}
|
||||
};
|
||||
|
@ -264,6 +275,7 @@ export default {
|
|||
name: "",
|
||||
type: "telegram",
|
||||
isDefault: false,
|
||||
usePathAsName: true,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue