Merge branch 'master' into feat-3672-use_query_params_for_filter

This commit is contained in:
Suven-p 2024-10-28 20:40:12 +05:45 committed by GitHub
commit 36a6f19f3e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 155 additions and 164 deletions

View file

@ -1,4 +1,4 @@
name: json-yaml-validate
name: validate
on:
push:
branches:
@ -26,7 +26,8 @@ jobs:
comment: "true" # enable comment mode
exclude_file: ".github/config/exclude.txt" # gitignore style file for exclusions
check-lang-json:
# General validations
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@ -34,4 +35,9 @@ jobs:
uses: actions/setup-node@v4
with:
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

View 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
View file

@ -46,7 +46,7 @@
"jsonwebtoken": "~9.0.0",
"jwt-decode": "~3.1.2",
"kafkajs": "^2.2.4",
"knex": "^2.4.2",
"knex": "~3.1.0",
"limiter": "~2.1.0",
"liquidjs": "^10.7.0",
"marked": "^14.0.0",
@ -54,7 +54,7 @@
"mongodb": "~4.17.1",
"mqtt": "~4.3.7",
"mssql": "~11.0.0",
"mysql2": "~3.9.6",
"mysql2": "~3.11.3",
"nanoid": "~3.3.4",
"net-snmp": "^3.11.2",
"node-cloudflared-tunnel": "~1.0.9",
@ -5655,6 +5655,15 @@
"integrity": "sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==",
"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": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.28.1.tgz",
@ -10915,9 +10924,9 @@
}
},
"node_modules/knex": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/knex/-/knex-2.5.1.tgz",
"integrity": "sha512-z78DgGKUr4SE/6cm7ku+jHvFT0X97aERh/f0MUKAKgFnwCYBEW4TFBqtHWFYiJFid7fMrtpZ/gxJthvz5mEByA==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/knex/-/knex-3.1.0.tgz",
"integrity": "sha512-GLoII6hR0c4ti243gMs5/1Rb3B+AjwMOfjYm97pu0FOQa7JH56hgBxYf5WK2525ceSbBY1cjeZ9yk99GPMB6Kw==",
"license": "MIT",
"dependencies": {
"colorette": "2.0.19",
@ -10929,7 +10938,7 @@
"getopts": "2.3.0",
"interpret": "^2.2.0",
"lodash": "^4.17.21",
"pg-connection-string": "2.6.1",
"pg-connection-string": "2.6.2",
"rechoir": "^0.8.0",
"resolve-from": "^5.0.0",
"tarn": "^3.0.2",
@ -10939,7 +10948,7 @@
"knex": "bin/cli.js"
},
"engines": {
"node": ">=12"
"node": ">=16"
},
"peerDependenciesMeta": {
"better-sqlite3": {
@ -10989,9 +10998,9 @@
"license": "MIT"
},
"node_modules/knex/node_modules/pg-connection-string": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.1.tgz",
"integrity": "sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg==",
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz",
"integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==",
"license": "MIT"
},
"node_modules/knex/node_modules/resolve-from": {
@ -11242,6 +11251,21 @@
"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": {
"version": "0.30.12",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz",
@ -11917,16 +11941,17 @@
}
},
"node_modules/mysql2": {
"version": "3.9.9",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.9.9.tgz",
"integrity": "sha512-Qtb2RUxwWMFkWXqF7Rd/7ySkupbQnNY7O0zQuQYgPcuJZ06M36JG3HIDEh/pEeq7LImcvA6O3lOVQ9XQK+HEZg==",
"version": "3.11.3",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.11.3.tgz",
"integrity": "sha512-Qpu2ADfbKzyLdwC/5d4W7+5Yz7yBzCU05YWt5npWzACST37wJsB23wgOSo00qi043urkiRwXtEvJc9UnuLX/MQ==",
"license": "MIT",
"dependencies": {
"aws-ssl-profiles": "^1.1.1",
"denque": "^2.1.0",
"generate-function": "^2.3.1",
"iconv-lite": "^0.6.3",
"long": "^5.2.1",
"lru-cache": "^8.0.0",
"lru.min": "^1.0.0",
"named-placeholders": "^1.1.3",
"seq-queue": "^0.0.5",
"sqlstring": "^2.3.2"
@ -11935,15 +11960,6 @@
"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": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz",

View file

@ -109,7 +109,7 @@
"jsonwebtoken": "~9.0.0",
"jwt-decode": "~3.1.2",
"kafkajs": "^2.2.4",
"knex": "^2.4.2",
"knex": "~3.1.0",
"limiter": "~2.1.0",
"liquidjs": "^10.7.0",
"marked": "^14.0.0",
@ -117,7 +117,7 @@
"mongodb": "~4.17.1",
"mqtt": "~4.3.7",
"mssql": "~11.0.0",
"mysql2": "~3.9.6",
"mysql2": "~3.11.3",
"nanoid": "~3.3.4",
"net-snmp": "^3.11.2",
"node-cloudflared-tunnel": "~1.0.9",

View file

@ -10,6 +10,7 @@ const { Settings } = require("./settings");
const { UptimeCalculator } = require("./uptime-calculator");
const dayjs = require("dayjs");
const { SimpleMigrationServer } = require("./utils/simple-migration-server");
const KumaColumnCompiler = require("./utils/knex/lib/dialects/mysql2/schema/mysql2-columncompiler");
/**
* Database & App Data Folder
@ -198,6 +199,14 @@ class Database {
* @returns {Promise<void>}
*/
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;
let dbConfig;
try {

View file

@ -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;

View file

@ -97,8 +97,6 @@
"pushOthers": "Others",
"programmingLanguages": "Programming Languages",
"Save": "Save",
"Debug": "Debug",
"Copy": "Copy",
"Notifications": "Notifications",
"Not available, please setup.": "Not available, please set up.",
"Setup Notification": "Set Up Notification",
@ -251,14 +249,6 @@
"PushUrl": "Push URL",
"HeadersInvalidFormat": "The request headers are 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",
"clearDataOlderThan": "Keep monitor history data for {0} days.",
"PasswordsDoNotMatch": "Passwords do not match.",

View file

@ -1029,23 +1029,13 @@
<div class="fixed-bottom-bar p-3">
<button
id="monitor-submit-btn"
class="btn btn-primary me-2"
class="btn btn-primary"
type="submit"
:disabled="processing"
data-testid="save-button"
>
{{ $t("Save") }}
</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>
</form>
@ -1057,58 +1047,9 @@
<RemoteBrowserDialog ref="remoteBrowserDialog" />
</div>
</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>
<script>
import { Modal } from "bootstrap";
import VueMultiselect from "vue-multiselect";
import { useToast } from "vue-toastification";
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 HiddenInput from "../components/HiddenInput.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;
@ -1192,7 +1131,6 @@ export default {
data() {
return {
modal: null,
minInterval: MIN_INTERVAL_SECOND,
maxInterval: MAX_INTERVAL_SECOND,
processing: false,
@ -1219,54 +1157,6 @@ export default {
},
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() {
// Allow to test with simple dns server with port (127.0.0.1:5300)
@ -1623,7 +1513,6 @@ message HealthCheckResponse {
},
},
mounted() {
this.modal = new Modal(this.$refs.modal);
this.init();
let acceptedStatusCodeOptions = [
@ -1664,14 +1553,6 @@ message HealthCheckResponse {
this.kafkaSaslMechanismOptions = kafkaSaslMechanismOptions;
},
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
* @returns {void}
@ -1974,9 +1855,4 @@ message HealthCheckResponse {
textarea {
min-height: 200px;
}
#curl-debug {
font-family: monospace;
overflow: auto;
}
</style>