mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-02-26 05:25:56 +00:00
Compare commits
6 commits
3edb35080b
...
dca8260a34
Author | SHA1 | Date | |
---|---|---|---|
|
dca8260a34 | ||
|
be6e5211e5 | ||
|
5ee986c58e | ||
|
ca094296f2 | ||
|
49edf0d830 | ||
|
86ee98e0e8 |
15 changed files with 367 additions and 240 deletions
|
@ -1,4 +1,4 @@
|
||||||
name: json-yaml-validate
|
name: validate
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
|
@ -26,7 +26,8 @@ jobs:
|
||||||
comment: "true" # enable comment mode
|
comment: "true" # enable comment mode
|
||||||
exclude_file: ".github/config/exclude.txt" # gitignore style file for exclusions
|
exclude_file: ".github/config/exclude.txt" # gitignore style file for exclusions
|
||||||
|
|
||||||
check-lang-json:
|
# General validations
|
||||||
|
validate:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
@ -34,4 +35,9 @@ jobs:
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- run: node ./extra/check-lang-json.js
|
|
||||||
|
- name: Validate language JSON files
|
||||||
|
run: node ./extra/check-lang-json.js
|
||||||
|
|
||||||
|
- name: Validate knex migrations filename
|
||||||
|
run: node ./extra/check-knex-filenames.mjs
|
65
extra/change-username.js
Normal file
65
extra/change-username.js
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
console.log("== Uptime Kuma Change Username Tool ==");
|
||||||
|
|
||||||
|
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,
|
||||||
|
output: process.stdout
|
||||||
|
});
|
||||||
|
|
||||||
|
const main = async () => {
|
||||||
|
console.log("Connecting the database");
|
||||||
|
Database.init(args);
|
||||||
|
await Database.connect(false, false, true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// No need to actually reset the password for testing, just make sure no connection problem. It is ok for now.
|
||||||
|
if (!process.env.TEST_BACKEND) {
|
||||||
|
const user = await R.findOne("user");
|
||||||
|
if (! user) {
|
||||||
|
throw new Error("user not found, have you installed?");
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Found user: " + user.username);
|
||||||
|
let newUsername = await question("New username: ");
|
||||||
|
await User.updateUsername(user.id, newUsername);
|
||||||
|
|
||||||
|
// Reset all sessions by reset jwt secret
|
||||||
|
await initJWTSecret();
|
||||||
|
|
||||||
|
console.log("Username change successfully.");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error: " + e.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Database.close();
|
||||||
|
rl.close();
|
||||||
|
|
||||||
|
console.log("Finished.");
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ask question of user
|
||||||
|
* @param {string} question Question to ask
|
||||||
|
* @returns {Promise<string>} Users response
|
||||||
|
*/
|
||||||
|
function question(question) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
rl.question(question, (answer) => {
|
||||||
|
resolve(answer);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!process.env.TEST_BACKEND) {
|
||||||
|
main();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
main,
|
||||||
|
};
|
72
extra/check-knex-filenames.mjs
Normal file
72
extra/check-knex-filenames.mjs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
import fs from "fs";
|
||||||
|
const dir = "./db/knex_migrations";
|
||||||
|
|
||||||
|
// Get the file list (ending with .js) from the directory
|
||||||
|
const files = fs.readdirSync(dir).filter((file) => file !== "README.md");
|
||||||
|
|
||||||
|
// They are wrong, but they had been merged, so allowed.
|
||||||
|
const exceptionList = [
|
||||||
|
"2024-08-24-000-add-cache-bust.js",
|
||||||
|
"2024-10-1315-rabbitmq-monitor.js",
|
||||||
|
];
|
||||||
|
|
||||||
|
// Correct format: YYYY-MM-DD-HHmm-description.js
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
if (exceptionList.includes(file)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check ending with .js
|
||||||
|
if (!file.endsWith(".js")) {
|
||||||
|
console.error(`It should end with .js: ${file}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = file.split("-");
|
||||||
|
|
||||||
|
// Should be at least 5 parts
|
||||||
|
if (parts.length < 5) {
|
||||||
|
console.error(`Invalid format: ${file}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// First part should be a year >= 2024
|
||||||
|
const year = parseInt(parts[0], 10);
|
||||||
|
if (isNaN(year) || year < 2023) {
|
||||||
|
console.error(`Invalid year: ${file}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second part should be a month
|
||||||
|
const month = parseInt(parts[1], 10);
|
||||||
|
if (isNaN(month) || month < 1 || month > 12) {
|
||||||
|
console.error(`Invalid month: ${file}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Third part should be a day
|
||||||
|
const day = parseInt(parts[2], 10);
|
||||||
|
if (isNaN(day) || day < 1 || day > 31) {
|
||||||
|
console.error(`Invalid day: ${file}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fourth part should be HHmm
|
||||||
|
const time = parts[3];
|
||||||
|
|
||||||
|
// Check length is 4
|
||||||
|
if (time.length !== 4) {
|
||||||
|
console.error(`Invalid time: ${file}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const hour = parseInt(time.substring(0, 2), 10);
|
||||||
|
const minute = parseInt(time.substring(2), 10);
|
||||||
|
if (isNaN(hour) || hour < 0 || hour > 23 || isNaN(minute) || minute < 0 || minute > 59) {
|
||||||
|
console.error(`Invalid time: ${file}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("All knex filenames are correct.");
|
62
package-lock.json
generated
62
package-lock.json
generated
|
@ -46,7 +46,7 @@
|
||||||
"jsonwebtoken": "~9.0.0",
|
"jsonwebtoken": "~9.0.0",
|
||||||
"jwt-decode": "~3.1.2",
|
"jwt-decode": "~3.1.2",
|
||||||
"kafkajs": "^2.2.4",
|
"kafkajs": "^2.2.4",
|
||||||
"knex": "^2.4.2",
|
"knex": "~3.1.0",
|
||||||
"limiter": "~2.1.0",
|
"limiter": "~2.1.0",
|
||||||
"liquidjs": "^10.7.0",
|
"liquidjs": "^10.7.0",
|
||||||
"marked": "^14.0.0",
|
"marked": "^14.0.0",
|
||||||
|
@ -54,7 +54,7 @@
|
||||||
"mongodb": "~4.17.1",
|
"mongodb": "~4.17.1",
|
||||||
"mqtt": "~4.3.7",
|
"mqtt": "~4.3.7",
|
||||||
"mssql": "~11.0.0",
|
"mssql": "~11.0.0",
|
||||||
"mysql2": "~3.9.6",
|
"mysql2": "~3.11.3",
|
||||||
"nanoid": "~3.3.4",
|
"nanoid": "~3.3.4",
|
||||||
"net-snmp": "^3.11.2",
|
"net-snmp": "^3.11.2",
|
||||||
"node-cloudflared-tunnel": "~1.0.9",
|
"node-cloudflared-tunnel": "~1.0.9",
|
||||||
|
@ -5655,6 +5655,15 @@
|
||||||
"integrity": "sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==",
|
"integrity": "sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/aws-ssl-profiles": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "0.28.1",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.28.1.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-0.28.1.tgz",
|
||||||
|
@ -10915,9 +10924,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/knex": {
|
"node_modules/knex": {
|
||||||
"version": "2.5.1",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/knex/-/knex-2.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/knex/-/knex-3.1.0.tgz",
|
||||||
"integrity": "sha512-z78DgGKUr4SE/6cm7ku+jHvFT0X97aERh/f0MUKAKgFnwCYBEW4TFBqtHWFYiJFid7fMrtpZ/gxJthvz5mEByA==",
|
"integrity": "sha512-GLoII6hR0c4ti243gMs5/1Rb3B+AjwMOfjYm97pu0FOQa7JH56hgBxYf5WK2525ceSbBY1cjeZ9yk99GPMB6Kw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"colorette": "2.0.19",
|
"colorette": "2.0.19",
|
||||||
|
@ -10929,7 +10938,7 @@
|
||||||
"getopts": "2.3.0",
|
"getopts": "2.3.0",
|
||||||
"interpret": "^2.2.0",
|
"interpret": "^2.2.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"pg-connection-string": "2.6.1",
|
"pg-connection-string": "2.6.2",
|
||||||
"rechoir": "^0.8.0",
|
"rechoir": "^0.8.0",
|
||||||
"resolve-from": "^5.0.0",
|
"resolve-from": "^5.0.0",
|
||||||
"tarn": "^3.0.2",
|
"tarn": "^3.0.2",
|
||||||
|
@ -10939,7 +10948,7 @@
|
||||||
"knex": "bin/cli.js"
|
"knex": "bin/cli.js"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=16"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"better-sqlite3": {
|
"better-sqlite3": {
|
||||||
|
@ -10989,9 +10998,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/knex/node_modules/pg-connection-string": {
|
"node_modules/knex/node_modules/pg-connection-string": {
|
||||||
"version": "2.6.1",
|
"version": "2.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz",
|
||||||
"integrity": "sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg==",
|
"integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/knex/node_modules/resolve-from": {
|
"node_modules/knex/node_modules/resolve-from": {
|
||||||
|
@ -11242,6 +11251,21 @@
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lru.min": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-FbAj6lXil6t8z4z3j0E5mfRlPzxkySotzUHwRXjlpRh10vc6AI6WN62ehZj82VG7M20rqogJ0GLwar2Xa05a8Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"bun": ">=1.0.0",
|
||||||
|
"deno": ">=1.30.0",
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/wellwelwel"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/magic-string": {
|
"node_modules/magic-string": {
|
||||||
"version": "0.30.12",
|
"version": "0.30.12",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz",
|
||||||
|
@ -11917,16 +11941,17 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mysql2": {
|
"node_modules/mysql2": {
|
||||||
"version": "3.9.9",
|
"version": "3.11.3",
|
||||||
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.9.9.tgz",
|
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.11.3.tgz",
|
||||||
"integrity": "sha512-Qtb2RUxwWMFkWXqF7Rd/7ySkupbQnNY7O0zQuQYgPcuJZ06M36JG3HIDEh/pEeq7LImcvA6O3lOVQ9XQK+HEZg==",
|
"integrity": "sha512-Qpu2ADfbKzyLdwC/5d4W7+5Yz7yBzCU05YWt5npWzACST37wJsB23wgOSo00qi043urkiRwXtEvJc9UnuLX/MQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"aws-ssl-profiles": "^1.1.1",
|
||||||
"denque": "^2.1.0",
|
"denque": "^2.1.0",
|
||||||
"generate-function": "^2.3.1",
|
"generate-function": "^2.3.1",
|
||||||
"iconv-lite": "^0.6.3",
|
"iconv-lite": "^0.6.3",
|
||||||
"long": "^5.2.1",
|
"long": "^5.2.1",
|
||||||
"lru-cache": "^8.0.0",
|
"lru.min": "^1.0.0",
|
||||||
"named-placeholders": "^1.1.3",
|
"named-placeholders": "^1.1.3",
|
||||||
"seq-queue": "^0.0.5",
|
"seq-queue": "^0.0.5",
|
||||||
"sqlstring": "^2.3.2"
|
"sqlstring": "^2.3.2"
|
||||||
|
@ -11935,15 +11960,6 @@
|
||||||
"node": ">= 8.0"
|
"node": ">= 8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mysql2/node_modules/lru-cache": {
|
|
||||||
"version": "8.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz",
|
|
||||||
"integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==",
|
|
||||||
"license": "ISC",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=16.14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/named-placeholders": {
|
"node_modules/named-placeholders": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz",
|
||||||
|
|
|
@ -50,6 +50,7 @@
|
||||||
"setup": "git checkout 1.23.14 && npm ci --production && npm run download-dist",
|
"setup": "git checkout 1.23.14 && npm ci --production && npm run download-dist",
|
||||||
"download-dist": "node extra/download-dist.js",
|
"download-dist": "node extra/download-dist.js",
|
||||||
"mark-as-nightly": "node extra/mark-as-nightly.js",
|
"mark-as-nightly": "node extra/mark-as-nightly.js",
|
||||||
|
"change-username": "node extra/change-username.js",
|
||||||
"reset-password": "node extra/reset-password.js",
|
"reset-password": "node extra/reset-password.js",
|
||||||
"remove-2fa": "node extra/remove-2fa.js",
|
"remove-2fa": "node extra/remove-2fa.js",
|
||||||
"simple-dns-server": "node extra/simple-dns-server.js",
|
"simple-dns-server": "node extra/simple-dns-server.js",
|
||||||
|
@ -109,7 +110,7 @@
|
||||||
"jsonwebtoken": "~9.0.0",
|
"jsonwebtoken": "~9.0.0",
|
||||||
"jwt-decode": "~3.1.2",
|
"jwt-decode": "~3.1.2",
|
||||||
"kafkajs": "^2.2.4",
|
"kafkajs": "^2.2.4",
|
||||||
"knex": "^2.4.2",
|
"knex": "~3.1.0",
|
||||||
"limiter": "~2.1.0",
|
"limiter": "~2.1.0",
|
||||||
"liquidjs": "^10.7.0",
|
"liquidjs": "^10.7.0",
|
||||||
"marked": "^14.0.0",
|
"marked": "^14.0.0",
|
||||||
|
@ -117,7 +118,7 @@
|
||||||
"mongodb": "~4.17.1",
|
"mongodb": "~4.17.1",
|
||||||
"mqtt": "~4.3.7",
|
"mqtt": "~4.3.7",
|
||||||
"mssql": "~11.0.0",
|
"mssql": "~11.0.0",
|
||||||
"mysql2": "~3.9.6",
|
"mysql2": "~3.11.3",
|
||||||
"nanoid": "~3.3.4",
|
"nanoid": "~3.3.4",
|
||||||
"net-snmp": "^3.11.2",
|
"net-snmp": "^3.11.2",
|
||||||
"node-cloudflared-tunnel": "~1.0.9",
|
"node-cloudflared-tunnel": "~1.0.9",
|
||||||
|
|
|
@ -7,6 +7,9 @@ const { loginRateLimiter, apiRateLimiter } = require("./rate-limiter");
|
||||||
const { Settings } = require("./settings");
|
const { Settings } = require("./settings");
|
||||||
const dayjs = require("dayjs");
|
const dayjs = require("dayjs");
|
||||||
|
|
||||||
|
const remoteAuthEnabled = process.env.REMOTE_AUTH_ENABLED || false;
|
||||||
|
const remoteAuthHeader = process.env.REMOTE_AUTH_HEADER || "Remote-User";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Login to web app
|
* Login to web app
|
||||||
* @param {string} username Username to login with
|
* @param {string} username Username to login with
|
||||||
|
@ -133,29 +136,40 @@ function userAuthorizer(username, password, callback) {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
exports.basicAuth = async function (req, res, next) {
|
exports.basicAuth = async function (req, res, next) {
|
||||||
const middleware = basicAuth({
|
|
||||||
authorizer: userAuthorizer,
|
|
||||||
authorizeAsync: true,
|
|
||||||
challenge: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const disabledAuth = await setting("disableAuth");
|
const disabledAuth = await setting("disableAuth");
|
||||||
|
|
||||||
if (!disabledAuth) {
|
if (remoteAuthEnabled) {
|
||||||
middleware(req, res, next);
|
const remoteUser = req.headers[remoteAuthHeader.toLowerCase()];
|
||||||
} else {
|
if (remoteUser !== undefined) {
|
||||||
next();
|
let user = await R.findOne("user", " username = ? AND active = 1 ", [ remoteUser ]);
|
||||||
|
if (user) {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!disabledAuth) {
|
||||||
|
const middleware = basicAuth({
|
||||||
|
authorizer: userAuthorizer,
|
||||||
|
authorizeAsync: true,
|
||||||
|
challenge: true,
|
||||||
|
});
|
||||||
|
middleware(req, res, next);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use use API Key if API keys enabled, else use basic auth
|
* Use API Key if API keys enabled, else use basic auth
|
||||||
* @param {express.Request} req Express request object
|
* @param {express.Request} req Express request object
|
||||||
* @param {express.Response} res Express response object
|
* @param {express.Response} res Express response object
|
||||||
* @param {express.NextFunction} next Next handler in chain
|
* @param {express.NextFunction} next Next handler in chain
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
exports.apiAuth = async function (req, res, next) {
|
exports.authMiddleware = async function (req, res, next) {
|
||||||
if (!await Settings.get("disableAuth")) {
|
if (!await Settings.get("disableAuth")) {
|
||||||
let usingAPIKeys = await Settings.get("apiKeysEnabled");
|
let usingAPIKeys = await Settings.get("apiKeysEnabled");
|
||||||
let middleware;
|
let middleware;
|
||||||
|
|
|
@ -10,6 +10,7 @@ const { Settings } = require("./settings");
|
||||||
const { UptimeCalculator } = require("./uptime-calculator");
|
const { UptimeCalculator } = require("./uptime-calculator");
|
||||||
const dayjs = require("dayjs");
|
const dayjs = require("dayjs");
|
||||||
const { SimpleMigrationServer } = require("./utils/simple-migration-server");
|
const { SimpleMigrationServer } = require("./utils/simple-migration-server");
|
||||||
|
const KumaColumnCompiler = require("./utils/knex/lib/dialects/mysql2/schema/mysql2-columncompiler");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Database & App Data Folder
|
* Database & App Data Folder
|
||||||
|
@ -198,6 +199,14 @@ class Database {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
static async connect(testMode = false, autoloadModels = true, noLog = false) {
|
static async connect(testMode = false, autoloadModels = true, noLog = false) {
|
||||||
|
// Patch "mysql2" knex client
|
||||||
|
// Workaround: Tried extending the ColumnCompiler class, but it didn't work for unknown reasons, so I override the function via prototype
|
||||||
|
const { getDialectByNameOrAlias } = require("knex/lib/dialects");
|
||||||
|
const mysql2 = getDialectByNameOrAlias("mysql2");
|
||||||
|
mysql2.prototype.columnCompiler = function () {
|
||||||
|
return new KumaColumnCompiler(this, ...arguments);
|
||||||
|
};
|
||||||
|
|
||||||
const acquireConnectionTimeout = 120 * 1000;
|
const acquireConnectionTimeout = 120 * 1000;
|
||||||
let dbConfig;
|
let dbConfig;
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -48,6 +48,17 @@ class User extends BeanModel {
|
||||||
}, jwtSecret);
|
}, jwtSecret);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} userID ID of user to update
|
||||||
|
* @param {string} newUsername Users new username
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
static async updateUsername(userID, newUsername) {
|
||||||
|
await R.exec("UPDATE `user` SET username = ? WHERE id = ? ", [
|
||||||
|
newUsername,
|
||||||
|
userID
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = User;
|
module.exports = User;
|
||||||
|
|
|
@ -104,12 +104,14 @@ log.debug("server", "Importing Background Jobs");
|
||||||
const { initBackgroundJobs, stopBackgroundJobs } = require("./jobs");
|
const { initBackgroundJobs, stopBackgroundJobs } = require("./jobs");
|
||||||
const { loginRateLimiter, twoFaRateLimiter } = require("./rate-limiter");
|
const { loginRateLimiter, twoFaRateLimiter } = require("./rate-limiter");
|
||||||
|
|
||||||
const { apiAuth } = require("./auth");
|
const { authMiddleware } = require("./auth");
|
||||||
const { login } = require("./auth");
|
const { login } = require("./auth");
|
||||||
const passwordHash = require("./password-hash");
|
const passwordHash = require("./password-hash");
|
||||||
|
|
||||||
const hostname = config.hostname;
|
const remoteAuthEnabled = process.env.REMOTE_AUTH_ENABLED || false;
|
||||||
|
const remoteAuthHeader = process.env.REMOTE_AUTH_HEADER || "Remote-User";
|
||||||
|
|
||||||
|
const hostname = config.hostname;
|
||||||
if (hostname) {
|
if (hostname) {
|
||||||
log.info("server", "Custom hostname: " + hostname);
|
log.info("server", "Custom hostname: " + hostname);
|
||||||
}
|
}
|
||||||
|
@ -292,7 +294,7 @@ let needSetup = false;
|
||||||
|
|
||||||
// Prometheus API metrics /metrics
|
// Prometheus API metrics /metrics
|
||||||
// With Basic Auth using the first user's username/password
|
// With Basic Auth using the first user's username/password
|
||||||
app.get("/metrics", apiAuth, prometheusAPIMetrics());
|
app.get("/metrics", authMiddleware, prometheusAPIMetrics());
|
||||||
|
|
||||||
app.use("/", expressStaticGzip("dist", {
|
app.use("/", expressStaticGzip("dist", {
|
||||||
enableBrotli: true,
|
enableBrotli: true,
|
||||||
|
@ -1583,10 +1585,26 @@ let needSetup = false;
|
||||||
// ***************************
|
// ***************************
|
||||||
|
|
||||||
log.debug("auth", "check auto login");
|
log.debug("auth", "check auto login");
|
||||||
if (await setting("disableAuth")) {
|
if (await Settings.get("disableAuth")) {
|
||||||
log.info("auth", "Disabled Auth: auto login to admin");
|
log.info("auth", "Disabled Auth: auto login to admin");
|
||||||
await afterLogin(socket, await R.findOne("user"));
|
await afterLogin(socket, await R.findOne("user"));
|
||||||
socket.emit("autoLogin");
|
socket.emit("autoLogin");
|
||||||
|
} else if (remoteAuthEnabled) {
|
||||||
|
log.debug("auth", socket.handshake.headers);
|
||||||
|
const remoteUser = socket.handshake.headers[remoteAuthHeader.toLowerCase()];
|
||||||
|
if (remoteUser !== undefined) {
|
||||||
|
const user = await R.findOne("user", " username = ? AND active = 1 ", [ remoteUser ]);
|
||||||
|
if (user) {
|
||||||
|
log.info("auth", `Login by remote-user header. IP=${await server.getClientIP(socket)}`);
|
||||||
|
log.debug("auth", `Remote user ${remoteUser} exists, found user ${user.username}`);
|
||||||
|
afterLogin(socket, user);
|
||||||
|
socket.emit("autoLoginRemoteHeader", user.username);
|
||||||
|
} else {
|
||||||
|
log.debug("auth", `Remote user ${remoteUser} doesn't exist`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.debug("auth", "Remote user header set but not found in headers");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
socket.emit("loginRequired");
|
socket.emit("loginRequired");
|
||||||
log.debug("auth", "need auth");
|
log.debug("auth", "need auth");
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
const ColumnCompilerMySQL = require("knex/lib/dialects/mysql/schema/mysql-columncompiler");
|
||||||
|
const { formatDefault } = require("knex/lib/formatter/formatterUtils");
|
||||||
|
const { log } = require("../../../../../../../src/util");
|
||||||
|
|
||||||
|
class KumaColumnCompiler extends ColumnCompilerMySQL {
|
||||||
|
/**
|
||||||
|
* Override defaultTo method to handle default value for TEXT fields
|
||||||
|
* @param {any} value Value
|
||||||
|
* @returns {string|void} Default value (Don't understand why it can return void or string, but it's the original code, lol)
|
||||||
|
*/
|
||||||
|
defaultTo(value) {
|
||||||
|
if (this.type === "text" && typeof value === "string") {
|
||||||
|
log.debug("defaultTo", `${this.args[0]}: ${this.type} ${value} ${typeof value}`);
|
||||||
|
// MySQL 8.0 is required and only if the value is written as an expression: https://dev.mysql.com/doc/refman/8.0/en/data-type-defaults.html
|
||||||
|
// MariaDB 10.2 is required: https://mariadb.com/kb/en/text/
|
||||||
|
return `default (${formatDefault(value, this.type, this.client)})`;
|
||||||
|
}
|
||||||
|
return super.defaultTo.apply(this, arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = KumaColumnCompiler;
|
|
@ -5,66 +5,67 @@
|
||||||
<template v-if="!settings.disableAuth">
|
<template v-if="!settings.disableAuth">
|
||||||
<p>
|
<p>
|
||||||
{{ $t("Current User") }}: <strong>{{ $root.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>
|
<button v-if="$root.socket.token.startsWith('autoLogin') === false" id="logout-btn" class="btn btn-danger ms-4 me-2 mb-2" @click="$root.logout">{{ $t("Logout") }}</button>
|
||||||
</p>
|
</p>
|
||||||
|
<template v-if="$root.socket.token.startsWith('autoLogin') === false">
|
||||||
<h5 class="my-4 settings-subheading">{{ $t("Change Password") }}</h5>
|
<h5 class="my-4 settings-subheading">{{ $t("Change Password") }}</h5>
|
||||||
<form class="mb-3" @submit.prevent="savePassword">
|
<form class="mb-3" @submit.prevent="savePassword">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="current-password" class="form-label">
|
<label for="current-password" class="form-label">
|
||||||
{{ $t("Current Password") }}
|
{{ $t("Current Password") }}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="current-password"
|
id="current-password"
|
||||||
v-model="password.currentPassword"
|
v-model="password.currentPassword"
|
||||||
type="password"
|
type="password"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
autocomplete="current-password"
|
autocomplete="current-password"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="new-password" class="form-label">
|
|
||||||
{{ $t("New Password") }}
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
id="new-password"
|
|
||||||
v-model="password.newPassword"
|
|
||||||
type="password"
|
|
||||||
class="form-control"
|
|
||||||
autocomplete="new-password"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="repeat-new-password" class="form-label">
|
|
||||||
{{ $t("Repeat New Password") }}
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
id="repeat-new-password"
|
|
||||||
v-model="password.repeatNewPassword"
|
|
||||||
type="password"
|
|
||||||
class="form-control"
|
|
||||||
:class="{ 'is-invalid': invalidPassword }"
|
|
||||||
autocomplete="new-password"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<div class="invalid-feedback">
|
|
||||||
{{ $t("passwordNotMatchMsg") }}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<div class="mb-3">
|
||||||
<button class="btn btn-primary" type="submit">
|
<label for="new-password" class="form-label">
|
||||||
{{ $t("Update Password") }}
|
{{ $t("New Password") }}
|
||||||
</button>
|
</label>
|
||||||
</div>
|
<input
|
||||||
</form>
|
id="new-password"
|
||||||
|
v-model="password.newPassword"
|
||||||
|
type="password"
|
||||||
|
class="form-control"
|
||||||
|
autocomplete="new-password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="repeat-new-password" class="form-label">
|
||||||
|
{{ $t("Repeat New Password") }}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="repeat-new-password"
|
||||||
|
v-model="password.repeatNewPassword"
|
||||||
|
type="password"
|
||||||
|
class="form-control"
|
||||||
|
:class="{ 'is-invalid': invalidPassword }"
|
||||||
|
autocomplete="new-password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
{{ $t("passwordNotMatchMsg") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-primary" type="submit">
|
||||||
|
{{ $t("Update Password") }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div v-if="! settings.disableAuth" class="mt-5 mb-3">
|
<div v-if="$root.socket.token.startsWith('autoLogin') === false" class="mt-5 mb-3">
|
||||||
<h5 class="my-4 settings-subheading">
|
<h5 class="my-4 settings-subheading">
|
||||||
{{ $t("Two Factor Authentication") }}
|
{{ $t("Two Factor Authentication") }}
|
||||||
</h5>
|
</h5>
|
||||||
|
@ -85,7 +86,7 @@
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<button v-if="settings.disableAuth" id="enableAuth-btn" class="btn btn-outline-primary me-2 mb-2" @click="enableAuth">{{ $t("Enable Auth") }}</button>
|
<button v-if="settings.disableAuth" id="enableAuth-btn" class="btn btn-outline-primary me-2 mb-2" @click="enableAuth">{{ $t("Enable Auth") }}</button>
|
||||||
<button v-if="! settings.disableAuth" id="disableAuth-btn" class="btn btn-primary me-2 mb-2" @click="confirmDisableAuth">{{ $t("Disable Auth") }}</button>
|
<button v-if="!settings.disableAuth" id="disableAuth-btn" class="btn btn-primary me-2 mb-2" @click="confirmDisableAuth">{{ $t("Disable Auth") }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -97,8 +97,6 @@
|
||||||
"pushOthers": "Others",
|
"pushOthers": "Others",
|
||||||
"programmingLanguages": "Programming Languages",
|
"programmingLanguages": "Programming Languages",
|
||||||
"Save": "Save",
|
"Save": "Save",
|
||||||
"Debug": "Debug",
|
|
||||||
"Copy": "Copy",
|
|
||||||
"Notifications": "Notifications",
|
"Notifications": "Notifications",
|
||||||
"Not available, please setup.": "Not available, please set up.",
|
"Not available, please setup.": "Not available, please set up.",
|
||||||
"Setup Notification": "Set Up Notification",
|
"Setup Notification": "Set Up Notification",
|
||||||
|
@ -251,14 +249,6 @@
|
||||||
"PushUrl": "Push URL",
|
"PushUrl": "Push URL",
|
||||||
"HeadersInvalidFormat": "The request headers are not valid JSON: ",
|
"HeadersInvalidFormat": "The request headers are not valid JSON: ",
|
||||||
"BodyInvalidFormat": "The request body is not valid JSON: ",
|
"BodyInvalidFormat": "The request body is not valid JSON: ",
|
||||||
"CopyToClipboardError": "Couldn't copy to clipboard: {error}",
|
|
||||||
"CopyToClipboardSuccess": "Copied!",
|
|
||||||
"CurlDebugInfo": "To debug the monitor, you can either paste this into your own machines terminal or into the machines terminal which uptime kuma is running on and see what you are requesting.{newiline}Please be aware of networking differences like {firewalls}, {dns_resolvers} or {docker_networks}.",
|
|
||||||
"firewalls": "firewalls",
|
|
||||||
"dns resolvers": "dns resolvers",
|
|
||||||
"docker networks": "docker networks",
|
|
||||||
"CurlDebugInfoOAuth2CCUnsupported": "Full oauth client credential flow is not supported in {curl}.{newline}Please get a bearer token and pass it via the {oauth2_bearer} option.",
|
|
||||||
"CurlDebugInfoProxiesUnsupported": "Proxy support in the above {curl} command is currently not implemented.",
|
|
||||||
"Monitor History": "Monitor History",
|
"Monitor History": "Monitor History",
|
||||||
"clearDataOlderThan": "Keep monitor history data for {0} days.",
|
"clearDataOlderThan": "Keep monitor history data for {0} days.",
|
||||||
"PasswordsDoNotMatch": "Passwords do not match.",
|
"PasswordsDoNotMatch": "Passwords do not match.",
|
||||||
|
|
|
@ -69,7 +69,7 @@
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li v-if="$root.loggedIn && $root.socket.token !== 'autoLogin'">
|
<li v-if="$root.loggedIn && $root.socket.token.startsWith('autoLogin') === false">
|
||||||
<button class="dropdown-item" @click="$root.logout">
|
<button class="dropdown-item" @click="$root.logout">
|
||||||
<font-awesome-icon icon="sign-out-alt" />
|
<font-awesome-icon icon="sign-out-alt" />
|
||||||
{{ $t("Logout") }}
|
{{ $t("Logout") }}
|
||||||
|
|
|
@ -119,17 +119,25 @@ export default {
|
||||||
this.info = info;
|
this.info = info;
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("setup", (monitorID, data) => {
|
socket.on("setup", () => {
|
||||||
this.$router.push("/setup");
|
this.$router.push("/setup");
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("autoLogin", (monitorID, data) => {
|
socket.on("autoLogin", () => {
|
||||||
this.loggedIn = true;
|
this.loggedIn = true;
|
||||||
this.storage().token = "autoLogin";
|
this.storage().token = "autoLogin";
|
||||||
this.socket.token = "autoLogin";
|
this.socket.token = "autoLogin";
|
||||||
this.allowLoginDialog = false;
|
this.allowLoginDialog = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on("autoLoginRemoteHeader", (username) => {
|
||||||
|
this.loggedIn = true;
|
||||||
|
this.username = username;
|
||||||
|
this.storage().token = "autoLoginRemoteHeader";
|
||||||
|
this.socket.token = "autoLoginRemoteHeader";
|
||||||
|
this.allowLoginDialog = false;
|
||||||
|
});
|
||||||
|
|
||||||
socket.on("loginRequired", () => {
|
socket.on("loginRequired", () => {
|
||||||
let token = this.storage().token;
|
let token = this.storage().token;
|
||||||
if (token && token !== "autoLogin") {
|
if (token && token !== "autoLogin") {
|
||||||
|
@ -275,6 +283,24 @@ export default {
|
||||||
this.clearData();
|
this.clearData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let token = this.storage().token;
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
if (token.startsWith("autoLogin") === false) {
|
||||||
|
this.loginByToken(token);
|
||||||
|
} else {
|
||||||
|
// Timeout if it is not actually auto login
|
||||||
|
setTimeout(() => {
|
||||||
|
if (! this.loggedIn) {
|
||||||
|
this.allowLoginDialog = true;
|
||||||
|
this.$root.storage().removeItem("token");
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.allowLoginDialog = true;
|
||||||
|
}
|
||||||
|
|
||||||
this.socket.firstConnect = false;
|
this.socket.firstConnect = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -326,7 +352,7 @@ export default {
|
||||||
getJWTPayload() {
|
getJWTPayload() {
|
||||||
const jwtToken = this.$root.storage().token;
|
const jwtToken = this.$root.storage().token;
|
||||||
|
|
||||||
if (jwtToken && jwtToken !== "autoLogin") {
|
if (jwtToken && jwtToken.startsWith("autoLogin") === false) {
|
||||||
return jwtDecode(jwtToken);
|
return jwtDecode(jwtToken);
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|
|
@ -1029,23 +1029,13 @@
|
||||||
<div class="fixed-bottom-bar p-3">
|
<div class="fixed-bottom-bar p-3">
|
||||||
<button
|
<button
|
||||||
id="monitor-submit-btn"
|
id="monitor-submit-btn"
|
||||||
class="btn btn-primary me-2"
|
class="btn btn-primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
:disabled="processing"
|
:disabled="processing"
|
||||||
data-testid="save-button"
|
data-testid="save-button"
|
||||||
>
|
>
|
||||||
{{ $t("Save") }}
|
{{ $t("Save") }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
v-if="monitor.type === 'http'"
|
|
||||||
id="monitor-debug-btn"
|
|
||||||
class="btn btn-outline-primary"
|
|
||||||
type="button"
|
|
||||||
:disabled="processing"
|
|
||||||
@click.stop="modal.show()"
|
|
||||||
>
|
|
||||||
{{ $t("Debug") }}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1057,58 +1047,9 @@
|
||||||
<RemoteBrowserDialog ref="remoteBrowserDialog" />
|
<RemoteBrowserDialog ref="remoteBrowserDialog" />
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
<div ref="modal" class="modal fade" tabindex="-1">
|
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-body">
|
|
||||||
<textarea id="curl-debug" v-model="curlCommand" class="form-control mb-3" readonly wrap="off"></textarea>
|
|
||||||
<button id="debug-copy-btn" class="btn btn-outline-primary position-absolute top-0 end-0 mt-3 me-3 border-0" type="button" @click.stop="copyToClipboard">
|
|
||||||
<font-awesome-icon icon="copy" />
|
|
||||||
</button>
|
|
||||||
<i18n-t keypath="CurlDebugInfo" tag="p" class="form-text">
|
|
||||||
<template #newiline>
|
|
||||||
<br>
|
|
||||||
</template>
|
|
||||||
<template #firewalls>
|
|
||||||
<a href="https://xkcd.com/2259/" target="_blank">{{ $t('firewalls') }}</a>
|
|
||||||
</template>
|
|
||||||
<template #dns_resolvers>
|
|
||||||
<a href="https://www.reddit.com/r/sysadmin/comments/rxho93/thank_you_for_the_running_its_always_dns_joke_its/" target="_blank">{{ $t('dns resolvers') }}</a>
|
|
||||||
</template>
|
|
||||||
<template #docker_networks>
|
|
||||||
<a href="https://youtu.be/bKFMS5C4CG0" target="_blank">{{ $t('docker networks') }}</a>
|
|
||||||
</template>
|
|
||||||
</i18n-t>
|
|
||||||
<div v-if="monitor.authMethod === 'oauth2-cc'" class="alert alert-warning d-flex align-items-center gap-2" role="alert">
|
|
||||||
<div role="img" aria-label="Warning:">⚠️</div>
|
|
||||||
<i18n-t keypath="CurlDebugInfoOAuth2CCUnsupported" tag="div">
|
|
||||||
<template #curl>
|
|
||||||
<code>curl</code>
|
|
||||||
</template>
|
|
||||||
<template #newline>
|
|
||||||
<br>
|
|
||||||
</template>
|
|
||||||
<template #oauth2_bearer>
|
|
||||||
<code>--oauth2-bearer TOKEN</code>
|
|
||||||
</template>
|
|
||||||
</i18n-t>
|
|
||||||
</div>
|
|
||||||
<div v-if="monitor.proxyId" class="alert alert-warning d-flex align-items-center gap-2" role="alert">
|
|
||||||
<div role="img" aria-label="Warning:">⚠️</div>
|
|
||||||
<i18n-t keypath="CurlDebugInfoProxiesUnsupported" tag="div">
|
|
||||||
<template #curl>
|
|
||||||
<code>curl</code>
|
|
||||||
</template>
|
|
||||||
</i18n-t>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { Modal } from "bootstrap";
|
|
||||||
import VueMultiselect from "vue-multiselect";
|
import VueMultiselect from "vue-multiselect";
|
||||||
import { useToast } from "vue-toastification";
|
import { useToast } from "vue-toastification";
|
||||||
import ActionSelect from "../components/ActionSelect.vue";
|
import ActionSelect from "../components/ActionSelect.vue";
|
||||||
|
@ -1123,10 +1064,8 @@ import { genSecret, isDev, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND, sleep } fro
|
||||||
import { hostNameRegexPattern } from "../util-frontend";
|
import { hostNameRegexPattern } from "../util-frontend";
|
||||||
import HiddenInput from "../components/HiddenInput.vue";
|
import HiddenInput from "../components/HiddenInput.vue";
|
||||||
import EditMonitorConditions from "../components/EditMonitorConditions.vue";
|
import EditMonitorConditions from "../components/EditMonitorConditions.vue";
|
||||||
import { version } from "../../package.json";
|
|
||||||
const userAgent = `'Uptime-Kuma/${version}'`;
|
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast;
|
||||||
|
|
||||||
const pushTokenLength = 32;
|
const pushTokenLength = 32;
|
||||||
|
|
||||||
|
@ -1192,7 +1131,6 @@ export default {
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
modal: null,
|
|
||||||
minInterval: MIN_INTERVAL_SECOND,
|
minInterval: MIN_INTERVAL_SECOND,
|
||||||
maxInterval: MAX_INTERVAL_SECOND,
|
maxInterval: MAX_INTERVAL_SECOND,
|
||||||
processing: false,
|
processing: false,
|
||||||
|
@ -1219,54 +1157,6 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
|
||||||
curlCommand() {
|
|
||||||
const command = [ "curl", "--verbose", "--head", "--request", this.monitor.method, "\\\n", "--user-agent", userAgent, "\\\n" ];
|
|
||||||
if (this.monitor.ignoreTls) {
|
|
||||||
command.push("--insecure", "\\\n");
|
|
||||||
}
|
|
||||||
if (this.monitor.headers) {
|
|
||||||
try {
|
|
||||||
// trying to parse the supplied data as json to trim whitespace
|
|
||||||
for (const [ key, value ] of Object.entries(JSON.parse(this.monitor.headers))) {
|
|
||||||
command.push("--header", `'${key}: ${value}'`, "\\\n");
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
command.push("--header", `'${this.monitor.headers}'`, "\\\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.monitor.authMethod === "basic") {
|
|
||||||
command.push("--user", `${this.monitor.basic_auth_user}:${this.monitor.basic_auth_pass}`, "--basic", "\\\n");
|
|
||||||
} else if (this.monitor.authmethod === "mtls") {
|
|
||||||
command.push("--cacert", `'${this.monitor.tlsCa}'`, "\\\n", "--key", `'${this.monitor.tlsKey}'`, "\\\n", "--cert", `'${this.monitor.tlsCert}'`, "\\\n");
|
|
||||||
} else if (this.monitor.authMethod === "ntlm") {
|
|
||||||
command.push("--user", `'${this.monitor.authDomain ? `${this.monitor.authDomain}/` : ""}${this.monitor.basic_auth_user}:${this.monitor.basic_auth_pass}'`, "--ntlm", "\\\n");
|
|
||||||
}
|
|
||||||
if (this.monitor.body && this.monitor.httpBodyEncoding === "json") {
|
|
||||||
let json = "";
|
|
||||||
try {
|
|
||||||
// trying to parse the supplied data as json to trim whitespace
|
|
||||||
json = JSON.stringify(JSON.parse(this.monitor.body));
|
|
||||||
} catch (e) {
|
|
||||||
json = this.monitor.body;
|
|
||||||
}
|
|
||||||
command.push("--header", "'Content-Type: application/json'", "\\\n", "--data", `'${json}'`, "\\\n");
|
|
||||||
} else if (this.monitor.body && this.monitor.httpBodyEncoding === "xml") {
|
|
||||||
command.push("--headers", "'Content-Type: application/xml'", "\\\n", "--data", `'${this.monitor.body}'`, "\\\n");
|
|
||||||
}
|
|
||||||
if (this.monitor.maxredirects) {
|
|
||||||
command.push("--location", "--max-redirs", this.monitor.maxredirects, "\\\n");
|
|
||||||
}
|
|
||||||
if (this.monitor.timeout) {
|
|
||||||
command.push("--max-time", this.monitor.timeout, "\\\n");
|
|
||||||
}
|
|
||||||
if (this.monitor.maxretries) {
|
|
||||||
command.push("--retry", this.monitor.maxretries, "\\\n");
|
|
||||||
}
|
|
||||||
command.push("--url", this.monitor.url);
|
|
||||||
return command.join(" ");
|
|
||||||
},
|
|
||||||
|
|
||||||
ipRegex() {
|
ipRegex() {
|
||||||
|
|
||||||
// Allow to test with simple dns server with port (127.0.0.1:5300)
|
// Allow to test with simple dns server with port (127.0.0.1:5300)
|
||||||
|
@ -1623,7 +1513,6 @@ message HealthCheckResponse {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.modal = new Modal(this.$refs.modal);
|
|
||||||
this.init();
|
this.init();
|
||||||
|
|
||||||
let acceptedStatusCodeOptions = [
|
let acceptedStatusCodeOptions = [
|
||||||
|
@ -1664,14 +1553,6 @@ message HealthCheckResponse {
|
||||||
this.kafkaSaslMechanismOptions = kafkaSaslMechanismOptions;
|
this.kafkaSaslMechanismOptions = kafkaSaslMechanismOptions;
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async copyToClipboard() {
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(this.curlCommand);
|
|
||||||
toast.success(this.$t("CopyToClipboardSuccess"));
|
|
||||||
} catch (err) {
|
|
||||||
toast.error(this.$t("CopyToClipboardError", { error: err.message }));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
* Initialize the edit monitor form
|
* Initialize the edit monitor form
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
|
@ -1974,9 +1855,4 @@ message HealthCheckResponse {
|
||||||
textarea {
|
textarea {
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#curl-debug {
|
|
||||||
font-family: monospace;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Add table
Reference in a new issue