diff --git a/.eslintrc.js b/.eslintrc.js index 21e35910..4dae1432 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -22,39 +22,47 @@ module.exports = { requireConfigFile: false, }, rules: { - "linebreak-style": ["error", "unix"], - "camelcase": ["warn", { + "yoda": "error", + eqeqeq: [ "warn", "smart" ], + "linebreak-style": [ "error", "unix" ], + "camelcase": [ "warn", { "properties": "never", "ignoreImports": true }], - // override/add rules settings here, such as: - // 'vue/no-unused-vars': 'error' - "no-unused-vars": "warn", + "no-unused-vars": [ "warn", { + "args": "none" + }], indent: [ "error", 4, { - ignoredNodes: ["TemplateLiteral"], + ignoredNodes: [ "TemplateLiteral" ], SwitchCase: 1, }, ], - quotes: ["warn", "double"], + quotes: [ "warn", "double" ], semi: "error", - "vue/html-indent": ["warn", 4], // default: 2 + "vue/html-indent": [ "warn", 4 ], // default: 2 "vue/max-attributes-per-line": "off", "vue/singleline-html-element-content-newline": "off", "vue/html-self-closing": "off", + "vue/require-component-is": "off", // not allow is="style" https://github.com/vuejs/eslint-plugin-vue/issues/462#issuecomment-430234675 "vue/attribute-hyphenation": "off", // This change noNL to "no-n-l" unexpectedly - "no-multi-spaces": ["error", { + "no-multi-spaces": [ "error", { ignoreEOLComments: true, }], - "space-before-function-paren": ["error", { + "array-bracket-spacing": [ "warn", "always", { + "singleValue": true, + "objectsInArrays": false, + "arraysInArrays": false + }], + "space-before-function-paren": [ "error", { "anonymous": "always", "named": "never", "asyncArrow": "always" }], "curly": "error", - "object-curly-spacing": ["error", "always"], + "object-curly-spacing": [ "error", "always" ], "object-curly-newline": "off", "object-property-newline": "error", "comma-spacing": "error", @@ -65,36 +73,36 @@ module.exports = { "space-infix-ops": "warn", "arrow-spacing": "warn", "no-trailing-spaces": "warn", - "no-constant-condition": ["error", { + "no-constant-condition": [ "error", { "checkLoops": false, }], "space-before-blocks": "warn", //'no-console': 'warn', "no-extra-boolean-cast": "off", - "no-multiple-empty-lines": ["warn", { + "no-multiple-empty-lines": [ "warn", { "max": 1, "maxBOF": 0, }], - "lines-between-class-members": ["warn", "always", { + "lines-between-class-members": [ "warn", "always", { exceptAfterSingleLine: true, }], "no-unneeded-ternary": "error", - "array-bracket-newline": ["error", "consistent"], - "eol-last": ["error", "always"], + "array-bracket-newline": [ "error", "consistent" ], + "eol-last": [ "error", "always" ], //'prefer-template': 'error', - "comma-dangle": ["warn", "only-multiline"], - "no-empty": ["error", { + "comma-dangle": [ "warn", "only-multiline" ], + "no-empty": [ "error", { "allowEmptyCatch": true }], "no-control-regex": "off", - "one-var": ["error", "never"], - "max-statements-per-line": ["error", { "max": 1 }] + "one-var": [ "error", "never" ], + "max-statements-per-line": [ "error", { "max": 1 }] }, "overrides": [ { "files": [ "src/languages/*.js", "src/icon.js" ], "rules": { - "comma-dangle": ["error", "always-multiline"], + "comma-dangle": [ "error", "always-multiline" ], } }, diff --git a/babel.config.js b/babel.config.js index d2ad8213..6bb8a01a 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,11 +1,11 @@ const config = {}; if (process.env.TEST_FRONTEND) { - config.presets = ["@babel/preset-env"]; + config.presets = [ "@babel/preset-env" ]; } if (process.env.TEST_BACKEND) { - config.plugins = ["babel-plugin-rewire"]; + config.plugins = [ "babel-plugin-rewire" ]; } module.exports = config; diff --git a/config/vite.config.js b/config/vite.config.js index a9701d42..9fdc5fab 100644 --- a/config/vite.config.js +++ b/config/vite.config.js @@ -10,15 +10,15 @@ export default defineConfig({ plugins: [ vue(), legacy({ - targets: ["ie > 11"], - additionalLegacyPolyfills: ["regenerator-runtime/runtime"] + targets: [ "ie > 11" ], + additionalLegacyPolyfills: [ "regenerator-runtime/runtime" ] }) ], css: { postcss: { "parser": postCssScss, "map": false, - "plugins": [postcssRTLCSS] + "plugins": [ postcssRTLCSS ] } }, }); diff --git a/db/patch-status-page-footer-css.sql b/db/patch-status-page-footer-css.sql new file mode 100644 index 00000000..413918f1 --- /dev/null +++ b/db/patch-status-page-footer-css.sql @@ -0,0 +1,6 @@ +-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. +BEGIN TRANSACTION; +ALTER TABLE status_page ADD footer_text TEXT; +ALTER TABLE status_page ADD custom_css TEXT; +ALTER TABLE status_page ADD show_powered_by BOOLEAN NOT NULL DEFAULT 1; +COMMIT; diff --git a/extra/beta/update-version.js b/extra/beta/update-version.js index 4fddcfeb..b8de95df 100644 --- a/extra/beta/update-version.js +++ b/extra/beta/update-version.js @@ -5,7 +5,6 @@ const util = require("../../src/util"); util.polyfill(); -const oldVersion = pkg.version; const version = process.env.VERSION; console.log("Beta Version: " + version); @@ -32,7 +31,7 @@ if (! exists) { function commit(version) { let msg = "Update to " + version; - let res = childProcess.spawnSync("git", ["commit", "-m", msg, "-a"]); + let res = childProcess.spawnSync("git", [ "commit", "-m", msg, "-a" ]); let stdout = res.stdout.toString().trim(); console.log(stdout); @@ -40,15 +39,15 @@ function commit(version) { throw new Error("commit error"); } - res = childProcess.spawnSync("git", ["push", "origin", "master"]); + res = childProcess.spawnSync("git", [ "push", "origin", "master" ]); console.log(res.stdout.toString().trim()); } function tag(version) { - let res = childProcess.spawnSync("git", ["tag", version]); + let res = childProcess.spawnSync("git", [ "tag", version ]); console.log(res.stdout.toString().trim()); - res = childProcess.spawnSync("git", ["push", "origin", version]); + res = childProcess.spawnSync("git", [ "push", "origin", version ]); console.log(res.stdout.toString().trim()); } @@ -57,15 +56,7 @@ function tagExists(version) { throw new Error("invalid version"); } - let res = childProcess.spawnSync("git", ["tag", "-l", version]); + let res = childProcess.spawnSync("git", [ "tag", "-l", version ]); return res.stdout.toString().trim() === version; } - -function safeDelete(dir) { - if (fs.existsSync(dir)) { - fs.rmdirSync(dir, { - recursive: true, - }); - } -} diff --git a/extra/close-incorrect-issue.js b/extra/close-incorrect-issue.js index a15a5da3..ae38bccc 100644 --- a/extra/close-incorrect-issue.js +++ b/extra/close-incorrect-issue.js @@ -29,7 +29,7 @@ const github = require("@actions/github"); owner: issue.owner, repo: issue.repo, issue_number: issue.number, - labels: ["invalid-format"] + labels: [ "invalid-format" ] }); // Add the issue closing comment diff --git a/extra/update-version.js b/extra/update-version.js index d72fee68..f17ad200 100644 --- a/extra/update-version.js +++ b/extra/update-version.js @@ -1,7 +1,6 @@ const pkg = require("../package.json"); const fs = require("fs"); -const rmSync = require("./fs-rmSync.js"); -const child_process = require("child_process"); +const childProcess = require("child_process"); const util = require("../src/util"); util.polyfill(); @@ -42,7 +41,7 @@ if (! exists) { function commit(version) { let msg = "Update to " + version; - let res = child_process.spawnSync("git", ["commit", "-m", msg, "-a"]); + let res = childProcess.spawnSync("git", [ "commit", "-m", msg, "-a" ]); let stdout = res.stdout.toString().trim(); console.log(stdout); @@ -52,7 +51,7 @@ function commit(version) { } function tag(version) { - let res = child_process.spawnSync("git", ["tag", version]); + let res = childProcess.spawnSync("git", [ "tag", version ]); console.log(res.stdout.toString().trim()); } @@ -67,7 +66,7 @@ function tagExists(version) { throw new Error("invalid version"); } - let res = child_process.spawnSync("git", ["tag", "-l", version]); + let res = childProcess.spawnSync("git", [ "tag", "-l", version ]); return res.stdout.toString().trim() === version; } diff --git a/extra/update-wiki-version.js b/extra/update-wiki-version.js index 10631c33..d0f10561 100644 --- a/extra/update-wiki-version.js +++ b/extra/update-wiki-version.js @@ -1,4 +1,4 @@ -const child_process = require("child_process"); +const childProcess = require("child_process"); const fs = require("fs"); const newVersion = process.env.VERSION; @@ -16,23 +16,23 @@ function updateWiki(newVersion) { safeDelete(wikiDir); - child_process.spawnSync("git", ["clone", "https://github.com/louislam/uptime-kuma.wiki.git", wikiDir]); + childProcess.spawnSync("git", [ "clone", "https://github.com/louislam/uptime-kuma.wiki.git", wikiDir ]); let content = fs.readFileSync(howToUpdateFilename).toString(); // Replace the version: https://regex101.com/r/hmj2Bc/1 content = content.replace(/(git checkout )([^\s]+)/, `$1${newVersion}`); fs.writeFileSync(howToUpdateFilename, content); - child_process.spawnSync("git", ["add", "-A"], { + childProcess.spawnSync("git", [ "add", "-A" ], { cwd: wikiDir, }); - child_process.spawnSync("git", ["commit", "-m", `Update to ${newVersion}`], { + childProcess.spawnSync("git", [ "commit", "-m", `Update to ${newVersion}` ], { cwd: wikiDir, }); console.log("Pushing to Github"); - child_process.spawnSync("git", ["push"], { + childProcess.spawnSync("git", [ "push" ], { cwd: wikiDir, }); diff --git a/package-lock.json b/package-lock.json index ca2af5d6..5f106da9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,6 +45,7 @@ "password-hash": "~1.2.2", "postcss-rtlcss": "~3.4.1", "postcss-scss": "~4.0.3", + "prismjs": "^1.27.0", "prom-client": "~13.2.0", "prometheus-api-metrics": "~3.2.1", "qrcode": "~1.5.0", @@ -64,6 +65,7 @@ "vue-i18n": "~9.1.9", "vue-image-crop-upload": "~3.0.3", "vue-multiselect": "~3.0.0-alpha.2", + "vue-prism-editor": "^2.0.0-alpha.2", "vue-qrcode": "~1.0.0", "vue-router": "~4.0.14", "vue-toastification": "~2.0.0-rc.5", @@ -13012,6 +13014,14 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/prismjs": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", + "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==", + "engines": { + "node": ">=6" + } + }, "node_modules/proc-log": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-2.0.1.tgz", @@ -15790,6 +15800,17 @@ "npm": ">= 3.0.0" } }, + "node_modules/vue-prism-editor": { + "version": "2.0.0-alpha.2", + "resolved": "https://registry.npmjs.org/vue-prism-editor/-/vue-prism-editor-2.0.0-alpha.2.tgz", + "integrity": "sha512-Gu42ba9nosrE+gJpnAEuEkDMqG9zSUysIR8SdXUw8MQKDjBnnNR9lHC18uOr/ICz7yrA/5c7jHJr9lpElODC7w==", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, "node_modules/vue-qrcode": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/vue-qrcode/-/vue-qrcode-1.0.0.tgz", @@ -26033,6 +26054,11 @@ } } }, + "prismjs": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", + "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==" + }, "proc-log": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-2.0.1.tgz", @@ -28156,6 +28182,11 @@ "resolved": "https://registry.npmjs.org/vue-multiselect/-/vue-multiselect-3.0.0-alpha.2.tgz", "integrity": "sha512-Xp9fGJECns45v+v8jXbCIsAkCybYkEg0lNwr7Z6HDUSMyx2TEIK2giipPE+qXiShEc1Ipn+ZtttH2iq9hwXP4Q==" }, + "vue-prism-editor": { + "version": "2.0.0-alpha.2", + "resolved": "https://registry.npmjs.org/vue-prism-editor/-/vue-prism-editor-2.0.0-alpha.2.tgz", + "integrity": "sha512-Gu42ba9nosrE+gJpnAEuEkDMqG9zSUysIR8SdXUw8MQKDjBnnNR9lHC18uOr/ICz7yrA/5c7jHJr9lpElODC7w==" + }, "vue-qrcode": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/vue-qrcode/-/vue-qrcode-1.0.0.tgz", diff --git a/package.json b/package.json index a1b7bdce..00e7d4af 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "install-legacy": "npm install --legacy-peer-deps", "update-legacy": "npm update --legacy-peer-deps", "lint:js": "eslint --ext \".js,.vue\" --ignore-path .gitignore .", + "lint-fix:js": "eslint --ext \".js,.vue\" --fix --ignore-path .gitignore .", "lint:style": "stylelint \"**/*.{vue,css,scss}\" --ignore-path .gitignore", "lint": "npm run lint:js && npm run lint:style", "dev": "vite --host --config ./config/vite.config.js", @@ -93,6 +94,7 @@ "password-hash": "~1.2.2", "postcss-rtlcss": "~3.4.1", "postcss-scss": "~4.0.3", + "prismjs": "^1.27.0", "prom-client": "~13.2.0", "prometheus-api-metrics": "~3.2.1", "qrcode": "~1.5.0", @@ -112,6 +114,7 @@ "vue-i18n": "~9.1.9", "vue-image-crop-upload": "~3.0.3", "vue-multiselect": "~3.0.0-alpha.2", + "vue-prism-editor": "^2.0.0-alpha.2", "vue-qrcode": "~1.0.0", "vue-router": "~4.0.14", "vue-toastification": "~2.0.0-rc.5", diff --git a/server/2fa.js b/server/2fa.js index bc8145cf..f8d700bf 100644 --- a/server/2fa.js +++ b/server/2fa.js @@ -1,4 +1,3 @@ -const { checkLogin } = require("./util-server"); const { R } = require("redbean-node"); class TwoFA { diff --git a/server/client.js b/server/client.js index 3a2d6df2..31b10e84 100644 --- a/server/client.js +++ b/server/client.js @@ -98,7 +98,7 @@ async function sendImportantHeartbeatList(socket, monitorID, toUser = false, ove async function sendProxyList(socket) { const timeLogger = new TimeLogger(); - const list = await R.find("proxy", " user_id = ? ", [socket.userID]); + const list = await R.find("proxy", " user_id = ? ", [ socket.userID ]); io.to(socket.userID).emit("proxyList", list.map(bean => bean.export())); timeLogger.print("Send Proxy List"); diff --git a/server/database.js b/server/database.js index 961cab82..099d15d5 100644 --- a/server/database.js +++ b/server/database.js @@ -56,6 +56,7 @@ class Database { "patch-status-page.sql": true, "patch-proxy.sql": true, "patch-monitor-expiry-notification.sql": true, + "patch-status-page-footer-css.sql": true, "patch-added-mqtt-monitor.sql": true, } diff --git a/server/jobs/clear-old-data.js b/server/jobs/clear-old-data.js index 7c368014..0ec5ffa5 100644 --- a/server/jobs/clear-old-data.js +++ b/server/jobs/clear-old-data.js @@ -30,7 +30,7 @@ const DEFAULT_KEEP_PERIOD = 180; try { await R.exec( "DELETE FROM heartbeat WHERE time < DATETIME('now', '-' || ? || ' days') ", - [parsedPeriod] + [ parsedPeriod ] ); } catch (e) { log(`Failed to clear old data: ${e.message}`); diff --git a/server/jobs/util-worker.js b/server/jobs/util-worker.js index 9426840d..f122e682 100644 --- a/server/jobs/util-worker.js +++ b/server/jobs/util-worker.js @@ -9,7 +9,7 @@ const log = function (any) { }; const exit = function (error) { - if (error && error != 0) { + if (error && error !== 0) { process.exit(error); } else { if (parentPort) { diff --git a/server/model/monitor.js b/server/model/monitor.js index 52e1da34..2abf4be3 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -42,7 +42,7 @@ class Monitor extends BeanModel { /** * Return an object that ready to parse to JSON */ - async toJSON() { + async toJSON(includeSensitiveData = true) { let notificationIDList = {}; @@ -56,15 +56,11 @@ class Monitor extends BeanModel { const tags = await this.getTags(); - return { + let data = { id: this.id, name: this.name, url: this.url, method: this.method, - body: this.body, - headers: this.headers, - basic_auth_user: this.basic_auth_user, - basic_auth_pass: this.basic_auth_pass, hostname: this.hostname, port: this.port, maxretries: this.maxretries, @@ -82,7 +78,6 @@ class Monitor extends BeanModel { dns_resolve_type: this.dns_resolve_type, dns_resolve_server: this.dns_resolve_server, dns_last_result: this.dns_last_result, - pushToken: this.pushToken, proxyId: this.proxy_id, notificationIDList, tags: tags, @@ -91,10 +86,23 @@ class Monitor extends BeanModel { mqttTopic: this.mqttTopic, mqttSuccessMessage: this.mqttSuccessMessage }; + + if (includeSensitiveData) { + data = { + ...data, + headers: this.headers, + body: this.body, + basic_auth_user: this.basic_auth_user, + basic_auth_pass: this.basic_auth_pass, + pushToken: this.pushToken, + }; + } + + return data; } async getTags() { - return await R.getAll("SELECT mt.*, tag.name, tag.color FROM monitor_tag mt JOIN tag ON mt.tag_id = tag.id WHERE mt.monitor_id = ?", [this.id]); + return await R.getAll("SELECT mt.*, tag.name, tag.color FROM monitor_tag mt JOIN tag ON mt.tag_id = tag.id WHERE mt.monitor_id = ?", [ this.id ]); } /** @@ -266,7 +274,7 @@ class Monitor extends BeanModel { log.debug("monitor", "Cert Info Query Time: " + (dayjs().valueOf() - certInfoStartTime) + "ms"); } - if (process.env.UPTIME_KUMA_LOG_RESPONSE_BODY_MONITOR_ID == this.id) { + if (process.env.UPTIME_KUMA_LOG_RESPONSE_BODY_MONITOR_ID === this.id) { log.info("monitor", res.data); } @@ -306,24 +314,24 @@ class Monitor extends BeanModel { let dnsRes = await dnsResolve(this.hostname, this.dns_resolve_server, this.dns_resolve_type); bean.ping = dayjs().valueOf() - startTime; - if (this.dns_resolve_type == "A" || this.dns_resolve_type == "AAAA" || this.dns_resolve_type == "TXT") { + if (this.dns_resolve_type === "A" || this.dns_resolve_type === "AAAA" || this.dns_resolve_type === "TXT") { dnsMessage += "Records: "; dnsMessage += dnsRes.join(" | "); - } else if (this.dns_resolve_type == "CNAME" || this.dns_resolve_type == "PTR") { + } else if (this.dns_resolve_type === "CNAME" || this.dns_resolve_type === "PTR") { dnsMessage = dnsRes[0]; - } else if (this.dns_resolve_type == "CAA") { + } else if (this.dns_resolve_type === "CAA") { dnsMessage = dnsRes[0].issue; - } else if (this.dns_resolve_type == "MX") { + } else if (this.dns_resolve_type === "MX") { dnsRes.forEach(record => { dnsMessage += `Hostname: ${record.exchange} - Priority: ${record.priority} | `; }); dnsMessage = dnsMessage.slice(0, -2); - } else if (this.dns_resolve_type == "NS") { + } else if (this.dns_resolve_type === "NS") { dnsMessage += "Servers: "; dnsMessage += dnsRes.join(" | "); - } else if (this.dns_resolve_type == "SOA") { + } else if (this.dns_resolve_type === "SOA") { dnsMessage += `NS-Name: ${dnsRes.nsname} | Hostmaster: ${dnsRes.hostmaster} | Serial: ${dnsRes.serial} | Refresh: ${dnsRes.refresh} | Retry: ${dnsRes.retry} | Expire: ${dnsRes.expire} | MinTTL: ${dnsRes.minttl}`; - } else if (this.dns_resolve_type == "SRV") { + } else if (this.dns_resolve_type === "SRV") { dnsRes.forEach(record => { dnsMessage += `Name: ${record.name} | Port: ${record.port} | Priority: ${record.priority} | Weight: ${record.weight} | `; }); @@ -620,11 +628,11 @@ class Monitor extends BeanModel { } static async sendCertInfo(io, monitorID, userID) { - let tls_info = await R.findOne("monitor_tls_info", "monitor_id = ?", [ + let tlsInfo = await R.findOne("monitor_tls_info", "monitor_id = ?", [ monitorID, ]); - if (tls_info != null) { - io.to(userID).emit("certInfo", monitorID, tls_info.info_json); + if (tlsInfo != null) { + io.to(userID).emit("certInfo", monitorID, tlsInfo.info_json); } } @@ -738,7 +746,7 @@ class Monitor extends BeanModel { for (let notification of notificationList) { try { - await Notification.send(JSON.parse(notification.config), msg, await monitor.toJSON(), bean.toJSON()); + await Notification.send(JSON.parse(notification.config), msg, await monitor.toJSON(false), bean.toJSON()); } catch (e) { log.error("monitor", "Cannot send notification to " + notification.name); log.error("monitor", e); diff --git a/server/model/status_page.js b/server/model/status_page.js index 1383d3b0..b1befc25 100644 --- a/server/model/status_page.js +++ b/server/model/status_page.js @@ -92,6 +92,9 @@ class StatusPage extends BeanModel { published: !!this.published, showTags: !!this.show_tags, domainNameList: this.getDomainNameList(), + customCSS: this.custom_css, + footerText: this.footer_text, + showPoweredBy: !!this.show_powered_by, }; } @@ -104,6 +107,9 @@ class StatusPage extends BeanModel { theme: this.theme, published: !!this.published, showTags: !!this.show_tags, + customCSS: this.custom_css, + footerText: this.footer_text, + showPoweredBy: !!this.show_powered_by, }; } diff --git a/server/notification-providers/alerta.js b/server/notification-providers/alerta.js index bcee80df..2b85d67a 100644 --- a/server/notification-providers/alerta.js +++ b/server/notification-providers/alerta.js @@ -40,17 +40,17 @@ class Alerta extends NotificationProvider { await axios.post(alertaUrl, postData, config); } else { let datadup = Object.assign( { - correlate: ["service_up", "service_down"], + correlate: [ "service_up", "service_down" ], event: monitorJSON["type"], group: "uptimekuma-" + monitorJSON["type"], resource: monitorJSON["name"], }, data ); - if (heartbeatJSON["status"] == DOWN) { + if (heartbeatJSON["status"] === DOWN) { datadup.severity = notification.alertaAlertState; // critical datadup.text = "Service " + monitorJSON["type"] + " is down."; await axios.post(alertaUrl, datadup, config); - } else if (heartbeatJSON["status"] == UP) { + } else if (heartbeatJSON["status"] === UP) { datadup.severity = notification.alertaRecoverState; // cleaned datadup.text = "Service " + monitorJSON["type"] + " is up."; await axios.post(alertaUrl, datadup, config); diff --git a/server/notification-providers/aliyun-sms.js b/server/notification-providers/aliyun-sms.js index 6a206320..9a14240c 100644 --- a/server/notification-providers/aliyun-sms.js +++ b/server/notification-providers/aliyun-sms.js @@ -64,7 +64,7 @@ class AliyunSMS extends NotificationProvider { }; let result = await axios(config); - if (result.data.Message == "OK") { + if (result.data.Message === "OK") { return true; } return false; diff --git a/server/notification-providers/apprise.js b/server/notification-providers/apprise.js index 692483d8..2d795d4e 100644 --- a/server/notification-providers/apprise.js +++ b/server/notification-providers/apprise.js @@ -1,12 +1,12 @@ const NotificationProvider = require("./notification-provider"); -const child_process = require("child_process"); +const childProcess = require("child_process"); class Apprise extends NotificationProvider { name = "apprise"; async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { - let s = child_process.spawnSync("apprise", [ "-vv", "-b", msg, notification.appriseURL]); + let s = childProcess.spawnSync("apprise", [ "-vv", "-b", msg, notification.appriseURL ]); let output = (s.stdout) ? s.stdout.toString() : "ERROR: maybe apprise not found"; diff --git a/server/notification-providers/bark.js b/server/notification-providers/bark.js index a4c52512..8920c295 100644 --- a/server/notification-providers/bark.js +++ b/server/notification-providers/bark.js @@ -28,12 +28,12 @@ class Bark extends NotificationProvider { barkEndpoint = barkEndpoint.substring(0, barkEndpoint.length - 1); } - if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] == UP) { + if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] === UP) { let title = "UptimeKuma Monitor Up"; return await this.postNotification(title, msg, barkEndpoint); } - if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] == DOWN) { + if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] === DOWN) { let title = "UptimeKuma Monitor Down"; return await this.postNotification(title, msg, barkEndpoint); } diff --git a/server/notification-providers/dingding.js b/server/notification-providers/dingding.js index cf08f14b..ef7dba0d 100644 --- a/server/notification-providers/dingding.js +++ b/server/notification-providers/dingding.js @@ -50,7 +50,7 @@ class DingDing extends NotificationProvider { }; let result = await axios(config); - if (result.data.errmsg == "ok") { + if (result.data.errmsg === "ok") { return true; } return false; diff --git a/server/notification-providers/discord.js b/server/notification-providers/discord.js index f5c22a44..dd63e74b 100644 --- a/server/notification-providers/discord.js +++ b/server/notification-providers/discord.js @@ -35,7 +35,7 @@ class Discord extends NotificationProvider { } // If heartbeatJSON is not null, we go into the normal alerting loop. - if (heartbeatJSON["status"] == DOWN) { + if (heartbeatJSON["status"] === DOWN) { let discorddowndata = { username: discordDisplayName, embeds: [{ @@ -70,7 +70,7 @@ class Discord extends NotificationProvider { await axios.post(notification.discordWebhookUrl, discorddowndata); return okMsg; - } else if (heartbeatJSON["status"] == UP) { + } else if (heartbeatJSON["status"] === UP) { let discordupdata = { username: discordDisplayName, embeds: [{ diff --git a/server/notification-providers/feishu.js b/server/notification-providers/feishu.js index 05fc9c18..73781ca4 100644 --- a/server/notification-providers/feishu.js +++ b/server/notification-providers/feishu.js @@ -21,7 +21,7 @@ class Feishu extends NotificationProvider { return okMsg; } - if (heartbeatJSON["status"] == DOWN) { + if (heartbeatJSON["status"] === DOWN) { let downdata = { msg_type: "post", content: { @@ -48,7 +48,7 @@ class Feishu extends NotificationProvider { return okMsg; } - if (heartbeatJSON["status"] == UP) { + if (heartbeatJSON["status"] === UP) { let updata = { msg_type: "post", content: { diff --git a/server/notification-providers/gorush.js b/server/notification-providers/gorush.js index 58da5525..6d756e46 100644 --- a/server/notification-providers/gorush.js +++ b/server/notification-providers/gorush.js @@ -18,7 +18,7 @@ class Gorush extends NotificationProvider { let data = { "notifications": [ { - "tokens": [notification.gorushDeviceToken], + "tokens": [ notification.gorushDeviceToken ], "platform": platformMapping[notification.gorushPlatform], "message": msg, // Optional diff --git a/server/notification-providers/line.js b/server/notification-providers/line.js index 6a09b502..e594e174 100644 --- a/server/notification-providers/line.js +++ b/server/notification-providers/line.js @@ -27,7 +27,7 @@ class Line extends NotificationProvider { ] }; await axios.post(lineAPIUrl, testMessage, config); - } else if (heartbeatJSON["status"] == DOWN) { + } else if (heartbeatJSON["status"] === DOWN) { let downMessage = { "to": notification.lineUserID, "messages": [ @@ -38,7 +38,7 @@ class Line extends NotificationProvider { ] }; await axios.post(lineAPIUrl, downMessage, config); - } else if (heartbeatJSON["status"] == UP) { + } else if (heartbeatJSON["status"] === UP) { let upMessage = { "to": notification.lineUserID, "messages": [ diff --git a/server/notification-providers/lunasea.js b/server/notification-providers/lunasea.js index 82d2fde5..b53f3241 100644 --- a/server/notification-providers/lunasea.js +++ b/server/notification-providers/lunasea.js @@ -20,7 +20,7 @@ class LunaSea extends NotificationProvider { return okMsg; } - if (heartbeatJSON["status"] == DOWN) { + if (heartbeatJSON["status"] === DOWN) { let downdata = { "title": "UptimeKuma Alert: " + monitorJSON["name"], "body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], @@ -29,7 +29,7 @@ class LunaSea extends NotificationProvider { return okMsg; } - if (heartbeatJSON["status"] == UP) { + if (heartbeatJSON["status"] === UP) { let updata = { "title": "UptimeKuma Alert: " + monitorJSON["name"], "body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], diff --git a/server/notification-providers/mattermost.js b/server/notification-providers/mattermost.js index fe7b685e..2076ad21 100644 --- a/server/notification-providers/mattermost.js +++ b/server/notification-providers/mattermost.js @@ -29,7 +29,7 @@ class Mattermost extends NotificationProvider { const mattermostIconEmoji = notification.mattermosticonemo; const mattermostIconUrl = notification.mattermosticonurl; - if (heartbeatJSON["status"] == DOWN) { + if (heartbeatJSON["status"] === DOWN) { let mattermostdowndata = { username: mattermostUserName, text: "Uptime Kuma Alert", @@ -73,7 +73,7 @@ class Mattermost extends NotificationProvider { mattermostdowndata ); return okMsg; - } else if (heartbeatJSON["status"] == UP) { + } else if (heartbeatJSON["status"] === UP) { let mattermostupdata = { username: mattermostUserName, text: "Uptime Kuma Alert", diff --git a/server/notification-providers/octopush.js b/server/notification-providers/octopush.js index 68416b9a..0eda940b 100644 --- a/server/notification-providers/octopush.js +++ b/server/notification-providers/octopush.js @@ -10,7 +10,7 @@ class Octopush extends NotificationProvider { try { // Default - V2 - if (notification.octopushVersion == 2 || !notification.octopushVersion) { + if (notification.octopushVersion === 2 || !notification.octopushVersion) { let config = { headers: { "api-key": notification.octopushAPIKey, @@ -31,13 +31,13 @@ class Octopush extends NotificationProvider { "sender": notification.octopushSenderName }; await axios.post("https://api.octopush.com/v1/public/sms-campaign/send", data, config); - } else if (notification.octopushVersion == 1) { + } else if (notification.octopushVersion === 1) { let data = { "user_login": notification.octopushDMLogin, "api_key": notification.octopushDMAPIKey, "sms_recipients": notification.octopushDMPhoneNumber, "sms_sender": notification.octopushDMSenderName, - "sms_type": (notification.octopushDMSMSType == "sms_premium") ? "FR" : "XXX", + "sms_type": (notification.octopushDMSMSType === "sms_premium") ? "FR" : "XXX", "transactional": "1", //octopush not supporting non ascii char "sms_text": msg.replace(/[^\x00-\x7F]/g, ""), diff --git a/server/notification-providers/onebot.js b/server/notification-providers/onebot.js index c08cc01e..6c62eccb 100644 --- a/server/notification-providers/onebot.js +++ b/server/notification-providers/onebot.js @@ -27,7 +27,7 @@ class OneBot extends NotificationProvider { "auto_escape": true, "message": pushText, }; - if (notification.msgType == "group") { + if (notification.msgType === "group") { data["message_type"] = "group"; data["group_id"] = notification.recieverId; } else { diff --git a/server/notification-providers/pushbullet.js b/server/notification-providers/pushbullet.js index 07b4ed68..7f7a1c8d 100644 --- a/server/notification-providers/pushbullet.js +++ b/server/notification-providers/pushbullet.js @@ -25,14 +25,14 @@ class Pushbullet extends NotificationProvider { "body": "Testing Successful.", }; await axios.post(pushbulletUrl, testdata, config); - } else if (heartbeatJSON["status"] == DOWN) { + } else if (heartbeatJSON["status"] === DOWN) { let downdata = { "type": "note", "title": "UptimeKuma Alert: " + monitorJSON["name"], "body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], }; await axios.post(pushbulletUrl, downdata, config); - } else if (heartbeatJSON["status"] == UP) { + } else if (heartbeatJSON["status"] === UP) { let updata = { "type": "note", "title": "UptimeKuma Alert: " + monitorJSON["name"], diff --git a/server/notification-providers/pushdeer.js b/server/notification-providers/pushdeer.js new file mode 100644 index 00000000..bbd83f4b --- /dev/null +++ b/server/notification-providers/pushdeer.js @@ -0,0 +1,52 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); +const { DOWN, UP } = require("../../src/util"); + +class PushDeer extends NotificationProvider { + + name = "PushDeer"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully."; + let pushdeerlink = "https://api2.pushdeer.com/message/push"; + + let valid = msg != null && monitorJSON != null && heartbeatJSON != null; + + let title; + if (valid && heartbeatJSON.status === UP) { + title = "## Uptime Kuma: " + monitorJSON.name + " up"; + } else if (valid && heartbeatJSON.status === DOWN) { + title = "## Uptime Kuma: " + monitorJSON.name + " down"; + } else { + title = "## Uptime Kuma Message"; + } + + let data = { + "pushkey": notification.pushdeerKey, + "text": title, + "desp": msg.replace(/\n/g, "\n\n"), + "type": "markdown", + }; + + try { + let res = await axios.post(pushdeerlink, data); + + if ("error" in res.data) { + let error = res.data.error; + this.throwGeneralAxiosError(error); + } + if (res.data.content.result.length === 0) { + let error = "Invalid PushDeer key"; + this.throwGeneralAxiosError(error); + } else if (JSON.parse(res.data.content.result[0]).success !== "ok") { + let error = "Unknown error"; + this.throwGeneralAxiosError(error); + } + return okMsg; + } catch (error) { + this.throwGeneralAxiosError(error); + } + } +} + +module.exports = PushDeer; diff --git a/server/notification-providers/smtp.js b/server/notification-providers/smtp.js index d85ee88c..a6a0cc01 100644 --- a/server/notification-providers/smtp.js +++ b/server/notification-providers/smtp.js @@ -1,6 +1,6 @@ const nodemailer = require("nodemailer"); const NotificationProvider = require("./notification-provider"); -const { DOWN, UP } = require("../../src/util"); +const { DOWN } = require("../../src/util"); class SMTP extends NotificationProvider { diff --git a/server/notification-providers/wecom.js b/server/notification-providers/wecom.js index b377cedd..deca278c 100644 --- a/server/notification-providers/wecom.js +++ b/server/notification-providers/wecom.js @@ -26,10 +26,10 @@ class WeCom extends NotificationProvider { composeMessage(heartbeatJSON, msg) { let title; - if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] == UP) { + if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] === UP) { title = "UptimeKuma Monitor Up"; } - if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] == DOWN) { + if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] === DOWN) { title = "UptimeKuma Monitor Down"; } if (msg != null) { diff --git a/server/notification.js b/server/notification.js index a83a8cdc..842e0e2f 100644 --- a/server/notification.js +++ b/server/notification.js @@ -32,6 +32,7 @@ const GoogleChat = require("./notification-providers/google-chat"); const Gorush = require("./notification-providers/gorush"); const Alerta = require("./notification-providers/alerta"); const OneBot = require("./notification-providers/onebot"); +const PushDeer = require("./notification-providers/pushdeer"); class Notification { @@ -75,6 +76,7 @@ class Notification { new Gorush(), new Alerta(), new OneBot(), + new PushDeer(), ]; for (let item of list) { diff --git a/server/prometheus.js b/server/prometheus.js index 9634c308..fe0896f6 100644 --- a/server/prometheus.js +++ b/server/prometheus.js @@ -9,24 +9,24 @@ const commonLabels = [ "monitor_port", ]; -const monitor_cert_days_remaining = new PrometheusClient.Gauge({ +const monitorCertDaysRemaining = new PrometheusClient.Gauge({ name: "monitor_cert_days_remaining", help: "The number of days remaining until the certificate expires", labelNames: commonLabels }); -const monitor_cert_is_valid = new PrometheusClient.Gauge({ +const monitorCertIsValid = new PrometheusClient.Gauge({ name: "monitor_cert_is_valid", help: "Is the certificate still valid? (1 = Yes, 0= No)", labelNames: commonLabels }); -const monitor_response_time = new PrometheusClient.Gauge({ +const monitorResponseTime = new PrometheusClient.Gauge({ name: "monitor_response_time", help: "Monitor Response Time (ms)", labelNames: commonLabels }); -const monitor_status = new PrometheusClient.Gauge({ +const monitorStatus = new PrometheusClient.Gauge({ name: "monitor_status", help: "Monitor Status (1 = UP, 0= DOWN)", labelNames: commonLabels @@ -49,13 +49,13 @@ class Prometheus { if (typeof tlsInfo !== "undefined") { try { - let isValid = 0; - if (tlsInfo.valid == true) { + let isValid; + if (tlsInfo.valid === true) { isValid = 1; } else { isValid = 0; } - monitor_cert_is_valid.set(this.monitorLabelValues, isValid); + monitorCertIsValid.set(this.monitorLabelValues, isValid); } catch (e) { log.error("prometheus", "Caught error"); log.error("prometheus", e); @@ -63,7 +63,7 @@ class Prometheus { try { if (tlsInfo.certInfo != null) { - monitor_cert_days_remaining.set(this.monitorLabelValues, tlsInfo.certInfo.daysRemaining); + monitorCertDaysRemaining.set(this.monitorLabelValues, tlsInfo.certInfo.daysRemaining); } } catch (e) { log.error("prometheus", "Caught error"); @@ -72,7 +72,7 @@ class Prometheus { } try { - monitor_status.set(this.monitorLabelValues, heartbeat.status); + monitorStatus.set(this.monitorLabelValues, heartbeat.status); } catch (e) { log.error("prometheus", "Caught error"); log.error("prometheus", e); @@ -80,10 +80,10 @@ class Prometheus { try { if (typeof heartbeat.ping === "number") { - monitor_response_time.set(this.monitorLabelValues, heartbeat.ping); + monitorResponseTime.set(this.monitorLabelValues, heartbeat.ping); } else { // Is it good? - monitor_response_time.set(this.monitorLabelValues, -1); + monitorResponseTime.set(this.monitorLabelValues, -1); } } catch (e) { log.error("prometheus", "Caught error"); @@ -93,10 +93,10 @@ class Prometheus { remove() { try { - monitor_cert_days_remaining.remove(this.monitorLabelValues); - monitor_cert_is_valid.remove(this.monitorLabelValues); - monitor_response_time.remove(this.monitorLabelValues); - monitor_status.remove(this.monitorLabelValues); + monitorCertDaysRemaining.remove(this.monitorLabelValues); + monitorCertIsValid.remove(this.monitorLabelValues); + monitorResponseTime.remove(this.monitorLabelValues); + monitorStatus.remove(this.monitorLabelValues); } catch (e) { console.error(e); } diff --git a/server/proxy.js b/server/proxy.js index af72402d..3de6425c 100644 --- a/server/proxy.js +++ b/server/proxy.js @@ -7,7 +7,7 @@ const server = require("./server"); class Proxy { - static SUPPORTED_PROXY_PROTOCOLS = ["http", "https", "socks", "socks5", "socks4"] + static SUPPORTED_PROXY_PROTOCOLS = [ "http", "https", "socks", "socks5", "socks4" ] /** * Saves and updates given proxy entity @@ -21,7 +21,7 @@ class Proxy { let bean; if (proxyID) { - bean = await R.findOne("proxy", " id = ? AND user_id = ? ", [proxyID, userID]); + bean = await R.findOne("proxy", " id = ? AND user_id = ? ", [ proxyID, userID ]); if (!bean) { throw new Error("proxy not found"); @@ -71,14 +71,14 @@ class Proxy { * @return {Promise} */ static async delete(proxyID, userID) { - const bean = await R.findOne("proxy", " id = ? AND user_id = ? ", [proxyID, userID]); + const bean = await R.findOne("proxy", " id = ? AND user_id = ? ", [ proxyID, userID ]); if (!bean) { throw new Error("proxy not found"); } // Delete removed proxy from monitors if exists - await R.exec("UPDATE monitor SET proxy_id = null WHERE proxy_id = ?", [proxyID]); + await R.exec("UPDATE monitor SET proxy_id = null WHERE proxy_id = ?", [ proxyID ]); // Delete proxy from list await R.trash(bean); @@ -172,12 +172,12 @@ class Proxy { */ async function applyProxyEveryMonitor(proxyID, userID) { // Find all monitors with id and proxy id - const monitors = await R.getAll("SELECT id, proxy_id FROM monitor WHERE user_id = ?", [userID]); + const monitors = await R.getAll("SELECT id, proxy_id FROM monitor WHERE user_id = ?", [ userID ]); // Update proxy id not match with given proxy id for (const monitor of monitors) { if (monitor.proxy_id !== proxyID) { - await R.exec("UPDATE monitor SET proxy_id = ? WHERE id = ?", [proxyID, monitor.id]); + await R.exec("UPDATE monitor SET proxy_id = ? WHERE id = ?", [ proxyID, monitor.id ]); } } } diff --git a/server/routers/api-router.js b/server/routers/api-router.js index ad1a671b..51aa5f3f 100644 --- a/server/routers/api-router.js +++ b/server/routers/api-router.js @@ -1,5 +1,5 @@ let express = require("express"); -const { allowDevAllOrigin, getSettings, setting } = require("../util-server"); +const { allowDevAllOrigin } = require("../util-server"); const { R } = require("redbean-node"); const server = require("../server"); const apicache = require("../modules/apicache"); @@ -195,14 +195,6 @@ router.get("/api/status-page/heartbeat/:slug", cache("1 minutes"), async (reques } }); -/** - * Default is published - * @returns {Promise} - */ -async function isPublished() { - return true; -} - function send403(res, msg = "") { res.status(403).json({ "status": "fail", diff --git a/server/server.js b/server/server.js index 82c014ea..728e1d9b 100644 --- a/server/server.js +++ b/server/server.js @@ -11,7 +11,7 @@ if (nodeVersion < requiredVersion) { } const args = require("args-parser")(process.argv); -const { sleep, log, getRandomInt, genSecret, debug } = require("../src/util"); +const { sleep, log, getRandomInt, genSecret, debug, isDev } = require("../src/util"); const config = require("./config"); log.info("server", "Welcome to Uptime Kuma"); @@ -108,7 +108,7 @@ if (hostname) { log.info("server", "Custom hostname: " + hostname); } -const port = [args.port, process.env.UPTIME_KUMA_PORT, process.env.PORT, 3001] +const port = [ args.port, process.env.UPTIME_KUMA_PORT, process.env.PORT, 3001 ] .map(portValue => parseInt(portValue)) .find(portValue => !isNaN(portValue)); @@ -119,7 +119,7 @@ const disableFrameSameOrigin = args["disable-frame-sameorigin"] || !!process.env const cloudflaredToken = args["cloudflared-token"] || process.env.UPTIME_KUMA_CLOUDFLARED_TOKEN || undefined; // 2FA / notp verification defaults -const twofa_verification_opts = { +const twoFAVerifyOptions = { "window": 1, "time": 30 }; @@ -175,6 +175,7 @@ app.use(function (req, res, next) { /** * Total WebSocket client connected to server currently, no actual use + * * @type {number} */ let totalClient = 0; @@ -234,6 +235,13 @@ try { } }); + if (isDev) { + app.post("/test-webhook", async (request, response) => { + log.debug("test", request.body); + response.send("OK"); + }); + } + // Robots.txt app.get("/robots.txt", async (_request, response) => { let txt = "User-agent: *\nDisallow:"; @@ -379,7 +387,7 @@ try { } if (data.token) { - let verify = notp.totp.verify(data.token, user.twofa_secret, twofa_verification_opts); + let verify = notp.totp.verify(data.token, user.twofa_secret, twoFAVerifyOptions); if (user.twofa_last_token !== data.token && verify) { afterLogin(socket, user); @@ -546,7 +554,7 @@ try { socket.userID, ]); - let verify = notp.totp.verify(token, user.twofa_secret, twofa_verification_opts); + let verify = notp.totp.verify(token, user.twofa_secret, twoFAVerifyOptions); if (user.twofa_last_token !== token && verify) { callback({ @@ -1239,7 +1247,7 @@ try { const exists = proxies.find(item => item.id === proxy.id); // Do not process when proxy already exists in import handle is skip and keep - if (["skip", "keep"].includes(importHandle) && !exists) { + if ([ "skip", "keep" ].includes(importHandle) && !exists) { return; } diff --git a/server/socket-handlers/status-page-socket-handler.js b/server/socket-handlers/status-page-socket-handler.js index 36e90fb9..a06271da 100644 --- a/server/socket-handlers/status-page-socket-handler.js +++ b/server/socket-handlers/status-page-socket-handler.js @@ -155,6 +155,9 @@ module.exports.statusPageSocketHandler = (socket) => { //statusPage.search_engine_index = ; statusPage.show_tags = config.showTags; //statusPage.password = null; + statusPage.footer_text = config.footerText; + statusPage.custom_css = config.customCSS; + statusPage.show_powered_by = config.showPoweredBy; statusPage.modified_date = R.isoDateTime(); await R.store(statusPage); diff --git a/server/util-server.js b/server/util-server.js index d0a932aa..a9ec89e6 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -146,11 +146,11 @@ exports.mqttAsync = function (hostname, topic, okMessage, options = {}) { }); }; -exports.dnsResolve = function (hostname, resolver_server, rrtype) { +exports.dnsResolve = function (hostname, resolverServer, rrtype) { const resolver = new Resolver(); - resolver.setServers([resolver_server]); + resolver.setServers([ resolverServer ]); return new Promise((resolve, reject) => { - if (rrtype == "PTR") { + if (rrtype === "PTR") { resolver.reverse(hostname, (err, records) => { if (err) { reject(err); @@ -315,19 +315,19 @@ exports.checkCertificate = function (res) { // Return: true if the status code is within the accepted ranges, false otherwise // Will throw an error if the provided status code is not a valid range string or code string -exports.checkStatusCode = function (status, accepted_codes) { - if (accepted_codes == null || accepted_codes.length === 0) { +exports.checkStatusCode = function (status, acceptedCodes) { + if (acceptedCodes == null || acceptedCodes.length === 0) { return false; } - for (const code_range of accepted_codes) { - const code_range_split = code_range.split("-").map(string => parseInt(string)); - if (code_range_split.length === 1) { - if (status === code_range_split[0]) { + for (const codeRange of acceptedCodes) { + const codeRangeSplit = codeRange.split("-").map(string => parseInt(string)); + if (codeRangeSplit.length === 1) { + if (status === codeRangeSplit[0]) { return true; } - } else if (code_range_split.length === 2) { - if (status >= code_range_split[0] && status <= code_range_split[1]) { + } else if (codeRangeSplit.length === 2) { + if (status >= codeRangeSplit[0] && status <= codeRangeSplit[1]) { return true; } } else { @@ -403,7 +403,7 @@ exports.doubleCheckPassword = async (socket, currentPassword) => { exports.startUnitTest = async () => { console.log("Starting unit test..."); const npm = /^win/.test(process.platform) ? "npm.cmd" : "npm"; - const child = childProcess.spawn(npm, ["run", "jest"]); + const child = childProcess.spawn(npm, [ "run", "jest" ]); child.stdout.on("data", (data) => { console.log(data.toString()); diff --git a/src/assets/app.scss b/src/assets/app.scss index 0b27c6a6..c3f2fa79 100644 --- a/src/assets/app.scss +++ b/src/assets/app.scss @@ -469,6 +469,10 @@ textarea.form-control { color: $primary; } +.prism-editor__textarea { + outline: none !important; +} + // Localization @import "localization.scss"; diff --git a/src/components/Confirm.vue b/src/components/Confirm.vue index 99407583..1bfe7fe4 100644 --- a/src/components/Confirm.vue +++ b/src/components/Confirm.vue @@ -42,6 +42,7 @@ export default { default: "No", }, }, + emits: [ "yes" ], data: () => ({ modal: null, }), diff --git a/src/components/CopyableInput.vue b/src/components/CopyableInput.vue index 1fe89802..1bccfa2c 100644 --- a/src/components/CopyableInput.vue +++ b/src/components/CopyableInput.vue @@ -57,6 +57,7 @@ export default { default: undefined, }, }, + emits: [ "update:modelValue" ], data() { return { visibility: "password", diff --git a/src/components/CountUp.vue b/src/components/CountUp.vue index 5a0deb74..41edc4a0 100644 --- a/src/components/CountUp.vue +++ b/src/components/CountUp.vue @@ -10,7 +10,7 @@ import { sleep } from "../util.ts"; export default { props: { - value: [String, Number], + value: [ String, Number ], time: { type: Number, default: 0.3, diff --git a/src/components/HiddenInput.vue b/src/components/HiddenInput.vue index 1d32a2e6..d2327b9d 100644 --- a/src/components/HiddenInput.vue +++ b/src/components/HiddenInput.vue @@ -48,6 +48,7 @@ export default { default: undefined, }, }, + emits: [ "update:modelValue" ], data() { return { visibility: "password", diff --git a/src/components/NotificationDialog.vue b/src/components/NotificationDialog.vue index 96ab8580..7a1f1a10 100644 --- a/src/components/NotificationDialog.vue +++ b/src/components/NotificationDialog.vue @@ -78,7 +78,7 @@ export default { Confirm, }, props: {}, - emits: ["added"], + emits: [ "added" ], data() { return { model: null, diff --git a/src/components/PingChart.vue b/src/components/PingChart.vue index 4ff4c708..9132b4e9 100644 --- a/src/components/PingChart.vue +++ b/src/components/PingChart.vue @@ -220,6 +220,7 @@ export default { if (newPeriod == "0") { newPeriod = null; this.heartbeatList = null; + this.$root.storage().removeItem(`chart-period-${this.monitorId}`); } else { this.loading = true; @@ -228,6 +229,7 @@ export default { toast.error(res.msg); } else { this.heartbeatList = res.data; + this.$root.storage()[`chart-period-${this.monitorId}`] = newPeriod; } this.loading = false; }); @@ -248,6 +250,12 @@ export default { }, { deep: true } ); + + // Load chart period from storage if saved + let period = this.$root.storage()[`chart-period-${this.monitorId}`]; + if (period != null) { + this.chartPeriodHrs = Math.min(period, 6); + } } }; diff --git a/src/components/ProxyDialog.vue b/src/components/ProxyDialog.vue index a6c23465..3070925c 100644 --- a/src/components/ProxyDialog.vue +++ b/src/components/ProxyDialog.vue @@ -105,7 +105,7 @@ export default { Confirm, }, props: {}, - emits: ["added"], + emits: [ "added" ], data() { return { model: null, diff --git a/src/components/notifications/PushDeer.vue b/src/components/notifications/PushDeer.vue new file mode 100644 index 00000000..c2b7f5cb --- /dev/null +++ b/src/components/notifications/PushDeer.vue @@ -0,0 +1,19 @@ + + + diff --git a/src/components/notifications/index.js b/src/components/notifications/index.js index 2fa36c0f..496d35fa 100644 --- a/src/components/notifications/index.js +++ b/src/components/notifications/index.js @@ -30,6 +30,7 @@ import GoogleChat from "./GoogleChat.vue"; import Gorush from "./Gorush.vue"; import Alerta from "./Alerta.vue"; import OneBot from "./OneBot.vue"; +import PushDeer from "./PushDeer.vue"; /** * Manage all notification form. @@ -69,6 +70,7 @@ const NotificationFormList = { "gorush": Gorush, "alerta": Alerta, "OneBot": OneBot, + "PushDeer": PushDeer, }; export default NotificationFormList; diff --git a/src/i18n.js b/src/i18n.js index 5505e5c2..83a31991 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -43,7 +43,7 @@ for (let lang in languageList) { }; } -const rtlLangs = ["fa"]; +const rtlLangs = [ "fa" ]; export const currentLocale = () => localStorage.locale || languageList[navigator.language] && navigator.language diff --git a/src/languages/bg-BG.js b/src/languages/bg-BG.js index 787db0b2..01c342fa 100644 --- a/src/languages/bg-BG.js +++ b/src/languages/bg-BG.js @@ -197,7 +197,7 @@ export default { line: "Line Messenger", mattermost: "Mattermost", "Status Page": "Статус страница", - "Status Pages": "Статус страница", + "Status Pages": "Статус страници", "Primary Base URL": "Основен базов URL адрес", "Push URL": "Генериран Push URL адрес", needPushEvery: "Необходимо е да извършвате заявка към този URL адрес на всеки {0} секунди", @@ -371,4 +371,75 @@ export default { alertaAlertState: "Състояние на тревога", alertaRecoverState: "Състояние на възстановяване", deleteStatusPageMsg: "Сигурни ли сте, че желаете да изтриете тази статус страница?", + Proxies: "Проксита", + default: "По подразбиране", + enabled: "Включено", + setAsDefault: "Зададен по подразбиране", + deleteProxyMsg: "Сигурни ли сте, че желаете да изтриете това прокси за всички монитори?", + proxyDescription: "Прокситата трябва да бъдат зададени към монитор за да функционират.", + enableProxyDescription: "Това прокси няма да има ефект върху заявките за мониторинг, докато не бъде активирано. Може да контролирате временното деактивиране на проксито от всички монитори чрез статуса на активиране.", + setAsDefaultProxyDescription: "Това проки ще бъде включено по подразбиране за новите монитори. Може да го изключите по отделно за всеки един монитор.", + "Certificate Chain": "Верига на сертификата", + Valid: "Валиден", + Invalid: "Невалиден", + AccessKeyId: "ID на ключ за достъп", + SecretAccessKey: "Тайна на ключа за достъп", + PhoneNumbers: "Телефонни номера", + TemplateCode: "Шаблон Код", + SignName: "Знак име", + "Sms template must contain parameters: ": "SMS шаблонът трябва да съдържа следните параметри: ", + "Bark Endpoint": "Bark крайна точка", + WebHookUrl: "URL адрес на уеб кука", + SecretKey: "Таен ключ", + "For safety, must use secret key": "За сигурност, трябва да се използва таен ключ", + "Device Token": "Токен за устройство", + Platform: "Платформа", + iOS: "iOS", + Android: "Android", + Huawei: "Huawei", + High: "Висок", + Retry: "Повтори", + Topic: "Тема", + "WeCom Bot Key": "WeCom бот ключ", + "Setup Proxy": "Настройка за прокси", + "Proxy Protocol": "Прокси протокол", + "Proxy Server": "Прокси сървър", + "Proxy server has authentication": "Прокси сървърът е с удостоверяване", + User: "Потребител", + Installed: "Инсталиран", + "Not installed": "Не е инсталиран", + Running: "Работи", + "Not running": "Не работи", + "Remove Token": "Премахни токен", + Start: "Старт", + Stop: "Стоп", + "Uptime Kuma": "Uptime Kuma", + "Add New Status Page": "Добави нова статус страница", + Slug: "Слъг", + "Accept characters:": "Приеми символи:", + startOrEndWithOnly: "Започва или завършва само с {0}", + "No consecutive dashes": "Без последователни тирета", + Next: "Следващ", + "The slug is already taken. Please choose another slug.": "Този слъг вече се използва. Моля изберете друг.", + "No Proxy": "Без прокси", + "HTTP Basic Auth": "HTTP основно удостоверяване", + "New Status Page": "Нова статус страница", + "Page Not Found": "Страницата не е открита", + "Reverse Proxy": "Ревърс прокси", + Backup: "Архивиране", + About: "Относно", + wayToGetCloudflaredURL: "(Свалете \"cloudflared\" от {0})", + cloudflareWebsite: "Cloudflare уебсайт", + "Message:": "Съобщение:", + "Don't know how to get the token? Please read the guide:": "Не знаете как да вземете токен? Моля, прочетете ръководството:", + "The current connection may be lost if you are currently connecting via Cloudflare Tunnel. Are you sure want to stop it? Type your current password to confirm it.": "Текущата връзка може да прекъсне ако в момента сте свързани чрез \"Cloudflare Tunnel\". Сигурни ли сте, че желаете да го спрете? Въведете Вашата текуща парола за да потвърдите.", + "Other Software": "Друг софтуер", + "For example: nginx, Apache and Traefik.": "Например: Nginx, Apache и Traefik.", + "Please read": "Моля, прочетете", + "Subject:": "Тема:", + "Valid To:": "Валиден до:", + "Days Remaining:": "Оставащи дни:", + "Issuer:": "Издател:", + "Fingerprint:": "Пръстов отпечатък:", + "No status pages": "Няма статус страници", }; diff --git a/src/languages/de-DE.js b/src/languages/de-DE.js index 56452c2a..1dd86e23 100644 --- a/src/languages/de-DE.js +++ b/src/languages/de-DE.js @@ -337,7 +337,7 @@ export default { "Hide Tags": "Tags ausblenden", Description: "Beschreibung", "No monitors available.": "Keine Monitore verfügbar.", - "Add one": "Füge eins hinzu", + "Add one": "Hinzufügen", "No Monitors": "Keine Monitore", "Untitled Group": "Gruppe ohne Titel", Services: "Dienste", @@ -442,4 +442,7 @@ export default { "Issuer:": "Aussteller:", "Fingerprint:": "Fingerabdruck:", "No status pages": "Keine Status-Seiten", + Customize: "Anpassen", + "Custom Footer": "Eigener Footer", + "Custom CSS": "Eigenes CSS", }; diff --git a/src/languages/en.js b/src/languages/en.js index 2c421f0f..30f0b6ea 100644 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -358,6 +358,9 @@ export default { serwersmsPhoneNumber: "Phone number", serwersmsSenderName: "SMS Sender Name (registered via customer portal)", stackfield: "Stackfield", + Customize: "Customize", + "Custom Footer": "Custom Footer", + "Custom CSS": "Custom CSS", smtpDkimSettings: "DKIM Settings", smtpDkimDesc: "Please refer to the Nodemailer DKIM {0} for usage.", documentation: "documentation", @@ -455,4 +458,5 @@ export default { onebotPrivateMessage: "Private", onebotUserOrGroupId: "Group/User ID", onebotSafetyTips: "For safety, must set access token", + "PushDeer Key": "PushDeer Key", }; diff --git a/src/mixins/socket.js b/src/mixins/socket.js index 0f9846d4..633daa73 100644 --- a/src/mixins/socket.js +++ b/src/mixins/socket.js @@ -1,6 +1,6 @@ import { io } from "socket.io-client"; import { useToast } from "vue-toastification"; -import jwt_decode from "jwt-decode"; +import jwtDecode from "jwt-decode"; import Favico from "favico.js"; const toast = useToast(); @@ -89,7 +89,7 @@ export default { } socket = io(wsHost, { - transports: ["websocket"], + transports: [ "websocket" ], }); socket.on("info", (info) => { @@ -108,7 +108,7 @@ export default { socket.on("monitorList", (data) => { // Add Helper function - Object.entries(data).forEach(([monitorID, monitor]) => { + Object.entries(data).forEach(([ monitorID, monitor ]) => { monitor.getUrl = () => { try { return new URL(monitor.url); @@ -266,7 +266,7 @@ export default { const jwtToken = this.$root.storage().token; if (jwtToken && jwtToken !== "autoLogin") { - return jwt_decode(jwtToken); + return jwtDecode(jwtToken); } return undefined; }, diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index f05e436f..405ed5f8 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -426,17 +426,17 @@ export default { }, bodyPlaceholder() { - return this.$t("Example:", [` + return this.$t("Example:", [ ` { "key": "value" -}`]); +}` ]); }, headersPlaceholder() { - return this.$t("Example:", [` + return this.$t("Example:", [ ` { "HeaderName": "HeaderValue" -}`]); +}` ]); } }, @@ -521,7 +521,7 @@ export default { upsideDown: false, expiryNotification: false, maxredirects: 10, - accepted_statuscodes: ["200-299"], + accepted_statuscodes: [ "200-299" ], dns_resolve_type: "A", dns_resolve_server: "1.1.1.1", proxyId: null, diff --git a/src/pages/StatusPage.vue b/src/pages/StatusPage.vue index efe03cae..4b94289c 100644 --- a/src/pages/StatusPage.vue +++ b/src/pages/StatusPage.vue @@ -16,11 +16,18 @@ +
+ +
+ + +
+
@@ -31,6 +38,12 @@
+ +
+ + +
+
@@ -51,6 +64,12 @@
+ +
+
{{ $t("Custom CSS") }}
+ +
+
{{ $t("deleteStatusPageMsg") }} + + + {{ config.customCSS }} + @@ -259,11 +289,20 @@ import dayjs from "dayjs"; import Favico from "favico.js"; import { getResBaseURL } from "../util-frontend"; import Confirm from "../components/Confirm.vue"; +// import Prism Editor +import { PrismEditor } from "vue-prism-editor"; +import "vue-prism-editor/dist/prismeditor.min.css"; // import the styles somewhere + +// import highlighting library (you can use any library you want just return html string) +import { highlight, languages } from "prismjs/components/prism-core"; +import "prismjs/components/prism-css"; +import "prismjs/themes/prism-tomorrow.css"; // import syntax highlighting styles const toast = useToast(); const leavePageMsg = "Do you really want to leave? you have unsaved changes!"; +// eslint-disable-next-line no-unused-vars let feedInterval; const favicon = new Favico({ @@ -276,6 +315,7 @@ export default { PublicGroupList, ImageCropUpload, Confirm, + PrismEditor, }, // Leave Page for vue route change @@ -418,6 +458,13 @@ export default { this.$root.getSocket().emit("getStatusPage", this.slug, (res) => { if (res.ok) { this.config = res.config; + + if (!this.config.customCSS) { + this.config.customCSS = "body {\n" + + " \n" + + "}\n"; + } + } else { toast.error(res.msg); } @@ -520,6 +567,10 @@ export default { }, methods: { + highlighter(code) { + return highlight(code, languages.css); + }, + updateHeartbeatList() { // If editMode, it will use the data from websocket. if (! this.editMode) { @@ -893,4 +944,18 @@ footer { } } +/* required class */ +.css-editor { + /* we dont use `language-` classes anymore so thats why we need to add background and text color manually */ + + border-radius: 1rem; + padding: 10px 5px; + border: 1px solid #ced4da; + + .dark & { + background: $dark-bg; + border: 1px solid $dark-border-color; + } +} +