upload logo and expose ./data/upload to url

This commit is contained in:
LouisLam 2021-09-21 21:22:35 +08:00
parent 61c737c53c
commit 15c00d9158
7 changed files with 353 additions and 210 deletions

View file

@ -5,10 +5,23 @@ const { debug, sleep } = require("../src/util");
const dayjs = require("dayjs");
const knex = require("knex");
/**
* Database & App Data Folder
*/
class Database {
static templatePath = "./db/kuma.db";
/**
* Data Dir (Default: ./data)
*/
static dataDir;
/**
* User Upload Dir (Default: ./data/upload)
*/
static uploadDir;
static path;
/**
@ -52,6 +65,13 @@ class Database {
if (! fs.existsSync(Database.dataDir)) {
fs.mkdirSync(Database.dataDir, { recursive: true });
}
Database.uploadDir = Database.dataDir + "upload/";
if (! fs.existsSync(Database.uploadDir)) {
fs.mkdirSync(Database.uploadDir, { recursive: true });
}
console.log(`Data Dir: ${Database.dataDir}`);
}

57
server/image-data-uri.js Normal file
View file

@ -0,0 +1,57 @@
/*
From https://github.com/DiegoZoracKy/image-data-uri/blob/master/lib/image-data-uri.js
Modified with 0 dependencies
*/
let fs = require("fs");
let ImageDataURI = (() => {
function decode(dataURI) {
if (!/data:image\//.test(dataURI)) {
console.log("ImageDataURI :: Error :: It seems that it is not an Image Data URI. Couldn't match \"data:image/\"");
return null;
}
let regExMatches = dataURI.match("data:(image/.*);base64,(.*)");
return {
imageType: regExMatches[1],
dataBase64: regExMatches[2],
dataBuffer: new Buffer(regExMatches[2], "base64")
};
}
function encode(data, mediaType) {
if (!data || !mediaType) {
console.log("ImageDataURI :: Error :: Missing some of the required params: data, mediaType ");
return null;
}
mediaType = (/\//.test(mediaType)) ? mediaType : "image/" + mediaType;
let dataBase64 = (Buffer.isBuffer(data)) ? data.toString("base64") : new Buffer(data).toString("base64");
let dataImgBase64 = "data:" + mediaType + ";base64," + dataBase64;
return dataImgBase64;
}
function outputFile(dataURI, filePath) {
filePath = filePath || "./";
return new Promise((resolve, reject) => {
let imageDecoded = decode(dataURI);
fs.writeFile(filePath, imageDecoded.dataBuffer, err => {
if (err) {
return reject("ImageDataURI :: Error :: " + JSON.stringify(err, null, 4));
}
resolve(filePath);
});
});
}
return {
decode: decode,
encode: encode,
outputFile: outputFile,
};
})();
module.exports = ImageDataURI;

View file

@ -8,12 +8,12 @@ console.log("Node Env: " + process.env.NODE_ENV);
const { sleep, debug, TimeLogger, getRandomInt } = require("../src/util");
console.log("Importing Node libraries")
console.log("Importing Node libraries");
const fs = require("fs");
const http = require("http");
const https = require("https");
console.log("Importing 3rd-party libraries")
console.log("Importing 3rd-party libraries");
debug("Importing express");
const express = require("express");
debug("Importing socket.io");
@ -70,7 +70,7 @@ if (! fs.existsSync(Database.dataDir)) {
}
console.log(`Data Dir: ${Database.dataDir}`);
console.log("Creating express and socket.io instance")
console.log("Creating express and socket.io instance");
const app = express();
let server;
@ -157,6 +157,9 @@ exports.entryPage = "dashboard";
app.use("/", express.static("dist"));
// ./data/upload
app.use("/upload", express.static(Database.uploadDir));
app.get("/.well-known/change-password", async (_, response) => {
response.redirect("https://github.com/louislam/uptime-kuma/wiki/Reset-Password-via-CLI");
});
@ -167,22 +170,26 @@ exports.entryPage = "dashboard";
// Universal Route Handler, must be at the end of all express route.
app.get("*", async (_request, response) => {
response.send(indexHTML);
if (_request.originalUrl.startsWith("/upload/")) {
response.status(404).send("File not found.");
} else {
response.send(indexHTML);
}
});
console.log("Adding socket handler")
console.log("Adding socket handler");
io.on("connection", async (socket) => {
socket.emit("info", {
version: checkVersion.version,
latestVersion: checkVersion.latestVersion,
})
});
totalClient++;
if (needSetup) {
console.log("Redirect to setup page")
socket.emit("setup")
console.log("Redirect to setup page");
socket.emit("setup");
}
socket.on("disconnect", () => {
@ -198,44 +205,44 @@ exports.entryPage = "dashboard";
try {
let decoded = jwt.verify(token, jwtSecret);
console.log("Username from JWT: " + decoded.username)
console.log("Username from JWT: " + decoded.username);
let user = await R.findOne("user", " username = ? AND active = 1 ", [
decoded.username,
])
]);
if (user) {
debug("afterLogin")
debug("afterLogin");
afterLogin(socket, user)
afterLogin(socket, user);
debug("afterLogin ok")
debug("afterLogin ok");
callback({
ok: true,
})
});
} else {
callback({
ok: false,
msg: "The user is inactive or deleted.",
})
});
}
} catch (error) {
callback({
ok: false,
msg: "Invalid token.",
})
});
}
});
socket.on("login", async (data, callback) => {
console.log("Login")
console.log("Login");
let user = await login(data.username, data.password)
let user = await login(data.username, data.password);
if (user) {
afterLogin(socket, user)
afterLogin(socket, user);
if (user.twofaStatus == 0) {
callback({
@ -243,13 +250,13 @@ exports.entryPage = "dashboard";
token: jwt.sign({
username: data.username,
}, jwtSecret),
})
});
}
if (user.twofaStatus == 1 && !data.token) {
callback({
tokenRequired: true,
})
});
}
if (data.token) {
@ -261,39 +268,39 @@ exports.entryPage = "dashboard";
token: jwt.sign({
username: data.username,
}, jwtSecret),
})
});
} else {
callback({
ok: false,
msg: "Invalid Token!",
})
});
}
}
} else {
callback({
ok: false,
msg: "Incorrect username or password.",
})
});
}
});
socket.on("logout", async (callback) => {
socket.leave(socket.userID)
socket.leave(socket.userID);
socket.userID = null;
callback();
});
socket.on("prepare2FA", async (callback) => {
try {
checkLogin(socket)
checkLogin(socket);
let user = await R.findOne("user", " id = ? AND active = 1 ", [
socket.userID,
])
]);
if (user.twofa_status == 0) {
let newSecret = await genSecret()
let newSecret = await genSecret();
let encodedSecret = base32.encode(newSecret);
let uri = `otpauth://totp/Uptime%20Kuma:${user.username}?secret=${encodedSecret}`;
@ -305,24 +312,24 @@ exports.entryPage = "dashboard";
callback({
ok: true,
uri: uri,
})
});
} else {
callback({
ok: false,
msg: "2FA is already enabled.",
})
});
}
} catch (error) {
callback({
ok: false,
msg: "Error while trying to prepare 2FA.",
})
});
}
});
socket.on("save2FA", async (callback) => {
try {
checkLogin(socket)
checkLogin(socket);
await R.exec("UPDATE `user` SET twofa_status = 1 WHERE id = ? ", [
socket.userID,
@ -331,18 +338,18 @@ exports.entryPage = "dashboard";
callback({
ok: true,
msg: "2FA Enabled.",
})
});
} catch (error) {
callback({
ok: false,
msg: "Error while trying to change 2FA.",
})
});
}
});
socket.on("disable2FA", async (callback) => {
try {
checkLogin(socket)
checkLogin(socket);
await R.exec("UPDATE `user` SET twofa_status = 0 WHERE id = ? ", [
socket.userID,
@ -351,19 +358,19 @@ exports.entryPage = "dashboard";
callback({
ok: true,
msg: "2FA Disabled.",
})
});
} catch (error) {
callback({
ok: false,
msg: "Error while trying to change 2FA.",
})
});
}
});
socket.on("verifyToken", async (token, callback) => {
let user = await R.findOne("user", " id = ? AND active = 1 ", [
socket.userID,
])
]);
let verify = notp.totp.verify(token, user.twofa_secret);
@ -371,40 +378,40 @@ exports.entryPage = "dashboard";
callback({
ok: true,
valid: true,
})
});
} else {
callback({
ok: false,
msg: "Invalid Token.",
valid: false,
})
});
}
});
socket.on("twoFAStatus", async (callback) => {
checkLogin(socket)
checkLogin(socket);
try {
let user = await R.findOne("user", " id = ? AND active = 1 ", [
socket.userID,
])
]);
if (user.twofa_status == 1) {
callback({
ok: true,
status: true,
})
});
} else {
callback({
ok: true,
status: false,
})
});
}
} catch (error) {
callback({
ok: false,
msg: "Error while trying to get 2FA status.",
})
});
}
});
@ -415,13 +422,13 @@ exports.entryPage = "dashboard";
socket.on("setup", async (username, password, callback) => {
try {
if ((await R.count("user")) !== 0) {
throw new Error("Uptime Kuma has been setup. If you want to setup again, please delete the database.")
throw new Error("Uptime Kuma has been setup. If you want to setup again, please delete the database.");
}
let user = R.dispense("user")
let user = R.dispense("user");
user.username = username;
user.password = passwordHash.generate(password)
await R.store(user)
user.password = passwordHash.generate(password);
await R.store(user);
needSetup = false;
@ -445,8 +452,8 @@ exports.entryPage = "dashboard";
// Add a new monitor
socket.on("add", async (monitor, callback) => {
try {
checkLogin(socket)
let bean = R.dispense("monitor")
checkLogin(socket);
let bean = R.dispense("monitor");
let notificationIDList = monitor.notificationIDList;
delete monitor.notificationIDList;
@ -454,11 +461,11 @@ exports.entryPage = "dashboard";
monitor.accepted_statuscodes_json = JSON.stringify(monitor.accepted_statuscodes);
delete monitor.accepted_statuscodes;
bean.import(monitor)
bean.user_id = socket.userID
await R.store(bean)
bean.import(monitor);
bean.user_id = socket.userID;
await R.store(bean);
await updateMonitorNotification(bean.id, notificationIDList)
await updateMonitorNotification(bean.id, notificationIDList);
await startMonitor(socket.userID, bean.id);
await sendMonitorList(socket);
@ -480,18 +487,18 @@ exports.entryPage = "dashboard";
// Edit a monitor
socket.on("editMonitor", async (monitor, callback) => {
try {
checkLogin(socket)
checkLogin(socket);
let bean = await R.findOne("monitor", " id = ? ", [ monitor.id ])
let bean = await R.findOne("monitor", " id = ? ", [ monitor.id ]);
if (bean.user_id !== socket.userID) {
throw new Error("Permission denied.")
throw new Error("Permission denied.");
}
bean.name = monitor.name
bean.type = monitor.type
bean.url = monitor.url
bean.interval = monitor.interval
bean.name = monitor.name;
bean.type = monitor.type;
bean.url = monitor.url;
bean.interval = monitor.interval;
bean.retryInterval = monitor.retryInterval;
bean.hostname = monitor.hostname;
bean.maxretries = monitor.maxretries;
@ -504,12 +511,12 @@ exports.entryPage = "dashboard";
bean.dns_resolve_type = monitor.dns_resolve_type;
bean.dns_resolve_server = monitor.dns_resolve_server;
await R.store(bean)
await R.store(bean);
await updateMonitorNotification(bean.id, monitor.notificationIDList)
await updateMonitorNotification(bean.id, monitor.notificationIDList);
if (bean.active) {
await restartMonitor(socket.userID, bean.id)
await restartMonitor(socket.userID, bean.id);
}
await sendMonitorList(socket);
@ -521,7 +528,7 @@ exports.entryPage = "dashboard";
});
} catch (e) {
console.error(e)
console.error(e);
callback({
ok: false,
msg: e.message,
@ -531,13 +538,13 @@ exports.entryPage = "dashboard";
socket.on("getMonitorList", async (callback) => {
try {
checkLogin(socket)
checkLogin(socket);
await sendMonitorList(socket);
callback({
ok: true,
});
} catch (e) {
console.error(e)
console.error(e);
callback({
ok: false,
msg: e.message,
@ -547,14 +554,14 @@ exports.entryPage = "dashboard";
socket.on("getMonitor", async (monitorID, callback) => {
try {
checkLogin(socket)
checkLogin(socket);
console.log(`Get Monitor: ${monitorID} User ID: ${socket.userID}`)
console.log(`Get Monitor: ${monitorID} User ID: ${socket.userID}`);
let bean = await R.findOne("monitor", " id = ? AND user_id = ? ", [
monitorID,
socket.userID,
])
]);
callback({
ok: true,
@ -572,7 +579,7 @@ exports.entryPage = "dashboard";
// Start or Resume the monitor
socket.on("resumeMonitor", async (monitorID, callback) => {
try {
checkLogin(socket)
checkLogin(socket);
await startMonitor(socket.userID, monitorID);
await sendMonitorList(socket);
@ -591,8 +598,8 @@ exports.entryPage = "dashboard";
socket.on("pauseMonitor", async (monitorID, callback) => {
try {
checkLogin(socket)
await pauseMonitor(socket.userID, monitorID)
checkLogin(socket);
await pauseMonitor(socket.userID, monitorID);
await sendMonitorList(socket);
callback({
@ -610,13 +617,13 @@ exports.entryPage = "dashboard";
socket.on("deleteMonitor", async (monitorID, callback) => {
try {
checkLogin(socket)
checkLogin(socket);
console.log(`Delete Monitor: ${monitorID} User ID: ${socket.userID}`)
console.log(`Delete Monitor: ${monitorID} User ID: ${socket.userID}`);
if (monitorID in monitorList) {
monitorList[monitorID].stop();
delete monitorList[monitorID]
delete monitorList[monitorID];
}
await R.exec("DELETE FROM monitor WHERE id = ? AND user_id = ? ", [
@ -641,9 +648,9 @@ exports.entryPage = "dashboard";
socket.on("getTags", async (callback) => {
try {
checkLogin(socket)
checkLogin(socket);
const list = await R.findAll("tag")
const list = await R.findAll("tag");
callback({
ok: true,
@ -660,12 +667,12 @@ exports.entryPage = "dashboard";
socket.on("addTag", async (tag, callback) => {
try {
checkLogin(socket)
checkLogin(socket);
let bean = R.dispense("tag")
bean.name = tag.name
bean.color = tag.color
await R.store(bean)
let bean = R.dispense("tag");
bean.name = tag.name;
bean.color = tag.color;
await R.store(bean);
callback({
ok: true,
@ -682,12 +689,12 @@ exports.entryPage = "dashboard";
socket.on("editTag", async (tag, callback) => {
try {
checkLogin(socket)
checkLogin(socket);
let bean = await R.findOne("monitor", " id = ? ", [ tag.id ])
bean.name = tag.name
bean.color = tag.color
await R.store(bean)
let bean = await R.findOne("monitor", " id = ? ", [ tag.id ]);
bean.name = tag.name;
bean.color = tag.color;
await R.store(bean);
callback({
ok: true,
@ -704,9 +711,9 @@ exports.entryPage = "dashboard";
socket.on("deleteTag", async (tagID, callback) => {
try {
checkLogin(socket)
checkLogin(socket);
await R.exec("DELETE FROM tag WHERE id = ? ", [ tagID ])
await R.exec("DELETE FROM tag WHERE id = ? ", [ tagID ]);
callback({
ok: true,
@ -723,13 +730,13 @@ exports.entryPage = "dashboard";
socket.on("addMonitorTag", async (tagID, monitorID, value, callback) => {
try {
checkLogin(socket)
checkLogin(socket);
await R.exec("INSERT INTO monitor_tag (tag_id, monitor_id, value) VALUES (?, ?, ?)", [
tagID,
monitorID,
value,
])
]);
callback({
ok: true,
@ -746,13 +753,13 @@ exports.entryPage = "dashboard";
socket.on("editMonitorTag", async (tagID, monitorID, value, callback) => {
try {
checkLogin(socket)
checkLogin(socket);
await R.exec("UPDATE monitor_tag SET value = ? WHERE tag_id = ? AND monitor_id = ?", [
value,
tagID,
monitorID,
])
]);
callback({
ok: true,
@ -769,13 +776,13 @@ exports.entryPage = "dashboard";
socket.on("deleteMonitorTag", async (tagID, monitorID, value, callback) => {
try {
checkLogin(socket)
checkLogin(socket);
await R.exec("DELETE FROM monitor_tag WHERE tag_id = ? AND monitor_id = ? AND value = ?", [
tagID,
monitorID,
value,
])
]);
// Cleanup unused Tags
await R.exec("delete from tag where ( select count(*) from monitor_tag mt where tag.id = mt.tag_id ) = 0");
@ -795,15 +802,15 @@ exports.entryPage = "dashboard";
socket.on("changePassword", async (password, callback) => {
try {
checkLogin(socket)
checkLogin(socket);
if (! password.currentPassword) {
throw new Error("Invalid new password")
throw new Error("Invalid new password");
}
let user = await R.findOne("user", " id = ? AND active = 1 ", [
socket.userID,
])
]);
if (user && passwordHash.verify(password.currentPassword, user.password)) {
@ -812,9 +819,9 @@ exports.entryPage = "dashboard";
callback({
ok: true,
msg: "Password has been updated successfully.",
})
});
} else {
throw new Error("Incorrect current password")
throw new Error("Incorrect current password");
}
} catch (e) {
@ -827,7 +834,7 @@ exports.entryPage = "dashboard";
socket.on("getSettings", async (callback) => {
try {
checkLogin(socket)
checkLogin(socket);
callback({
ok: true,
@ -844,7 +851,7 @@ exports.entryPage = "dashboard";
socket.on("setSettings", async (data, callback) => {
try {
checkLogin(socket)
checkLogin(socket);
await setSettings("general", data);
exports.entryPage = data.entryPage;
@ -865,10 +872,10 @@ exports.entryPage = "dashboard";
// Add or Edit
socket.on("addNotification", async (notification, notificationID, callback) => {
try {
checkLogin(socket)
checkLogin(socket);
let notificationBean = await Notification.save(notification, notificationID, socket.userID)
await sendNotificationList(socket)
let notificationBean = await Notification.save(notification, notificationID, socket.userID);
await sendNotificationList(socket);
callback({
ok: true,
@ -886,10 +893,10 @@ exports.entryPage = "dashboard";
socket.on("deleteNotification", async (notificationID, callback) => {
try {
checkLogin(socket)
checkLogin(socket);
await Notification.delete(notificationID, socket.userID)
await sendNotificationList(socket)
await Notification.delete(notificationID, socket.userID);
await sendNotificationList(socket);
callback({
ok: true,
@ -906,9 +913,9 @@ exports.entryPage = "dashboard";
socket.on("testNotification", async (notification, callback) => {
try {
checkLogin(socket)
checkLogin(socket);
let msg = await Notification.send(notification, notification.name + " Testing")
let msg = await Notification.send(notification, notification.name + " Testing");
callback({
ok: true,
@ -916,7 +923,7 @@ exports.entryPage = "dashboard";
});
} catch (e) {
console.error(e)
console.error(e);
callback({
ok: false,
@ -927,7 +934,7 @@ exports.entryPage = "dashboard";
socket.on("checkApprise", async (callback) => {
try {
checkLogin(socket)
checkLogin(socket);
callback(Notification.checkApprise());
} catch (e) {
callback(false);
@ -936,19 +943,19 @@ exports.entryPage = "dashboard";
socket.on("uploadBackup", async (uploadedJSON, importHandle, callback) => {
try {
checkLogin(socket)
checkLogin(socket);
let backupData = JSON.parse(uploadedJSON);
console.log(`Importing Backup, User ID: ${socket.userID}, Version: ${backupData.version}`)
console.log(`Importing Backup, User ID: ${socket.userID}, Version: ${backupData.version}`);
let notificationListData = backupData.notificationList;
let monitorListData = backupData.monitorList;
if (importHandle == "overwrite") {
for (let id in monitorList) {
let monitor = monitorList[id]
await monitor.stop()
let monitor = monitorList[id];
await monitor.stop();
}
await R.exec("DELETE FROM heartbeat");
await R.exec("DELETE FROM monitor_notification");
@ -965,7 +972,7 @@ exports.entryPage = "dashboard";
if ((importHandle == "skip" && notificationNameListString.includes(notificationListData[i].name) == false) || importHandle == "keep" || importHandle == "overwrite") {
let notification = JSON.parse(notificationListData[i].config);
await Notification.save(notification, null, socket.userID)
await Notification.save(notification, null, socket.userID);
}
}
@ -994,9 +1001,9 @@ exports.entryPage = "dashboard";
dns_resolve_type: monitorListData[i].dns_resolve_type,
dns_resolve_server: monitorListData[i].dns_resolve_server,
notificationIDList: {},
}
};
let bean = R.dispense("monitor")
let bean = R.dispense("monitor");
let notificationIDList = monitor.notificationIDList;
delete monitor.notificationIDList;
@ -1004,11 +1011,11 @@ exports.entryPage = "dashboard";
monitor.accepted_statuscodes_json = JSON.stringify(monitor.accepted_statuscodes);
delete monitor.accepted_statuscodes;
bean.import(monitor)
bean.user_id = socket.userID
await R.store(bean)
bean.import(monitor);
bean.user_id = socket.userID;
await R.store(bean);
await updateMonitorNotification(bean.id, notificationIDList)
await updateMonitorNotification(bean.id, notificationIDList);
if (monitorListData[i].active == 1) {
await startMonitor(socket.userID, bean.id);
@ -1019,7 +1026,7 @@ exports.entryPage = "dashboard";
}
}
await sendNotificationList(socket)
await sendNotificationList(socket);
await sendMonitorList(socket);
}
@ -1038,9 +1045,9 @@ exports.entryPage = "dashboard";
socket.on("clearEvents", async (monitorID, callback) => {
try {
checkLogin(socket)
checkLogin(socket);
console.log(`Clear Events Monitor: ${monitorID} User ID: ${socket.userID}`)
console.log(`Clear Events Monitor: ${monitorID} User ID: ${socket.userID}`);
await R.exec("UPDATE heartbeat SET msg = ?, important = ? WHERE monitor_id = ? ", [
"",
@ -1064,9 +1071,9 @@ exports.entryPage = "dashboard";
socket.on("clearHeartbeats", async (monitorID, callback) => {
try {
checkLogin(socket)
checkLogin(socket);
console.log(`Clear Heartbeats Monitor: ${monitorID} User ID: ${socket.userID}`)
console.log(`Clear Heartbeats Monitor: ${monitorID} User ID: ${socket.userID}`);
await R.exec("DELETE FROM heartbeat WHERE monitor_id = ?", [
monitorID
@ -1088,9 +1095,9 @@ exports.entryPage = "dashboard";
socket.on("clearStatistics", async (callback) => {
try {
checkLogin(socket)
checkLogin(socket);
console.log(`Clear Statistics User ID: ${socket.userID}`)
console.log(`Clear Statistics User ID: ${socket.userID}`);
await R.exec("DELETE FROM heartbeat");
@ -1115,18 +1122,18 @@ exports.entryPage = "dashboard";
// Better do anything after added all socket handlers here
// ***************************
debug("check auto login")
debug("check auto login");
if (await setting("disableAuth")) {
console.log("Disabled Auth: auto login to admin")
afterLogin(socket, await R.findOne("user"))
socket.emit("autoLogin")
console.log("Disabled Auth: auto login to admin");
afterLogin(socket, await R.findOne("user"));
socket.emit("autoLogin");
} else {
debug("need auth")
debug("need auth");
}
});
console.log("Init the server")
console.log("Init the server");
server.once("error", async (err) => {
console.error("Cannot listen: " + err.message);
@ -1148,14 +1155,14 @@ exports.entryPage = "dashboard";
async function updateMonitorNotification(monitorID, notificationIDList) {
await R.exec("DELETE FROM monitor_notification WHERE monitor_id = ? ", [
monitorID,
])
]);
for (let notificationID in notificationIDList) {
if (notificationIDList[notificationID]) {
let relation = R.dispense("monitor_notification");
relation.monitor_id = monitorID;
relation.notification_id = notificationID;
await R.store(relation)
await R.store(relation);
}
}
}
@ -1164,7 +1171,7 @@ async function checkOwner(userID, monitorID) {
let row = await R.getRow("SELECT id FROM monitor WHERE id = ? AND user_id = ? ", [
monitorID,
userID,
])
]);
if (! row) {
throw new Error("You do not own this monitor.");
@ -1173,16 +1180,16 @@ async function checkOwner(userID, monitorID) {
async function sendMonitorList(socket) {
let list = await getMonitorJSONList(socket.userID);
io.to(socket.userID).emit("monitorList", list)
io.to(socket.userID).emit("monitorList", list);
return list;
}
async function afterLogin(socket, user) {
socket.userID = user.id;
socket.join(user.id)
socket.join(user.id);
let monitorList = await sendMonitorList(socket)
sendNotificationList(socket)
let monitorList = await sendMonitorList(socket);
sendNotificationList(socket);
await sleep(500);
@ -1195,7 +1202,7 @@ async function afterLogin(socket, user) {
}
for (let monitorID in monitorList) {
await Monitor.sendStats(io, monitorID, user.id)
await Monitor.sendStats(io, monitorID, user.id);
}
}
@ -1204,7 +1211,7 @@ async function getMonitorJSONList(userID) {
let monitorList = await R.find("monitor", " user_id = ? ORDER BY weight DESC, name", [
userID,
])
]);
for (let monitor of monitorList) {
result[monitor.id] = await monitor.toJSON();
@ -1215,16 +1222,16 @@ async function getMonitorJSONList(userID) {
async function initDatabase() {
if (! fs.existsSync(Database.path)) {
console.log("Copying Database")
console.log("Copying Database");
fs.copyFileSync(Database.templatePath, Database.path);
}
console.log("Connecting to Database")
console.log("Connecting to Database");
await Database.connect();
console.log("Connected")
console.log("Connected");
// Patch the database
await Database.patch()
await Database.patch();
let jwtSecretBean = await R.findOne("setting", " `key` = ? ", [
"jwtSecret",
@ -1240,7 +1247,7 @@ async function initDatabase() {
// If there is no record in user table, it is a new Uptime Kuma instance, need to setup
if ((await R.count("user")) === 0) {
console.log("No user, need setup")
console.log("No user, need setup");
needSetup = true;
}
@ -1248,9 +1255,9 @@ async function initDatabase() {
}
async function startMonitor(userID, monitorID) {
await checkOwner(userID, monitorID)
await checkOwner(userID, monitorID);
console.log(`Resume Monitor: ${monitorID} User ID: ${userID}`)
console.log(`Resume Monitor: ${monitorID} User ID: ${userID}`);
await R.exec("UPDATE monitor SET active = 1 WHERE id = ? AND user_id = ? ", [
monitorID,
@ -1259,24 +1266,24 @@ async function startMonitor(userID, monitorID) {
let monitor = await R.findOne("monitor", " id = ? ", [
monitorID,
])
]);
if (monitor.id in monitorList) {
monitorList[monitor.id].stop();
}
monitorList[monitor.id] = monitor;
monitor.start(io)
monitor.start(io);
}
async function restartMonitor(userID, monitorID) {
return await startMonitor(userID, monitorID)
return await startMonitor(userID, monitorID);
}
async function pauseMonitor(userID, monitorID) {
await checkOwner(userID, monitorID)
await checkOwner(userID, monitorID);
console.log(`Pause Monitor: ${monitorID} User ID: ${userID}`)
console.log(`Pause Monitor: ${monitorID} User ID: ${userID}`);
await R.exec("UPDATE monitor SET active = 0 WHERE id = ? AND user_id = ? ", [
monitorID,
@ -1292,7 +1299,7 @@ async function pauseMonitor(userID, monitorID) {
* Resume active monitors
*/
async function startMonitors() {
let list = await R.find("monitor", " active = 1 ")
let list = await R.find("monitor", " active = 1 ");
for (let monitor of list) {
monitorList[monitor.id] = monitor;
@ -1309,10 +1316,10 @@ async function shutdownFunction(signal) {
console.log("Shutdown requested");
console.log("Called signal: " + signal);
console.log("Stopping all monitors")
console.log("Stopping all monitors");
for (let id in monitorList) {
let monitor = monitorList[id]
monitor.stop()
let monitor = monitorList[id];
monitor.stop();
}
await sleep(2000);
await Database.close();

View file

@ -2,6 +2,9 @@ const { R } = require("redbean-node");
const { checkLogin, setSettings } = require("../util-server");
const dayjs = require("dayjs");
const { debug } = require("../../src/util");
const ImageDataURI = require("../image-data-uri");
const Database = require("../database");
const fs = require("fs");
module.exports.statusPageSocketHandler = (socket) => {
@ -67,18 +70,35 @@ module.exports.statusPageSocketHandler = (socket) => {
});
// Save Status Page
// imgDataUrl Only Accept PNG!
socket.on("saveStatusPage", async (config, imgDataUrl, publicGroupList, callback) => {
try {
checkLogin(socket);
const header = "data:image/png;base64,";
// Check logo format
// If is image data url, convert to png file
// Else assume it is a url, nothing to do
if (imgDataUrl.startsWith("data:")) {
if (! imgDataUrl.startsWith(header)) {
throw new Error("Only allowed PNG logo.");
}
// Convert to file
await ImageDataURI.outputFile(imgDataUrl, Database.uploadDir + "logo.png");
config.logo = "/upload/logo.png?t=" + Date.now();
} else {
config.icon = imgDataUrl;
}
// Save Config
await setSettings("statusPage", config);
await R.transaction(async (trx) => {
// Save Icon
// Save Public Group List
const groupIDList = [];
let groupOrder = 1;

View file

@ -26,6 +26,7 @@ import {
faArrowsAltV,
faUnlink,
faQuestionCircle,
faImages, faUpload,
} from "@fortawesome/free-solid-svg-icons";
library.add(
@ -51,6 +52,8 @@ library.add(
faArrowsAltV,
faUnlink,
faQuestionCircle,
faImages,
faUpload,
);
export { FontAwesomeIcon };

View file

@ -1,7 +1,6 @@
import { io } from "socket.io-client";
import { useToast } from "vue-toastification";
import axios from "axios";
const toast = useToast()
const toast = useToast();
let socket;
@ -32,7 +31,7 @@ export default {
certInfoList: {},
notificationList: [],
connectionErrorMsg: "Cannot connect to the socket server. Reconnecting...",
}
};
},
created() {
@ -74,7 +73,7 @@ export default {
});
socket.on("setup", (monitorID, data) => {
this.$router.push("/setup")
this.$router.push("/setup");
});
socket.on("autoLogin", (monitorID, data) => {
@ -132,7 +131,7 @@ export default {
this.importantHeartbeatList[data.monitorID] = [];
}
this.importantHeartbeatList[data.monitorID].unshift(data)
this.importantHeartbeatList[data.monitorID].unshift(data);
}
});
@ -140,27 +139,27 @@ export default {
if (! (monitorID in this.heartbeatList) || overwrite) {
this.heartbeatList[monitorID] = data;
} else {
this.heartbeatList[monitorID] = data.concat(this.heartbeatList[monitorID])
this.heartbeatList[monitorID] = data.concat(this.heartbeatList[monitorID]);
}
});
socket.on("avgPing", (monitorID, data) => {
this.avgPingList[monitorID] = data
this.avgPingList[monitorID] = data;
});
socket.on("uptime", (monitorID, type, data) => {
this.uptimeList[`${monitorID}_${type}`] = data
this.uptimeList[`${monitorID}_${type}`] = data;
});
socket.on("certInfo", (monitorID, data) => {
this.certInfoList[monitorID] = JSON.parse(data)
this.certInfoList[monitorID] = JSON.parse(data);
});
socket.on("importantHeartbeatList", (monitorID, data, overwrite) => {
if (! (monitorID in this.importantHeartbeatList) || overwrite) {
this.importantHeartbeatList[monitorID] = data;
} else {
this.importantHeartbeatList[monitorID] = data.concat(this.importantHeartbeatList[monitorID])
this.importantHeartbeatList[monitorID] = data.concat(this.importantHeartbeatList[monitorID]);
}
});
@ -172,26 +171,26 @@ export default {
});
socket.on("disconnect", () => {
console.log("disconnect")
console.log("disconnect");
this.connectionErrorMsg = "Lost connection to the socket server. Reconnecting...";
this.socket.connected = false;
});
socket.on("connect", () => {
console.log("connect")
console.log("connect");
this.socket.connectCount++;
this.socket.connected = true;
// Reset Heartbeat list if it is re-connect
if (this.socket.connectCount >= 2) {
this.clearData()
this.clearData();
}
let token = this.storage().token;
if (token) {
if (token !== "autoLogin") {
this.loginByToken(token)
this.loginByToken(token);
} else {
// Timeout if it is not actually auto login
@ -235,7 +234,7 @@ export default {
token,
}, (res) => {
if (res.tokenRequired) {
callback(res)
callback(res);
}
if (res.ok) {
@ -244,11 +243,11 @@ export default {
this.loggedIn = true;
// Trigger Chrome Save Password
history.pushState({}, "")
history.pushState({}, "");
}
callback(res)
})
callback(res);
});
},
loginByToken(token) {
@ -256,11 +255,11 @@ export default {
this.allowLoginDialog = true;
if (! res.ok) {
this.logout()
this.logout();
} else {
this.loggedIn = true;
}
})
});
},
logout() {
@ -268,68 +267,68 @@ export default {
this.socket.token = null;
this.loggedIn = false;
this.clearData()
this.clearData();
},
prepare2FA(callback) {
socket.emit("prepare2FA", callback)
socket.emit("prepare2FA", callback);
},
save2FA(secret, callback) {
socket.emit("save2FA", callback)
socket.emit("save2FA", callback);
},
disable2FA(callback) {
socket.emit("disable2FA", callback)
socket.emit("disable2FA", callback);
},
verifyToken(token, callback) {
socket.emit("verifyToken", token, callback)
socket.emit("verifyToken", token, callback);
},
twoFAStatus(callback) {
socket.emit("twoFAStatus", callback)
socket.emit("twoFAStatus", callback);
},
getMonitorList(callback) {
socket.emit("getMonitorList", callback)
socket.emit("getMonitorList", callback);
},
add(monitor, callback) {
socket.emit("add", monitor, callback)
socket.emit("add", monitor, callback);
},
deleteMonitor(monitorID, callback) {
socket.emit("deleteMonitor", monitorID, callback)
socket.emit("deleteMonitor", monitorID, callback);
},
clearData() {
console.log("reset heartbeat list")
this.heartbeatList = {}
this.importantHeartbeatList = {}
console.log("reset heartbeat list");
this.heartbeatList = {};
this.importantHeartbeatList = {};
},
uploadBackup(uploadedJSON, importHandle, callback) {
socket.emit("uploadBackup", uploadedJSON, importHandle, callback)
socket.emit("uploadBackup", uploadedJSON, importHandle, callback);
},
clearEvents(monitorID, callback) {
socket.emit("clearEvents", monitorID, callback)
socket.emit("clearEvents", monitorID, callback);
},
clearHeartbeats(monitorID, callback) {
socket.emit("clearHeartbeats", monitorID, callback)
socket.emit("clearHeartbeats", monitorID, callback);
},
clearStatistics(callback) {
socket.emit("clearStatistics", callback)
socket.emit("clearStatistics", callback);
},
},
computed: {
lastHeartbeatList() {
let result = {}
let result = {};
for (let monitorID in this.heartbeatList) {
let index = this.heartbeatList[monitorID].length - 1;
@ -340,15 +339,15 @@ export default {
},
statusList() {
let result = {}
let result = {};
let unknown = {
text: "Unknown",
color: "secondary",
}
};
for (let monitorID in this.lastHeartbeatList) {
let lastHeartBeat = this.lastHeartbeatList[monitorID]
let lastHeartBeat = this.lastHeartbeatList[monitorID];
if (! lastHeartBeat) {
result[monitorID] = unknown;
@ -381,12 +380,12 @@ export default {
// Reload the SPA if the server version is changed.
"info.version"(to, from) {
if (from && from !== to) {
window.location.reload()
window.location.reload();
}
},
remember() {
localStorage.remember = (this.remember) ? "1" : "0"
localStorage.remember = (this.remember) ? "1" : "0";
},
// Reconnect the socket io, if status-page to dashboard
@ -399,4 +398,4 @@ export default {
},
}
};

View file

@ -3,7 +3,10 @@
<!-- Logo & Title -->
<h1>
<!-- Logo -->
<img :src="imgDataUrl" alt class="logo me-2" :class="logoClass" @click="showImageCropUploadMethod" />
<div class="logo-wrapper" @click="showImageCropUploadMethod">
<img :src="imgDataUrl" alt class="logo me-2" :class="logoClass" />
<font-awesome-icon v-if="enableEditMode" class="icon-upload" icon="upload" />
</div>
<!-- Uploader -->
<!-- url="/api/status-page/upload-logo" -->
@ -376,6 +379,16 @@ export default {
axios.get("/api/status-page/config").then((res) => {
this.config = res.data;
if (this.config.logo) {
this.imgDataUrl = this.config.logo;
// Special handle for dev
const env = process.env.NODE_ENV;
if (env === "development" || localStorage.dev === "dev") {
let baseURL = location.protocol + "//" + location.hostname + ":3001";
this.imgDataUrl = baseURL + this.imgDataUrl;
}
}
});
axios.get("/api/status-page/incident").then((res) => {
@ -557,6 +570,30 @@ footer {
min-width: 50px;
}
.logo-wrapper {
display: inline-block;
position: relative;
&:hover {
.icon-upload {
transform: scale(1.2);
}
}
.icon-upload {
transition: all $easing-in 0.2s;
position: absolute;
bottom: 6px;
font-size: 20px;
left: -14px;
background-color: white;
padding: 5px;
border-radius: 10px;
cursor: pointer;
box-shadow: 0 15px 70px rgba(0, 0, 0, 0.9);
}
}
.logo {
transition: all $easing-in 0.2s;
@ -577,7 +614,7 @@ footer {
}
.date {
font-size: 14px;
font-size: 12px;
}
}