From ca094296f27a41d22af9cd846dc4e32e8bc5071b Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Mon, 28 Oct 2024 13:16:22 +0800 Subject: [PATCH 1/3] Fix: Knex cannot set a default value for TEXT field (MariaDB) (#5261) --- package-lock.json | 62 ++++++++++++------- package.json | 4 +- server/database.js | 9 +++ .../mysql2/schema/mysql2-columncompiler.js | 22 +++++++ 4 files changed, 72 insertions(+), 25 deletions(-) create mode 100644 server/utils/knex/lib/dialects/mysql2/schema/mysql2-columncompiler.js diff --git a/package-lock.json b/package-lock.json index a3c5dc23..100cdcd8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index a0fabbe4..ad9aac91 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/server/database.js b/server/database.js index eb459435..3927d6db 100644 --- a/server/database.js +++ b/server/database.js @@ -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} */ 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 { diff --git a/server/utils/knex/lib/dialects/mysql2/schema/mysql2-columncompiler.js b/server/utils/knex/lib/dialects/mysql2/schema/mysql2-columncompiler.js new file mode 100644 index 00000000..d05a6bc8 --- /dev/null +++ b/server/utils/knex/lib/dialects/mysql2/schema/mysql2-columncompiler.js @@ -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; From 5ee986c58e8a7e6ce9b1bdd5cbc132925c6d0724 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Mon, 28 Oct 2024 14:20:29 +0800 Subject: [PATCH 2/3] =?UTF-8?q?Check=20knex=20filenames=20and=20rename=20j?= =?UTF-8?q?son-yaml-validate.yml=20=E2=86=92=20validate.yml=20for=20genera?= =?UTF-8?q?l=20purposes=20=20(#5263)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{json-yaml-validate.yml => validate.yml} | 12 +++- extra/check-knex-filenames.mjs | 72 +++++++++++++++++++ 2 files changed, 81 insertions(+), 3 deletions(-) rename .github/workflows/{json-yaml-validate.yml => validate.yml} (76%) create mode 100644 extra/check-knex-filenames.mjs diff --git a/.github/workflows/json-yaml-validate.yml b/.github/workflows/validate.yml similarity index 76% rename from .github/workflows/json-yaml-validate.yml rename to .github/workflows/validate.yml index 7942884e..7e631ccd 100644 --- a/.github/workflows/json-yaml-validate.yml +++ b/.github/workflows/validate.yml @@ -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 diff --git a/extra/check-knex-filenames.mjs b/extra/check-knex-filenames.mjs new file mode 100644 index 00000000..4911fc56 --- /dev/null +++ b/extra/check-knex-filenames.mjs @@ -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."); From be6e5211e5961b9c2fffefcf5f625296fe8df912 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Mon, 28 Oct 2024 15:21:33 +0800 Subject: [PATCH 3/3] Revert #5152 (#5264) --- src/lang/en.json | 10 --- src/pages/EditMonitor.vue | 128 +------------------------------------- 2 files changed, 2 insertions(+), 136 deletions(-) diff --git a/src/lang/en.json b/src/lang/en.json index 07d3a3d2..fb892dea 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -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.", diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index 677210c4..4763f872 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -1029,23 +1029,13 @@
-
@@ -1057,58 +1047,9 @@ -