mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-09-11 22:06:59 +08:00
Compare commits
20 Commits
1.15.0-bet
...
1.15.0-bet
Author | SHA1 | Date | |
---|---|---|---|
|
7c13b1b6cb | ||
|
10f6a3c4f5 | ||
|
cd7c2beca6 | ||
|
8ee99760ec | ||
|
cb55e23718 | ||
|
200fdfb808 | ||
|
29d2d95c71 | ||
|
b782b25e17 | ||
|
919393cac9 | ||
|
17d4003e5c | ||
|
cefb5bb60a | ||
|
9bf3b3a0f4 | ||
|
e2c45f93bf | ||
|
addf75daa7 | ||
|
359a490ae3 | ||
|
cd38dd3f68 | ||
|
f712fe85e5 | ||
|
88604845e6 | ||
|
97a5b400db | ||
|
ca89f84b9a |
11
README.md
11
README.md
@@ -154,10 +154,17 @@ https://www.reddit.com/r/UptimeKuma/
|
||||
|
||||
## Contribute
|
||||
|
||||
### Test Beta Version
|
||||
|
||||
Check out the latest beta release here: https://github.com/louislam/uptime-kuma/releases
|
||||
|
||||
### Bug Reports / Feature Requests
|
||||
If you want to report a bug or request a new feature. Free feel to open a [new issue](https://github.com/louislam/uptime-kuma/issues).
|
||||
|
||||
## Translations
|
||||
If you want to translate Uptime Kuma into your language, please read: https://github.com/louislam/uptime-kuma/tree/master/src/languages
|
||||
|
||||
If you want to modify Uptime Kuma, this guideline may be useful for you: https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md
|
||||
Feel free to correct my grammar in this README, source code, or wiki, as my mother language is not English and my grammar is not that great.
|
||||
|
||||
Unfortunately, English proofreading is needed too because my grammar is not that great. Feel free to correct my grammar in this README, source code, or wiki.
|
||||
## Pull Requests
|
||||
If you want to modify Uptime Kuma, this guideline may be useful for you: https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md
|
||||
|
@@ -4,6 +4,7 @@ const Database = require("../server/database");
|
||||
const { R } = require("redbean-node");
|
||||
const readline = require("readline");
|
||||
const { initJWTSecret } = require("../server/util-server");
|
||||
const User = require("../server/model/user");
|
||||
const args = require("args-parser")(process.argv);
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
@@ -30,7 +31,7 @@ const main = async () => {
|
||||
let confirmPassword = await question("Confirm New Password: ");
|
||||
|
||||
if (password === confirmPassword) {
|
||||
await user.resetPassword(password);
|
||||
await User.resetPassword(user.id, password);
|
||||
|
||||
// Reset all sessions by reset jwt secret
|
||||
await initJWTSecret();
|
||||
|
1337
package-lock.json
generated
1337
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "uptime-kuma",
|
||||
"version": "1.15.0-beta.0",
|
||||
"version": "1.15.0-beta.1",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -37,7 +37,7 @@
|
||||
"build-docker-nightly-alpine": "docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly-alpine --target nightly . --push",
|
||||
"build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain",
|
||||
"upload-artifacts": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg VERSION --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain",
|
||||
"setup": "git checkout 1.14.0 && npm ci --production && npm run download-dist",
|
||||
"setup": "git checkout 1.14.1 && npm ci --production && npm run download-dist",
|
||||
"download-dist": "node extra/download-dist.js",
|
||||
"mark-as-nightly": "node extra/mark-as-nightly.js",
|
||||
"reset-password": "node extra/reset-password.js",
|
||||
@@ -62,7 +62,7 @@
|
||||
"@fortawesome/free-regular-svg-icons": "~5.15.4",
|
||||
"@fortawesome/free-solid-svg-icons": "~5.15.4",
|
||||
"@fortawesome/vue-fontawesome": "~3.0.0-5",
|
||||
"@louislam/sqlite3": "~6.0.1",
|
||||
"@louislam/sqlite3": "~15.0.3",
|
||||
"@popperjs/core": "~2.10.2",
|
||||
"args-parser": "~1.3.0",
|
||||
"axios": "~0.26.1",
|
||||
|
@@ -3,7 +3,8 @@
|
||||
*/
|
||||
const { TimeLogger } = require("../src/util");
|
||||
const { R } = require("redbean-node");
|
||||
const { io } = require("./server");
|
||||
const { UptimeKumaServer } = require("./uptime-kuma-server");
|
||||
const io = UptimeKumaServer.getInstance().io;
|
||||
const { setting } = require("./util-server");
|
||||
const checkVersion = require("./check-version");
|
||||
|
||||
|
@@ -5,17 +5,29 @@ const { R } = require("redbean-node");
|
||||
class User extends BeanModel {
|
||||
|
||||
/**
|
||||
* Direct execute, no need R.store()
|
||||
*
|
||||
* Fix #1510, as in the context reset-password.js, there is no auto model mapping. Call this static function instead.
|
||||
* @param userID
|
||||
* @param newPassword
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async resetPassword(userID, newPassword) {
|
||||
await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [
|
||||
passwordHash.generate(newPassword),
|
||||
userID
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param newPassword
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async resetPassword(newPassword) {
|
||||
await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [
|
||||
passwordHash.generate(newPassword),
|
||||
this.id
|
||||
]);
|
||||
await User.resetPassword(this.id, newPassword);
|
||||
this.password = newPassword;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = User;
|
||||
|
@@ -3,7 +3,7 @@ const HttpProxyAgent = require("http-proxy-agent");
|
||||
const HttpsProxyAgent = require("https-proxy-agent");
|
||||
const SocksProxyAgent = require("socks-proxy-agent");
|
||||
const { debug } = require("../src/util");
|
||||
const server = require("./server");
|
||||
const { UptimeKumaServer } = require("./uptime-kuma-server");
|
||||
|
||||
class Proxy {
|
||||
|
||||
@@ -151,6 +151,8 @@ class Proxy {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async reloadProxy() {
|
||||
const server = UptimeKumaServer.getInstance();
|
||||
|
||||
let updatedList = await R.getAssoc("SELECT id, proxy_id FROM monitor");
|
||||
|
||||
for (let monitorID in server.monitorList) {
|
||||
|
@@ -1,15 +1,16 @@
|
||||
let express = require("express");
|
||||
const { allowDevAllOrigin } = require("../util-server");
|
||||
const { R } = require("redbean-node");
|
||||
const server = require("../server");
|
||||
const apicache = require("../modules/apicache");
|
||||
const Monitor = require("../model/monitor");
|
||||
const dayjs = require("dayjs");
|
||||
const { UP, flipStatus, log } = require("../../src/util");
|
||||
const StatusPage = require("../model/status_page");
|
||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||
let router = express.Router();
|
||||
|
||||
let cache = apicache.middleware;
|
||||
const server = UptimeKumaServer.getInstance();
|
||||
let io = server.io;
|
||||
|
||||
router.get("/api/entry-page", async (request, response) => {
|
||||
|
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
* Uptime Kuma Server
|
||||
* node "server/server.js"
|
||||
* DO NOT require("./server") in other modules, it likely creates circular dependency!
|
||||
*/
|
||||
console.log("Welcome to Uptime Kuma");
|
||||
|
||||
// Check Node.js Version
|
||||
@@ -26,14 +31,10 @@ log.info("server", "Node Env: " + process.env.NODE_ENV);
|
||||
|
||||
log.info("server", "Importing Node libraries");
|
||||
const fs = require("fs");
|
||||
const http = require("http");
|
||||
const https = require("https");
|
||||
|
||||
log.info("server", "Importing 3rd-party libraries");
|
||||
log.debug("server", "Importing express");
|
||||
const express = require("express");
|
||||
log.debug("server", "Importing socket.io");
|
||||
const { Server } = require("socket.io");
|
||||
log.debug("server", "Importing redbean-node");
|
||||
const { R } = require("redbean-node");
|
||||
log.debug("server", "Importing jsonwebtoken");
|
||||
@@ -50,26 +51,10 @@ log.debug("server", "Importing 2FA Modules");
|
||||
const notp = require("notp");
|
||||
const base32 = require("thirty-two");
|
||||
|
||||
/**
|
||||
* `module.exports` (alias: `server`) should be inside this class, in order to avoid circular dependency issue.
|
||||
* @type {UptimeKumaServer}
|
||||
*/
|
||||
class UptimeKumaServer {
|
||||
/**
|
||||
* Main monitor list
|
||||
* @type {{}}
|
||||
*/
|
||||
monitorList = {};
|
||||
entryPage = "dashboard";
|
||||
|
||||
async sendMonitorList(socket) {
|
||||
let list = await getMonitorJSONList(socket.userID);
|
||||
io.to(socket.userID).emit("monitorList", list);
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
const server = module.exports = new UptimeKumaServer();
|
||||
const { UptimeKumaServer } = require("./uptime-kuma-server");
|
||||
const server = UptimeKumaServer.getInstance(args);
|
||||
const io = module.exports.io = server.io;
|
||||
const app = server.app;
|
||||
|
||||
log.info("server", "Importing this project modules");
|
||||
log.debug("server", "Importing Monitor");
|
||||
@@ -112,10 +97,7 @@ const port = [ args.port, process.env.UPTIME_KUMA_PORT, process.env.PORT, 3001 ]
|
||||
.map(portValue => parseInt(portValue))
|
||||
.find(portValue => !isNaN(portValue));
|
||||
|
||||
// SSL
|
||||
const sslKey = args["ssl-key"] || process.env.UPTIME_KUMA_SSL_KEY || process.env.SSL_KEY || undefined;
|
||||
const sslCert = args["ssl-cert"] || process.env.UPTIME_KUMA_SSL_CERT || process.env.SSL_CERT || undefined;
|
||||
const disableFrameSameOrigin = args["disable-frame-sameorigin"] || !!process.env.UPTIME_KUMA_DISABLE_FRAME_SAMEORIGIN || false;
|
||||
const disableFrameSameOrigin = !!process.env.UPTIME_KUMA_DISABLE_FRAME_SAMEORIGIN || args["disable-frame-sameorigin"] || false;
|
||||
const cloudflaredToken = args["cloudflared-token"] || process.env.UPTIME_KUMA_CLOUDFLARED_TOKEN || undefined;
|
||||
|
||||
// 2FA / notp verification defaults
|
||||
@@ -134,25 +116,6 @@ if (config.demoMode) {
|
||||
log.info("server", "==== Demo Mode ====");
|
||||
}
|
||||
|
||||
log.info("server", "Creating express and socket.io instance");
|
||||
const app = express();
|
||||
|
||||
let httpServer;
|
||||
|
||||
if (sslKey && sslCert) {
|
||||
log.info("server", "Server Type: HTTPS");
|
||||
httpServer = https.createServer({
|
||||
key: fs.readFileSync(sslKey),
|
||||
cert: fs.readFileSync(sslCert)
|
||||
}, app);
|
||||
} else {
|
||||
log.info("server", "Server Type: HTTP");
|
||||
httpServer = http.createServer(app);
|
||||
}
|
||||
|
||||
const io = new Server(httpServer);
|
||||
module.exports.io = io;
|
||||
|
||||
// Must be after io instantiation
|
||||
const { sendNotificationList, sendHeartbeatList, sendImportantHeartbeatList, sendInfo, sendProxyList } = require("./client");
|
||||
const { statusPageSocketHandler } = require("./socket-handlers/status-page-socket-handler");
|
||||
@@ -721,6 +684,7 @@ try {
|
||||
bean.pushToken = monitor.pushToken;
|
||||
bean.proxyId = Number.isInteger(monitor.proxyId) ? monitor.proxyId : null;
|
||||
bean.mqttUsername = monitor.mqttUsername;
|
||||
bean.mqttPassword = monitor.mqttPassword;
|
||||
bean.mqttTopic = monitor.mqttTopic;
|
||||
bean.mqttSuccessMessage = monitor.mqttSuccessMessage;
|
||||
|
||||
@@ -1482,12 +1446,12 @@ try {
|
||||
|
||||
log.info("server", "Init the server");
|
||||
|
||||
httpServer.once("error", async (err) => {
|
||||
server.httpServer.once("error", async (err) => {
|
||||
console.error("Cannot listen: " + err.message);
|
||||
await shutdownFunction();
|
||||
});
|
||||
|
||||
httpServer.listen(port, hostname, () => {
|
||||
server.httpServer.listen(port, hostname, () => {
|
||||
if (hostname) {
|
||||
log.info("server", `Listening on ${hostname}:${port}`);
|
||||
} else {
|
||||
@@ -1577,27 +1541,6 @@ async function afterLogin(socket, user) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of monitors for the given user.
|
||||
* @param {string} userID - The ID of the user to get monitors for.
|
||||
* @returns {Promise<Object>} A promise that resolves to an object with monitor IDs as keys and monitor objects as values.
|
||||
*
|
||||
* Generated by Trelent
|
||||
*/
|
||||
async function getMonitorJSONList(userID) {
|
||||
let result = {};
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the database and patch it if necessary.
|
||||
*
|
||||
@@ -1739,7 +1682,7 @@ function finalFunction() {
|
||||
log.info("server", "Graceful shutdown successful!");
|
||||
}
|
||||
|
||||
gracefulShutdown(httpServer, {
|
||||
gracefulShutdown(server.httpServer, {
|
||||
signals: "SIGINT SIGTERM",
|
||||
timeout: 30000, // timeout: 30 secs
|
||||
development: false, // not in dev mode
|
||||
|
@@ -1,6 +1,7 @@
|
||||
const { checkLogin, setSetting, setting, doubleCheckPassword } = require("../util-server");
|
||||
const { CloudflaredTunnel } = require("node-cloudflared-tunnel");
|
||||
const { io } = require("../server");
|
||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||
const io = UptimeKumaServer.getInstance().io;
|
||||
|
||||
const prefix = "cloudflared_";
|
||||
const cloudflared = new CloudflaredTunnel();
|
||||
@@ -86,5 +87,7 @@ module.exports.autoStart = async (token) => {
|
||||
|
||||
module.exports.stop = async () => {
|
||||
console.log("Stop cloudflared");
|
||||
cloudflared.stop();
|
||||
if (cloudflared) {
|
||||
cloudflared.stop();
|
||||
}
|
||||
};
|
||||
|
@@ -1,7 +1,8 @@
|
||||
const { checkLogin } = require("../util-server");
|
||||
const { Proxy } = require("../proxy");
|
||||
const { sendProxyList } = require("../client");
|
||||
const server = require("../server");
|
||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||
const server = UptimeKumaServer.getInstance();
|
||||
|
||||
module.exports.proxySocketHandler = (socket) => {
|
||||
socket.on("addProxy", async (proxy, proxyID, callback) => {
|
||||
|
@@ -6,7 +6,7 @@ const ImageDataURI = require("../image-data-uri");
|
||||
const Database = require("../database");
|
||||
const apicache = require("../modules/apicache");
|
||||
const StatusPage = require("../model/status_page");
|
||||
const server = require("../server");
|
||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||
|
||||
module.exports.statusPageSocketHandler = (socket) => {
|
||||
|
||||
@@ -215,6 +215,8 @@ module.exports.statusPageSocketHandler = (socket) => {
|
||||
];
|
||||
await R.exec(`DELETE FROM \`group\` WHERE id NOT IN (${slots}) AND status_page_id = ?`, data);
|
||||
|
||||
const server = UptimeKumaServer.getInstance();
|
||||
|
||||
// Also change entry page to new slug if it is the default one, and slug is changed.
|
||||
if (server.entryPage === "statusPage-" + slug && statusPage.slug !== slug) {
|
||||
server.entryPage = "statusPage-" + statusPage.slug;
|
||||
@@ -284,6 +286,8 @@ module.exports.statusPageSocketHandler = (socket) => {
|
||||
|
||||
// Delete a status page
|
||||
socket.on("deleteStatusPage", async (slug, callback) => {
|
||||
const server = UptimeKumaServer.getInstance();
|
||||
|
||||
try {
|
||||
checkLogin(socket);
|
||||
|
||||
|
90
server/uptime-kuma-server.js
Normal file
90
server/uptime-kuma-server.js
Normal file
@@ -0,0 +1,90 @@
|
||||
const express = require("express");
|
||||
const https = require("https");
|
||||
const fs = require("fs");
|
||||
const http = require("http");
|
||||
const { Server } = require("socket.io");
|
||||
const { R } = require("redbean-node");
|
||||
const { log } = require("../src/util");
|
||||
|
||||
/**
|
||||
* `module.exports` (alias: `server`) should be inside this class, in order to avoid circular dependency issue.
|
||||
* @type {UptimeKumaServer}
|
||||
*/
|
||||
class UptimeKumaServer {
|
||||
|
||||
/**
|
||||
*
|
||||
* @type {UptimeKumaServer}
|
||||
*/
|
||||
static instance = null;
|
||||
|
||||
/**
|
||||
* Main monitor list
|
||||
* @type {{}}
|
||||
*/
|
||||
monitorList = {};
|
||||
entryPage = "dashboard";
|
||||
app = undefined;
|
||||
httpServer = undefined;
|
||||
io = undefined;
|
||||
|
||||
static getInstance(args) {
|
||||
if (UptimeKumaServer.instance == null) {
|
||||
UptimeKumaServer.instance = new UptimeKumaServer(args);
|
||||
}
|
||||
return UptimeKumaServer.instance;
|
||||
}
|
||||
|
||||
constructor(args) {
|
||||
// SSL
|
||||
const sslKey = args["ssl-key"] || process.env.UPTIME_KUMA_SSL_KEY || process.env.SSL_KEY || undefined;
|
||||
const sslCert = args["ssl-cert"] || process.env.UPTIME_KUMA_SSL_CERT || process.env.SSL_CERT || undefined;
|
||||
|
||||
log.info("server", "Creating express and socket.io instance");
|
||||
this.app = express();
|
||||
|
||||
if (sslKey && sslCert) {
|
||||
log.info("server", "Server Type: HTTPS");
|
||||
this.httpServer = https.createServer({
|
||||
key: fs.readFileSync(sslKey),
|
||||
cert: fs.readFileSync(sslCert)
|
||||
}, this.app);
|
||||
} else {
|
||||
log.info("server", "Server Type: HTTP");
|
||||
this.httpServer = http.createServer(this.app);
|
||||
}
|
||||
|
||||
this.io = new Server(this.httpServer);
|
||||
}
|
||||
|
||||
async sendMonitorList(socket) {
|
||||
let list = await this.getMonitorJSONList(socket.userID);
|
||||
this.io.to(socket.userID).emit("monitorList", list);
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of monitors for the given user.
|
||||
* @param {string} userID - The ID of the user to get monitors for.
|
||||
* @returns {Promise<Object>} A promise that resolves to an object with monitor IDs as keys and monitor objects as values.
|
||||
*
|
||||
* Generated by Trelent
|
||||
*/
|
||||
async getMonitorJSONList(userID) {
|
||||
let result = {};
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
UptimeKumaServer
|
||||
};
|
@@ -135,10 +135,10 @@ exports.mqttAsync = function (hostname, topic, okMessage, options = {}) {
|
||||
if (messageTopic == topic) {
|
||||
client.end();
|
||||
clearTimeout(timeoutID);
|
||||
if (message.toString() === okMessage) {
|
||||
resolve(`Topic: ${messageTopic}; Message: ${message.toString()}`);
|
||||
if (okMessage != null && okMessage !== "" && message.toString() !== okMessage) {
|
||||
reject(new Error(`Message Mismatch - Topic: ${messageTopic}; Message: ${message.toString()}`));
|
||||
} else {
|
||||
reject(new Error(`Error; Topic: ${messageTopic}; Message: ${message.toString()}`));
|
||||
resolve(`Topic: ${messageTopic}; Message: ${message.toString()}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -4,7 +4,7 @@
|
||||
<!-- Change Password -->
|
||||
<template v-if="!settings.disableAuth">
|
||||
<p>
|
||||
{{ $t("Current User") }}: <strong>{{ username }}</strong>
|
||||
{{ $t("Current User") }}: <strong>{{ $root.username }}</strong>
|
||||
<button v-if="! settings.disableAuth" id="logout-btn" class="btn btn-danger ms-4 me-2 mb-2" @click="$root.logout">{{ $t("Logout") }}</button>
|
||||
</p>
|
||||
|
||||
@@ -269,7 +269,6 @@ export default {
|
||||
|
||||
data() {
|
||||
return {
|
||||
username: "",
|
||||
invalidPassword: false,
|
||||
password: {
|
||||
currentPassword: "",
|
||||
@@ -297,10 +296,6 @@ export default {
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.loadUsername();
|
||||
},
|
||||
|
||||
methods: {
|
||||
savePassword() {
|
||||
if (this.password.newPassword !== this.password.repeatNewPassword) {
|
||||
@@ -319,14 +314,6 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
loadUsername() {
|
||||
const jwtPayload = this.$root.getJWTPayload();
|
||||
|
||||
if (jwtPayload) {
|
||||
this.username = jwtPayload.username;
|
||||
}
|
||||
},
|
||||
|
||||
disableAuth() {
|
||||
this.settings.disableAuth = true;
|
||||
|
||||
|
@@ -34,11 +34,13 @@ import {
|
||||
faAward,
|
||||
faLink,
|
||||
faChevronDown,
|
||||
faSignOutAlt,
|
||||
faPen,
|
||||
faExternalLinkSquareAlt,
|
||||
faSpinner,
|
||||
faUndo,
|
||||
faPlusCircle,
|
||||
faAngleDown,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
library.add(
|
||||
@@ -72,11 +74,13 @@ library.add(
|
||||
faAward,
|
||||
faLink,
|
||||
faChevronDown,
|
||||
faSignOutAlt,
|
||||
faPen,
|
||||
faExternalLinkSquareAlt,
|
||||
faSpinner,
|
||||
faUndo,
|
||||
faPlusCircle,
|
||||
faAngleDown,
|
||||
);
|
||||
|
||||
export { FontAwesomeIcon };
|
||||
|
@@ -32,9 +32,27 @@
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if="$root.loggedIn" class="nav-item">
|
||||
<router-link to="/settings" class="nav-link" :class="{ active: $route.path.includes('settings') }">
|
||||
<font-awesome-icon icon="cog" /> {{ $t("Settings") }}
|
||||
</router-link>
|
||||
<div class="dropdown dropdown-profile-pic">
|
||||
<div type="button" class="nav-link" data-bs-toggle="dropdown">
|
||||
<div class="profile-pic">{{ $root.usernameFirstChar }}</div>
|
||||
<font-awesome-icon icon="angle-down" />
|
||||
</div>
|
||||
<ul class="dropdown-menu">
|
||||
<li><span class="dropdown-item-text">Signed in as <strong>{{ $root.username }}</strong></span></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<router-link to="/settings" class="dropdown-item" :class="{ active: $route.path.includes('settings') }">
|
||||
<font-awesome-icon icon="cog" /> {{ $t("Settings") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if="$root.loggedIn && $root.storage().token !== 'autoLogin'">
|
||||
<button class="dropdown-item" @click="$root.logout">
|
||||
<font-awesome-icon icon="sign-out-alt" />
|
||||
{{ $t("Logout") }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</header>
|
||||
@@ -192,6 +210,79 @@ main {
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
// Profile Pic Button with Dropdown
|
||||
.dropdown-profile-pic {
|
||||
user-select: none;
|
||||
|
||||
.nav-link {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
background-color: rgba(200, 200, 200, 0.2);
|
||||
padding: 0.5rem 0.8rem;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
transition: all 0.2s;
|
||||
padding-left: 0;
|
||||
margin-top: 8px !important;
|
||||
border-radius: 20px;
|
||||
|
||||
.dropdown-divider {
|
||||
margin: 0;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.4);
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.dropdown-item-text {
|
||||
font-size: 14px;
|
||||
padding-bottom: 0.7rem;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
padding: 0.7rem 1rem;
|
||||
}
|
||||
|
||||
.dark & {
|
||||
background-color: $dark-bg;
|
||||
color: $dark-font-color;
|
||||
border-color: $dark-border-color;
|
||||
|
||||
.dropdown-item {
|
||||
color: $dark-font-color;
|
||||
|
||||
&.active {
|
||||
color: $dark-font-color2;
|
||||
background-color: $highlight !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $dark-bg2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.profile-pic {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
background-color: $primary;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 5px;
|
||||
border-radius: 50rem;
|
||||
font-weight: bold;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
header {
|
||||
background-color: $dark-header-bg;
|
||||
|
@@ -28,6 +28,7 @@ export default {
|
||||
connectCount: 0,
|
||||
initedSocketIO: false,
|
||||
},
|
||||
username: null,
|
||||
remember: (localStorage.remember !== "0"),
|
||||
allowLoginDialog: false, // Allowed to show login dialog, but "loggedIn" have to be true too. This exists because prevent the login dialog show 0.1s in first before the socket server auth-ed.
|
||||
loggedIn: false,
|
||||
@@ -102,6 +103,7 @@ export default {
|
||||
|
||||
socket.on("autoLogin", (monitorID, data) => {
|
||||
this.loggedIn = true;
|
||||
this.username = "No Auth";
|
||||
this.storage().token = "autoLogin";
|
||||
this.allowLoginDialog = false;
|
||||
});
|
||||
@@ -233,7 +235,6 @@ export default {
|
||||
if (token !== "autoLogin") {
|
||||
this.loginByToken(token);
|
||||
} else {
|
||||
|
||||
// Timeout if it is not actually auto login
|
||||
setTimeout(() => {
|
||||
if (! this.loggedIn) {
|
||||
@@ -241,7 +242,6 @@ export default {
|
||||
this.$root.storage().removeItem("token");
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
}
|
||||
} else {
|
||||
this.allowLoginDialog = true;
|
||||
@@ -305,6 +305,7 @@ export default {
|
||||
this.storage().token = res.token;
|
||||
this.socket.token = res.token;
|
||||
this.loggedIn = true;
|
||||
this.username = this.getJWTPayload()?.username;
|
||||
|
||||
// Trigger Chrome Save Password
|
||||
history.pushState({}, "");
|
||||
@@ -322,6 +323,7 @@ export default {
|
||||
this.logout();
|
||||
} else {
|
||||
this.loggedIn = true;
|
||||
this.username = this.getJWTPayload()?.username;
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -331,6 +333,7 @@ export default {
|
||||
this.storage().removeItem("token");
|
||||
this.socket.token = null;
|
||||
this.loggedIn = false;
|
||||
this.username = null;
|
||||
this.clearData();
|
||||
},
|
||||
|
||||
@@ -398,6 +401,14 @@ export default {
|
||||
|
||||
computed: {
|
||||
|
||||
usernameFirstChar() {
|
||||
if (typeof this.username == "string" && this.username.length >= 1) {
|
||||
return this.username.charAt(0).toUpperCase();
|
||||
} else {
|
||||
return "🐻";
|
||||
}
|
||||
},
|
||||
|
||||
lastHeartbeatList() {
|
||||
let result = {};
|
||||
|
||||
|
@@ -141,7 +141,7 @@
|
||||
|
||||
<div class="my-3">
|
||||
<label for="mqttSuccessMessage" class="form-label">MQTT {{ $t("successMessage") }}</label>
|
||||
<input id="mqttSuccessMessage" v-model="monitor.mqttSuccessMessage" type="text" class="form-control" required>
|
||||
<input id="mqttSuccessMessage" v-model="monitor.mqttSuccessMessage" type="text" class="form-control">
|
||||
<div class="form-text">
|
||||
{{ $t("successMessageExplanation") }}
|
||||
</div>
|
||||
|
@@ -16,6 +16,14 @@
|
||||
{{ item.title }}
|
||||
</div>
|
||||
</router-link>
|
||||
|
||||
<!-- Logout Button -->
|
||||
<a v-if="$root.isMobile && $root.loggedIn && $root.storage().token !== 'autoLogin'" class="logout" @click.prevent="$root.logout">
|
||||
<div class="menu-item">
|
||||
<font-awesome-icon icon="sign-out-alt" />
|
||||
{{ $t("Logout") }}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="settings-content col-lg-9 col-md-7">
|
||||
<div v-if="currentPage" class="settings-content-header">
|
||||
@@ -233,4 +241,8 @@ footer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.logout {
|
||||
color: $danger !important;
|
||||
}
|
||||
</style>
|
||||
|
Reference in New Issue
Block a user