diff --git a/.dockerignore b/.dockerignore index cdd61ffc2..9c16887bd 100644 --- a/.dockerignore +++ b/.dockerignore @@ -20,6 +20,11 @@ yarn.lock app.json CODE_OF_CONDUCT.md CONTRIBUTING.md +CNAME +install.sh +SECURITY.md +tsconfig.json + ### .gitignore content (commented rules are duplicated) diff --git a/.eslintrc.js b/.eslintrc.js index fe63d4a4e..6704a85b2 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -17,7 +17,8 @@ module.exports = { }, rules: { "camelcase": ["warn", { - "properties": "never" + "properties": "never", + "ignoreImports": true }], // override/add rules settings here, such as: // 'vue/no-unused-vars': 'error' diff --git a/README.md b/README.md index 925b8798e..724bdd6ab 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,24 @@ It is a self-hosted monitoring tool like "Uptime Robot". +## 🥔 Live Demo + +Try it! + +https://demo.uptime.kuma.pet + +It is a 5 minutes live demo, all data will be deleted after that. The server is located at Tokyo, if you live far away from here, it may affact your experience. I suggest that you should install to try it. + +VPS is sponsored by Uptime Kuma sponsors on [Open Collective](https://opencollective.com/uptime-kuma)! Thank you so much! + + ## ⭐ Features * Monitoring uptime for HTTP(s) / TCP / Ping / DNS Record. * Fancy, Reactive, Fast UI/UX. * Notifications via Telegram, Discord, Gotify, Slack, Pushover, Email (SMTP), and [70+ notification services, click here for the full list](https://github.com/louislam/uptime-kuma/issues/284). * 20 seconds interval. +* [Multi Languages](https://github.com/louislam/uptime-kuma/tree/master/src/languages) ## 🔧 How to Install @@ -95,10 +107,23 @@ Telegram Notification Sample: If you love this project, please consider giving me a ⭐. + +## 🗣️ Discussion + +You can also discuss or ask for help in [Issues](https://github.com/louislam/uptime-kuma/issues). + +Alternatively, you can discuss in my original post on reddit: https://www.reddit.com/r/selfhosted/comments/oi7dc7/uptime_kuma_a_fancy_selfhosted_monitoring_tool_an/ + +I think the real "Discussion" tab is hard to use, as it is reddit-like flow, I always missed new comments. + + ## Contribute -If you want to report a bug or request a new feature. Free feel to open a new issue. +If you want to report a bug or request a new feature. Free feel to open a [new issue](https://github.com/louislam/uptime-kuma/issues). + +If you want to translate Uptime Kuma into your langauge, please read: https://github.com/louislam/uptime-kuma/tree/master/src/languages If you want to modify Uptime Kuma, this guideline may be useful for you: https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md English proofreading is needed too because my grammar is not that great sadly. Feel free to correct my grammar in this readme, source code, or wiki. + diff --git a/db/demo_kuma.db b/db/demo_kuma.db new file mode 100644 index 000000000..2042fcf2f Binary files /dev/null and b/db/demo_kuma.db differ diff --git a/db/patch11.sql b/db/patch11.sql new file mode 100644 index 000000000..c07d62981 --- /dev/null +++ b/db/patch11.sql @@ -0,0 +1,10 @@ +-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. +BEGIN TRANSACTION; + +-- For sendHeartbeatList +CREATE INDEX monitor_time_index ON heartbeat (monitor_id, time); + +-- For sendImportantHeartbeatList +CREATE INDEX monitor_important_time_index ON heartbeat (monitor_id, important,time); + +COMMIT; diff --git a/db/patch9.sql b/db/patch9.sql new file mode 100644 index 000000000..d4d13aab1 --- /dev/null +++ b/db/patch9.sql @@ -0,0 +1,7 @@ +-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. +BEGIN TRANSACTION; + +ALTER TABLE notification + ADD is_default BOOLEAN default 0 NOT NULL; + +COMMIT; diff --git a/dockerfile b/dockerfile index 8674b9993..ddb5f4e8c 100644 --- a/dockerfile +++ b/dockerfile @@ -1,25 +1,30 @@ -# DON'T UPDATE TO alpine3.13, 1.14, see #41. -FROM node:14-alpine3.12 AS release +FROM node:14-bullseye-slim AS release WORKDIR /app +# install dependencies +RUN apt update && apt --yes install python3 python3-pip python3-dev git g++ make iputils-ping +RUN ln -s /usr/bin/python3 /usr/bin/python + # split the sqlite install here, so that it can caches the arm prebuilt -RUN apk add --no-cache --virtual .build-deps make g++ python3 python3-dev git && \ - ln -s /usr/bin/python3 /usr/bin/python && \ - npm install mapbox/node-sqlite3#593c9d && \ - apk del .build-deps && \ - rm -f /usr/bin/python +RUN npm install mapbox/node-sqlite3#593c9d # Install apprise -RUN apk add --no-cache python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib +RUN apt --yes install python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib RUN pip3 --no-cache-dir install apprise && \ - rm -rf /root/.cache + rm -rf /root/.cache + +# additional package should be added here, since we don't want to re-compile the arm prebuilt again + +# add sqlite3 cli for debugging in the future +RUN apt --yes install sqlite3 + COPY . . RUN npm install --legacy-peer-deps && npm run build && npm prune EXPOSE 3001 VOLUME ["/app/data"] -HEALTHCHECK --interval=60s --timeout=30s --start-period=300s CMD node extra/healthcheck.js +HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --retries=5 CMD node extra/healthcheck.js CMD ["node", "server/server.js"] FROM release AS nightly diff --git a/dockerfile-alpine b/dockerfile-alpine new file mode 100644 index 000000000..c8bead8bb --- /dev/null +++ b/dockerfile-alpine @@ -0,0 +1,26 @@ +# DON'T UPDATE TO alpine3.13, 1.14, see #41. +FROM node:14-alpine3.12 AS release +WORKDIR /app + +# split the sqlite install here, so that it can caches the arm prebuilt +RUN apk add --no-cache --virtual .build-deps make g++ python3 python3-dev git && \ + ln -s /usr/bin/python3 /usr/bin/python && \ + npm install mapbox/node-sqlite3#593c9d && \ + apk del .build-deps && \ + rm -f /usr/bin/python + +# Install apprise +RUN apk add --no-cache python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib +RUN pip3 --no-cache-dir install apprise && \ + rm -rf /root/.cache + +COPY . . +RUN npm install --legacy-peer-deps && npm run build && npm prune + +EXPOSE 3001 +VOLUME ["/app/data"] +HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --retries=5 CMD node extra/healthcheck.js +CMD ["node", "server/server.js"] + +FROM release AS nightly +RUN npm run mark-as-nightly diff --git a/extra/healthcheck.js b/extra/healthcheck.js index c0b33b6ec..ba3569db7 100644 --- a/extra/healthcheck.js +++ b/extra/healthcheck.js @@ -1,19 +1,31 @@ -let http = require("http"); +process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; + +let client; + +if (process.env.SSL_KEY && process.env.SSL_CERT) { + client = require("https"); +} else { + client = require("http"); +} + let options = { - host: "localhost", - port: "3001", - timeout: 2000, + host: process.env.HOST || "127.0.0.1", + port: parseInt(process.env.PORT) || 3001, + timeout: 28 * 1000, }; -let request = http.request(options, (res) => { - console.log(`STATUS: ${res.statusCode}`); - if (res.statusCode == 200) { + +let request = client.request(options, (res) => { + console.log(`Health Check OK [Res Code: ${res.statusCode}]`); + if (res.statusCode === 200) { process.exit(0); } else { process.exit(1); } }); + request.on("error", function (err) { - console.log("ERROR"); + console.error("Health Check ERROR"); process.exit(1); }); + request.end(); diff --git a/extra/update-version.js b/extra/update-version.js index 697a64010..ca810a40a 100644 --- a/extra/update-version.js +++ b/extra/update-version.js @@ -23,6 +23,8 @@ if (! exists) { pkg.version = newVersion; pkg.scripts.setup = pkg.scripts.setup.replaceAll(oldVersion, newVersion); pkg.scripts["build-docker"] = pkg.scripts["build-docker"].replaceAll(oldVersion, newVersion); + pkg.scripts["build-docker-alpine"] = pkg.scripts["build-docker-alpine"].replaceAll(oldVersion, newVersion); + pkg.scripts["build-docker-debian"] = pkg.scripts["build-docker-debian"].replaceAll(oldVersion, newVersion); fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n"); commit(newVersion); diff --git a/package-lock.json b/package-lock.json index 25be14778..dcf5077da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "uptime-kuma", - "version": "1.5.0", + "version": "1.5.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "uptime-kuma", - "version": "1.5.0", + "version": "1.5.2", "license": "MIT", "dependencies": { "@fortawesome/fontawesome-svg-core": "^1.2.36", @@ -26,19 +26,19 @@ "express": "^4.17.1", "express-basic-auth": "^1.2.0", "form-data": "^4.0.0", - "http-graceful-shutdown": "^3.1.3", + "http-graceful-shutdown": "^3.1.4", "jsonwebtoken": "^8.5.1", "nodemailer": "^6.6.3", "password-hash": "^1.2.2", "prom-client": "^13.2.0", "prometheus-api-metrics": "^3.2.0", "redbean-node": "0.1.2", - "socket.io": "^4.1.3", - "socket.io-client": "^4.1.3", + "socket.io": "^4.2.0", + "socket.io-client": "^4.2.0", "sqlite3": "github:mapbox/node-sqlite3#593c9d", "tcp-ping": "^0.1.1", "v-pagination-3": "^0.1.6", - "vue": "^3.2.2", + "vue": "^3.2.8", "vue-chart-3": "^0.5.7", "vue-confirm-dialog": "^1.0.2", "vue-i18n": "^9.1.7", @@ -48,19 +48,19 @@ }, "devDependencies": { "@babel/eslint-parser": "^7.15.0", - "@types/bootstrap": "^5.1.1", - "@vitejs/plugin-legacy": "^1.5.1", - "@vitejs/plugin-vue": "^1.4.0", - "@vue/compiler-sfc": "^3.2.2", - "core-js": "^3.16.1", + "@types/bootstrap": "^5.1.2", + "@vitejs/plugin-legacy": "^1.5.2", + "@vitejs/plugin-vue": "^1.6.0", + "@vue/compiler-sfc": "^3.2.6", + "core-js": "^3.17.0", "dns2": "^2.0.1", "eslint": "^7.32.0", - "eslint-plugin-vue": "^7.16.0", - "sass": "^1.37.5", + "eslint-plugin-vue": "^7.17.0", + "sass": "^1.38.2", "stylelint": "^13.13.1", "stylelint-config-standard": "^22.0.0", - "typescript": "^4.3.5", - "vite": "^2.4.4" + "typescript": "^4.4.2", + "vite": "^2.5.3" }, "engines": { "node": "14.*" @@ -85,20 +85,20 @@ } }, "node_modules/@babel/core": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.15.0.tgz", - "integrity": "sha512-tXtmTminrze5HEUPn/a0JtOzzfp0nk+UEXQ/tqIJo3WDGypl/2OFQEMll/zSFU8f/lfmfLXvTaORHF3cfXIQMw==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.15.4.tgz", + "integrity": "sha512-Lkcv9I4a8bgUI8LJOLM6IKv6hnz1KOju6KM1lceqVMKlKKqNRopYd2Pc9MgIurqvMJ6BooemrnJz8jlIiQIpsA==", "dev": true, "dependencies": { "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.0", - "@babel/helper-compilation-targets": "^7.15.0", - "@babel/helper-module-transforms": "^7.15.0", - "@babel/helpers": "^7.14.8", - "@babel/parser": "^7.15.0", - "@babel/template": "^7.14.5", - "@babel/traverse": "^7.15.0", - "@babel/types": "^7.15.0", + "@babel/generator": "^7.15.4", + "@babel/helper-compilation-targets": "^7.15.4", + "@babel/helper-module-transforms": "^7.15.4", + "@babel/helpers": "^7.15.4", + "@babel/parser": "^7.15.4", + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -151,9 +151,9 @@ } }, "node_modules/@babel/eslint-parser": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.15.0.tgz", - "integrity": "sha512-+gSPtjSBxOZz4Uh8Ggqu7HbfpB8cT1LwW0DnVVLZEJvzXauiD0Di3zszcBkRmfGGrLdYeHUwcflG7i3tr9kQlw==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.15.4.tgz", + "integrity": "sha512-hPMIAmGNbmQzXJIo2P43Zj9UhRmGev5f9nqdBFOWNGDGh6XKmjby79woBvg6y0Jur6yRfQBneDbUQ8ZVc1krFw==", "dev": true, "dependencies": { "eslint-scope": "^5.1.1", @@ -169,12 +169,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.0.tgz", - "integrity": "sha512-eKl4XdMrbpYvuB505KTta4AV9g+wWzmVBW69tX0H2NwKVKd2YJbKgyK6M8j/rgLbmHOYJn6rUklV677nOyJrEQ==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.4.tgz", + "integrity": "sha512-d3itta0tu+UayjEORPNz6e1T3FtvWlP5N4V5M+lhp/CxT4oAA7/NcScnpRyspUMLK6tu9MNHmQHxRykuN2R7hw==", "dev": true, "dependencies": { - "@babel/types": "^7.15.0", + "@babel/types": "^7.15.4", "jsesc": "^2.5.1", "source-map": "^0.5.0" }, @@ -192,9 +192,9 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.0.tgz", - "integrity": "sha512-h+/9t0ncd4jfZ8wsdAsoIxSa61qhBYlycXiHWqJaQBCXAhDCMbPRSMTGnZIkkmt1u4ag+UQmuqcILwqKzZ4N2A==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz", + "integrity": "sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ==", "dev": true, "dependencies": { "@babel/compat-data": "^7.15.0", @@ -210,132 +210,132 @@ } }, "node_modules/@babel/helper-function-name": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", - "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", + "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", "dev": true, "dependencies": { - "@babel/helper-get-function-arity": "^7.14.5", - "@babel/template": "^7.14.5", - "@babel/types": "^7.14.5" + "@babel/helper-get-function-arity": "^7.15.4", + "@babel/template": "^7.15.4", + "@babel/types": "^7.15.4" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-get-function-arity": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", - "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", + "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", "dev": true, "dependencies": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.15.4" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz", - "integrity": "sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz", + "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", "dev": true, "dependencies": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.15.4" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.0.tgz", - "integrity": "sha512-Jq8H8U2kYiafuj2xMTPQwkTBnEEdGKpT35lJEQsRRjnG0LW3neucsaMWLgKcwu3OHKNeYugfw+Z20BXBSEs2Lg==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz", + "integrity": "sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA==", "dev": true, "dependencies": { - "@babel/types": "^7.15.0" + "@babel/types": "^7.15.4" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz", - "integrity": "sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz", + "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", "dev": true, "dependencies": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.15.4" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.0.tgz", - "integrity": "sha512-RkGiW5Rer7fpXv9m1B3iHIFDZdItnO2/BLfWVW/9q7+KqQSDY5kUfQEbzdXM1MVhJGcugKV7kRrNVzNxmk7NBg==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.4.tgz", + "integrity": "sha512-9fHHSGE9zTC++KuXLZcB5FKgvlV83Ox+NLUmQTawovwlJ85+QMhk1CnVk406CQVj97LaWod6KVjl2Sfgw9Aktw==", "dev": true, "dependencies": { - "@babel/helper-module-imports": "^7.14.5", - "@babel/helper-replace-supers": "^7.15.0", - "@babel/helper-simple-access": "^7.14.8", - "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/helper-module-imports": "^7.15.4", + "@babel/helper-replace-supers": "^7.15.4", + "@babel/helper-simple-access": "^7.15.4", + "@babel/helper-split-export-declaration": "^7.15.4", "@babel/helper-validator-identifier": "^7.14.9", - "@babel/template": "^7.14.5", - "@babel/traverse": "^7.15.0", - "@babel/types": "^7.15.0" + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz", - "integrity": "sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz", + "integrity": "sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw==", "dev": true, "dependencies": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.15.4" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.0.tgz", - "integrity": "sha512-6O+eWrhx+HEra/uJnifCwhwMd6Bp5+ZfZeJwbqUTuqkhIT6YcRhiZCOOFChRypOIe0cV46kFrRBlm+t5vHCEaA==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz", + "integrity": "sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw==", "dev": true, "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.15.0", - "@babel/helper-optimise-call-expression": "^7.14.5", - "@babel/traverse": "^7.15.0", - "@babel/types": "^7.15.0" + "@babel/helper-member-expression-to-functions": "^7.15.4", + "@babel/helper-optimise-call-expression": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-simple-access": { - "version": "7.14.8", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.14.8.tgz", - "integrity": "sha512-TrFN4RHh9gnWEU+s7JloIho2T76GPwRHhdzOWLqTrMnlas8T9O7ec+oEDNsRXndOmru9ymH9DFrEOxpzPoSbdg==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz", + "integrity": "sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg==", "dev": true, "dependencies": { - "@babel/types": "^7.14.8" + "@babel/types": "^7.15.4" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", - "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", + "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", "dev": true, "dependencies": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.15.4" }, "engines": { "node": ">=6.9.0" @@ -359,14 +359,14 @@ } }, "node_modules/@babel/helpers": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.3.tgz", - "integrity": "sha512-HwJiz52XaS96lX+28Tnbu31VeFSQJGOeKHJeaEPQlTl7PnlhFElWPj8tUXtqFIzeN86XxXoBr+WFAyK2PPVz6g==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.4.tgz", + "integrity": "sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ==", "dev": true, "dependencies": { - "@babel/template": "^7.14.5", - "@babel/traverse": "^7.15.0", - "@babel/types": "^7.15.0" + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4" }, "engines": { "node": ">=6.9.0" @@ -458,9 +458,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.3.tgz", - "integrity": "sha512-O0L6v/HvqbdJawj0iBEfVQMc3/6WP+AeOsovsIgBFyJaG+W2w7eqvZB7puddATmWuARlm1SX7DwxJ/JJUnDpEA==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.4.tgz", + "integrity": "sha512-xmzz+7fRpjrvDUj+GV7zfz/R3gSK2cOxGlazaXooxspCr539cbTXJKvBJzSVI2pPhcRGquoOtaIkKCsHQUiO3w==", "bin": { "parser": "bin/babel-parser.js" }, @@ -469,23 +469,23 @@ } }, "node_modules/@babel/standalone": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/standalone/-/standalone-7.15.3.tgz", - "integrity": "sha512-Bst2YWEyQ2ROyO0+jxPVnnkSmUh44/x54+LSbe5M4N5LGfOkxpajEUKVE4ndXtIVrLlHCyuiqCPwv3eC1ItnCg==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/standalone/-/standalone-7.15.4.tgz", + "integrity": "sha512-UO0QCTFjX5NSuwX/i8+/pesmRPoRTtf46Cpn8VHcXvNinEr2lxqe8Ix10TfU/UK5qsaOrcKk24We8wH1G0nTZA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", - "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", + "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", "dev": true, "dependencies": { "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.14.5", - "@babel/types": "^7.14.5" + "@babel/parser": "^7.15.4", + "@babel/types": "^7.15.4" }, "engines": { "node": ">=6.9.0" @@ -504,18 +504,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.0.tgz", - "integrity": "sha512-392d8BN0C9eVxVWd8H6x9WfipgVH5IaIoLp23334Sc1vbKKWINnvwRpb4us0xtPaCumlwbTtIYNA0Dv/32sVFw==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", + "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", "dev": true, "dependencies": { "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.0", - "@babel/helper-function-name": "^7.14.5", - "@babel/helper-hoist-variables": "^7.14.5", - "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/parser": "^7.15.0", - "@babel/types": "^7.15.0", + "@babel/generator": "^7.15.4", + "@babel/helper-function-name": "^7.15.4", + "@babel/helper-hoist-variables": "^7.15.4", + "@babel/helper-split-export-declaration": "^7.15.4", + "@babel/parser": "^7.15.4", + "@babel/types": "^7.15.4", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -545,9 +545,9 @@ } }, "node_modules/@babel/types": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.0.tgz", - "integrity": "sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.4.tgz", + "integrity": "sha512-0f1HJFuGmmbrKTCZtbm3cU+b/AqdEYk5toj5iQur58xkVMlS0JWaKxTBSmCXd47uiN7vbcozAupm6Mvs80GNhw==", "dependencies": { "@babel/helper-validator-identifier": "^7.14.9", "to-fast-properties": "^2.0.0" @@ -817,9 +817,9 @@ } }, "node_modules/@popperjs/core": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.9.3.tgz", - "integrity": "sha512-xDu17cEfh7Kid/d95kB6tZsLOmSWKCZKtprnhVepjsSaCij+lM3mItSJDuuHDMbCWTh8Ejmebwb+KONcCJ0eXQ==", + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.10.1.tgz", + "integrity": "sha512-HnUhk1Sy9IuKrxEMdIRCxpIqPw6BFsbYSEUO9p/hNw5sMld/+3OLMWQP80F8/db9qsv3qUjs7ZR5bS/R+iinXw==", "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -870,9 +870,9 @@ } }, "node_modules/@types/bootstrap": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.1.2.tgz", - "integrity": "sha512-dSQvMi2dMyNwJU6LZjP0pimuBowsMUvGScYdfqqeiDUoj9TxXZCpfu0cTl94U0Zvw/tdH9j/9ToOhi4LKNLZhg==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.1.4.tgz", + "integrity": "sha512-VAY+o6sCKrJ7Xix/lugdvQz0PpOn7Go+fQzCXOZvIdp7E/TDaiJddInVhNB/84bk9NX6uuKFSfl2pqslNYH9aA==", "dev": true, "dependencies": { "@popperjs/core": "^2.9.2", @@ -1013,9 +1013,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "16.7.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.8.tgz", - "integrity": "sha512-8upnoQU0OPzbIkm+ZMM0zCeFCkw2s3mS0IWdx0+AAaWqm4fkBb0UJp8Edl7FVKRamYbpJC/aVsHpKWBIbiC7Zg==" + "version": "16.7.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.10.tgz", + "integrity": "sha512-S63Dlv4zIPb8x6MMTgDq5WWRJQe56iBEY0O3SOFA9JrRienkOVDXSXBjjJw6HTNQYSE2JI6GMCR6LVbIMHJVvA==" }, "node_modules/@types/normalize-package-data": { "version": "2.4.1", @@ -1061,9 +1061,9 @@ "dev": true }, "node_modules/@vitejs/plugin-legacy": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-legacy/-/plugin-legacy-1.5.1.tgz", - "integrity": "sha512-g+0iy0X3NJRUSKZK+OCeSxNWnCuuE/6lsmr2WLWPOEt1vp6LdfHuNCYRooCm6s0ccTZ/SiumVk8vt9DWSYs+8A==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-legacy/-/plugin-legacy-1.5.2.tgz", + "integrity": "sha512-b1CaWY/wi7gQZnZaxH+ujPTPb91bEPgnnk7l0WIwxoQtW5UC5MQywRcAbFX+Ise62exXctOMBtsnXKJw2KajXw==", "dev": true, "dependencies": { "@babel/standalone": "^7.14.9", @@ -1092,40 +1092,40 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.6.tgz", - "integrity": "sha512-vbwnz7+OhtLO5p5i630fTuQCL+MlUpEMTKHuX+RfetQ+3pFCkItt2JUH+9yMaBG2Hkz6av+T9mwN/acvtIwpbw==", + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.8.tgz", + "integrity": "sha512-Sx8qJ030+QM/NakUrkQuUGCeDEb+0d0AgFOl5W4qRvR6e+YgLnW2ew0jREf4T1hak9Fdk8Edl67StECHrhEuew==", "dependencies": { "@babel/parser": "^7.15.0", "@babel/types": "^7.15.0", - "@vue/shared": "3.2.6", + "@vue/shared": "3.2.8", "estree-walker": "^2.0.2", "source-map": "^0.6.1" } }, "node_modules/@vue/compiler-dom": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.6.tgz", - "integrity": "sha512-+a/3oBAzFIXhHt8L5IHJOTP4a5egzvpXYyi13jR7CUYOR1S+Zzv7vBWKYBnKyJLwnrxTZnTQVjeHCgJq743XKg==", + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.8.tgz", + "integrity": "sha512-nxBW6k8FMWQ74294CRbqR+iEJRO5vIjx85I3YCOyZFD6FsDHyFL60g76TcJzucp+F2XXIDaYz+A+F4gQlDatjw==", "dependencies": { - "@vue/compiler-core": "3.2.6", - "@vue/shared": "3.2.6" + "@vue/compiler-core": "3.2.8", + "@vue/shared": "3.2.8" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.6.tgz", - "integrity": "sha512-Ariz1eDsf+2fw6oWXVwnBNtfKHav72RjlWXpEgozYBLnfRPzP+7jhJRw4Nq0OjSsLx2HqjF3QX7HutTjYB0/eA==", + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.8.tgz", + "integrity": "sha512-XClueQAXoWtN2EToKgfYH9FCL70Ac4bxx6OZFZzxYSg1bei8IB9srJP1UOfnJb2IpnM1heikAz1dp1HI1wHcyQ==", "dev": true, "dependencies": { "@babel/parser": "^7.15.0", "@babel/types": "^7.15.0", "@types/estree": "^0.0.48", - "@vue/compiler-core": "3.2.6", - "@vue/compiler-dom": "3.2.6", - "@vue/compiler-ssr": "3.2.6", - "@vue/ref-transform": "3.2.6", - "@vue/shared": "3.2.6", + "@vue/compiler-core": "3.2.8", + "@vue/compiler-dom": "3.2.8", + "@vue/compiler-ssr": "3.2.8", + "@vue/ref-transform": "3.2.8", + "@vue/shared": "3.2.8", "consolidate": "^0.16.0", "estree-walker": "^2.0.2", "hash-sum": "^2.0.0", @@ -1139,13 +1139,13 @@ } }, "node_modules/@vue/compiler-ssr": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.6.tgz", - "integrity": "sha512-A7IKRKHSyPnTC4w1FxHkjzoyjXInsXkcs/oX22nBQ+6AWlXj2Tt1le96CWPOXy5vYlsTYkF1IgfBaKIdeN/39g==", + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.8.tgz", + "integrity": "sha512-QqyiFRiIl55W0abDNQ6cNG/7iIfBHmbXVtssUAjX3IlI87ELeT0xackmrCyTSnfIX12ixljg9AN0COIZwlvt5A==", "dev": true, "dependencies": { - "@vue/compiler-dom": "3.2.6", - "@vue/shared": "3.2.6" + "@vue/compiler-dom": "3.2.8", + "@vue/shared": "3.2.8" } }, "node_modules/@vue/devtools-api": { @@ -1154,49 +1154,49 @@ "integrity": "sha512-quBx4Jjpexo6KDiNUGFr/zF/2A4srKM9S9v2uHgMXSU//hjgq1eGzqkIFql8T9gfX5ZaVOUzYBP3jIdIR3PKIA==" }, "node_modules/@vue/reactivity": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.6.tgz", - "integrity": "sha512-8vIDD2wpCnYisNNZjmcIj+Rixn0uhZNY3G1vzlgdVdLygeRSuFjkmnZk6WwvGzUWpKfnG0e/NUySM3mVi59hAA==", + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.8.tgz", + "integrity": "sha512-/Hj3Uz28SG+xB5SDWPOXUs0emvHkq82EmTgk44/plTVFeswCZ3i3Hd7WmsrPT4rGajlDKd5uqMmW0ith1ED0FA==", "dependencies": { - "@vue/shared": "3.2.6" + "@vue/shared": "3.2.8" } }, "node_modules/@vue/ref-transform": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@vue/ref-transform/-/ref-transform-3.2.6.tgz", - "integrity": "sha512-ie39+Y4nbirDLvH+WEq6Eo/l3n3mFATayqR+kEMSphrtMW6Uh/eEMx1Gk2Jnf82zmj3VLRq7dnmPx72JLcBYkQ==", + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@vue/ref-transform/-/ref-transform-3.2.8.tgz", + "integrity": "sha512-9LdADd4JM3klt+b2qNT8a7b7JvBETNBy2Btv5rDzyPrAVS4Vrw+1WWay6gZBgnxfJ9TPSvG8f/9zu6gNGHmJLA==", "dev": true, "dependencies": { "@babel/parser": "^7.15.0", - "@vue/compiler-core": "3.2.6", - "@vue/shared": "3.2.6", + "@vue/compiler-core": "3.2.8", + "@vue/shared": "3.2.8", "estree-walker": "^2.0.2", "magic-string": "^0.25.7" } }, "node_modules/@vue/runtime-core": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.6.tgz", - "integrity": "sha512-3mqtgpj/YSGFxtvTufSERRApo92B16JNNxz9p+5eG6PPuqTmuRJz214MqhKBEgLEAIQ6R6YCbd83ZDtjQnyw2g==", + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.8.tgz", + "integrity": "sha512-hwzXLGw1njBEY5JSyRXIIdCtzMFFF6F38WcKMmoIE3p7da30jEbWt8EwwrBomjT8ZbqzElOGlewBcnXNOiiIUg==", "dependencies": { - "@vue/reactivity": "3.2.6", - "@vue/shared": "3.2.6" + "@vue/reactivity": "3.2.8", + "@vue/shared": "3.2.8" } }, "node_modules/@vue/runtime-dom": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.6.tgz", - "integrity": "sha512-fq33urnP0BNCGm2O3KCzkJlKIHI80C94HJ4qDZbjsTtxyOn5IHqwKSqXVN3RQvO6epcQH+sWS+JNwcNDPzoasg==", + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.8.tgz", + "integrity": "sha512-A/aRrlGLJ5y4Z7eNbnO/xHwx2RiPijQo7D3OIwESroG3HNP+dpuoqamajo5TXS9ZGjbMOih82COoe7xb9P4BZw==", "dependencies": { - "@vue/runtime-core": "3.2.6", - "@vue/shared": "3.2.6", + "@vue/runtime-core": "3.2.8", + "@vue/shared": "3.2.8", "csstype": "^2.6.8" } }, "node_modules/@vue/shared": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.6.tgz", - "integrity": "sha512-uwX0Qs2e6kdF+WmxwuxJxOnKs/wEkMArtYpHSm7W+VY/23Tl8syMRyjnzEeXrNCAP0/8HZxEGkHJsjPEDNRuHw==" + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.8.tgz", + "integrity": "sha512-E2DQQnG7Qr4GwTs3GlfPPlHliGVADoufTnhpwfoViw7JlyLMmYtjfnTwM6nXAwvSJWiF7D+7AxpnWBBT3VWo6Q==" }, "node_modules/abbrev": { "version": "1.1.1", @@ -1315,9 +1315,9 @@ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, "node_modules/are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", + "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", "dependencies": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" @@ -2022,9 +2022,9 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, "node_modules/core-js": { - "version": "3.16.4", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.16.4.tgz", - "integrity": "sha512-Tq4GVE6XCjE+hcyW6hPy0ofN3hwtLudz5ZRdrlCnsnD/xkm/PWQRudzYHiKgZKUcefV6Q57fhDHjZHJP5dpfSg==", + "version": "3.17.2", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.17.2.tgz", + "integrity": "sha512-XkbXqhcXeMHPRk2ItS+zQYliAMilea2euoMsnpRRdDad6b2VY6CQQcwz1K8AnWesfw4p165RzY0bTnr3UrbYiA==", "dev": true, "hasInstallScript": true, "funding": { @@ -2033,9 +2033,9 @@ } }, "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, "node_modules/cors": { "version": "2.8.5", @@ -2313,9 +2313,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "node_modules/electron-to-chromium": { - "version": "1.3.824", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.824.tgz", - "integrity": "sha512-Fk+5aD0HDi9i9ZKt9n2VPOZO1dQy7PV++hz2wJ/KIn+CvVfu4fny39squHtyVDPuHNuoJGAZIbuReEklqYIqfA==", + "version": "1.3.830", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.830.tgz", + "integrity": "sha512-gBN7wNAxV5vl1430dG+XRcQhD4pIeYeak6p6rjdCtlz5wWNwDad8jwvphe5oi1chL5MV6RNRikfffBBiFuj+rQ==", "dev": true }, "node_modules/emoji-regex": { @@ -2431,9 +2431,9 @@ } }, "node_modules/esbuild": { - "version": "0.12.24", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.24.tgz", - "integrity": "sha512-C0ibY+HsXzYB6L/pLWEiWjMpghKsIc58Q5yumARwBQsHl9DXPakW+5NI/Y9w4YXiz0PEP6XTGTT/OV4Nnsmb4A==", + "version": "0.12.25", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.25.tgz", + "integrity": "sha512-woie0PosbRSoN8gQytrdCzUbS2ByKgO8nD1xCZkEup3D9q92miCze4PqEI9TZDYAuwn6CruEnQpJxgTRWdooAg==", "dev": true, "hasInstallScript": true, "bin": { @@ -2960,9 +2960,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.2.tgz", - "integrity": "sha512-yLR6WaE2lbF0x4K2qE2p9PEXKLDjUjnR/xmjS3wHAYxtlsI9MLLBJUZirAHKzUZDGLxje7w/cXR49WOUo4rbsA==", + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.3.tgz", + "integrity": "sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw==", "funding": [ { "type": "individual", @@ -3922,9 +3922,9 @@ } }, "node_modules/knex": { - "version": "0.95.10", - "resolved": "https://registry.npmjs.org/knex/-/knex-0.95.10.tgz", - "integrity": "sha512-I60A8TXcMdeJlE6h7DSgEYyY37S7kgLObz1qlJ7QvPMD6vnKO5dtuLEht5pMia9Qf5BomqVgkWCdVTqcC/ImOA==", + "version": "0.95.11", + "resolved": "https://registry.npmjs.org/knex/-/knex-0.95.11.tgz", + "integrity": "sha512-grDetD91O8VoQVCFqeWTgkzdq5406W6rggF/lK1hHuwzmjDs/0m9KxyncGdZbklTi7aUgHvw3+Cfy4x7FvpdaQ==", "dependencies": { "colorette": "1.2.1", "commander": "^7.1.0", @@ -5789,9 +5789,9 @@ } }, "node_modules/redbean-node/node_modules/@types/node": { - "version": "14.17.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.12.tgz", - "integrity": "sha512-vhUqgjJR1qxwTWV5Ps5txuy2XMdf7Fw+OrdChRboy8BmWUPkckOhphaohzFG6b8DW7CrxaBMdrdJ47SYFq1okw==" + "version": "14.17.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.14.tgz", + "integrity": "sha512-rsAj2u8Xkqfc332iXV12SqIsjVi07H479bOP4q94NAcjzmAvapumEhuVIt53koEf7JFrpjgNKjBga5Pnn/GL8A==" }, "node_modules/redent": { "version": "3.0.0", @@ -6032,9 +6032,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sass": { - "version": "1.38.2", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.38.2.tgz", - "integrity": "sha512-Bz1fG6qiyF0FX6m/I+VxtdVKz1Dfmg/e9kfDy2PhWOkq3T384q2KxwIfP0fXpeI+EyyETdOauH+cRHQDFASllA==", + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.39.0.tgz", + "integrity": "sha512-F4o+RhJkNOIG0b6QudYU8c78ZADKZjKDk5cyrf8XTKWfrgbtyVVXImFstJrc+1pkQDCggyidIOytq6gS4gCCZg==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0" @@ -7220,6 +7220,12 @@ "extsprintf": "^1.2.0" } }, + "node_modules/verror/node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "optional": true + }, "node_modules/vfile": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.1.tgz", @@ -7251,9 +7257,9 @@ } }, "node_modules/vite": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-2.5.2.tgz", - "integrity": "sha512-JK5uhiVyMqHiAJbgBa8rCvpP8bEhAE9dKDv1gCmP+EUP2FSPmEeW3WXlCXauPB3MDa8behPW+ntyNXqnGaxslg==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-2.5.3.tgz", + "integrity": "sha512-1wMDnjflvtTTkMov8O/Xb5+w1/VW/Gw8oCf8f6dqgHn8lMOEqq0SaPtFEQeikFcOKCfSbiU0nEi0LDIx6DNsaQ==", "dev": true, "dependencies": { "esbuild": "^0.12.17", @@ -7272,19 +7278,19 @@ } }, "node_modules/vue": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.6.tgz", - "integrity": "sha512-Zlb3LMemQS3Xxa6xPsecu45bNjr1hxO8Bh5FUmE0Dr6Ot0znZBKiM47rK6O7FTcakxOnvVN+NTXWJF6u8ajpCQ==", + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.8.tgz", + "integrity": "sha512-x7lwdnOSkceHQUXRVVHBaZzcp6v7M2CYtSZH75zZaT1mTjB4plC4KZHKP/5jAvdqOLBHZGwDSMkWXm3YbAufrA==", "dependencies": { - "@vue/compiler-dom": "3.2.6", - "@vue/runtime-dom": "3.2.6", - "@vue/shared": "3.2.6" + "@vue/compiler-dom": "3.2.8", + "@vue/runtime-dom": "3.2.8", + "@vue/shared": "3.2.8" } }, "node_modules/vue-chart-3": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/vue-chart-3/-/vue-chart-3-0.5.7.tgz", - "integrity": "sha512-BccfPv2rodY6IOppYHvMluVmIJE1CHfp5uW2DXrHrm1kIzaafLwpQ5SwdrxuCevn/QhKoi7azzcxwRcoWbX9hg==", + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/vue-chart-3/-/vue-chart-3-0.5.8.tgz", + "integrity": "sha512-VJEBTdMgWOaYqekXtz4LVBIeYyIx3qDlQnFyY4Ao1GwizokYZBycCeRN3oKDcYbbZi5yxYqTy6+Tm+m+SOPUPA==", "dependencies": { "@vue/runtime-core": "latest", "@vue/runtime-dom": "latest", @@ -7560,20 +7566,20 @@ "dev": true }, "@babel/core": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.15.0.tgz", - "integrity": "sha512-tXtmTminrze5HEUPn/a0JtOzzfp0nk+UEXQ/tqIJo3WDGypl/2OFQEMll/zSFU8f/lfmfLXvTaORHF3cfXIQMw==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.15.4.tgz", + "integrity": "sha512-Lkcv9I4a8bgUI8LJOLM6IKv6hnz1KOju6KM1lceqVMKlKKqNRopYd2Pc9MgIurqvMJ6BooemrnJz8jlIiQIpsA==", "dev": true, "requires": { "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.0", - "@babel/helper-compilation-targets": "^7.15.0", - "@babel/helper-module-transforms": "^7.15.0", - "@babel/helpers": "^7.14.8", - "@babel/parser": "^7.15.0", - "@babel/template": "^7.14.5", - "@babel/traverse": "^7.15.0", - "@babel/types": "^7.15.0", + "@babel/generator": "^7.15.4", + "@babel/helper-compilation-targets": "^7.15.4", + "@babel/helper-module-transforms": "^7.15.4", + "@babel/helpers": "^7.15.4", + "@babel/parser": "^7.15.4", + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -7609,9 +7615,9 @@ } }, "@babel/eslint-parser": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.15.0.tgz", - "integrity": "sha512-+gSPtjSBxOZz4Uh8Ggqu7HbfpB8cT1LwW0DnVVLZEJvzXauiD0Di3zszcBkRmfGGrLdYeHUwcflG7i3tr9kQlw==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.15.4.tgz", + "integrity": "sha512-hPMIAmGNbmQzXJIo2P43Zj9UhRmGev5f9nqdBFOWNGDGh6XKmjby79woBvg6y0Jur6yRfQBneDbUQ8ZVc1krFw==", "dev": true, "requires": { "eslint-scope": "^5.1.1", @@ -7620,12 +7626,12 @@ } }, "@babel/generator": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.0.tgz", - "integrity": "sha512-eKl4XdMrbpYvuB505KTta4AV9g+wWzmVBW69tX0H2NwKVKd2YJbKgyK6M8j/rgLbmHOYJn6rUklV677nOyJrEQ==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.4.tgz", + "integrity": "sha512-d3itta0tu+UayjEORPNz6e1T3FtvWlP5N4V5M+lhp/CxT4oAA7/NcScnpRyspUMLK6tu9MNHmQHxRykuN2R7hw==", "dev": true, "requires": { - "@babel/types": "^7.15.0", + "@babel/types": "^7.15.4", "jsesc": "^2.5.1", "source-map": "^0.5.0" }, @@ -7639,9 +7645,9 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.0.tgz", - "integrity": "sha512-h+/9t0ncd4jfZ8wsdAsoIxSa61qhBYlycXiHWqJaQBCXAhDCMbPRSMTGnZIkkmt1u4ag+UQmuqcILwqKzZ4N2A==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz", + "integrity": "sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ==", "dev": true, "requires": { "@babel/compat-data": "^7.15.0", @@ -7651,105 +7657,105 @@ } }, "@babel/helper-function-name": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", - "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", + "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.14.5", - "@babel/template": "^7.14.5", - "@babel/types": "^7.14.5" + "@babel/helper-get-function-arity": "^7.15.4", + "@babel/template": "^7.15.4", + "@babel/types": "^7.15.4" } }, "@babel/helper-get-function-arity": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", - "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", + "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", "dev": true, "requires": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.15.4" } }, "@babel/helper-hoist-variables": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz", - "integrity": "sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz", + "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", "dev": true, "requires": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.15.4" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.0.tgz", - "integrity": "sha512-Jq8H8U2kYiafuj2xMTPQwkTBnEEdGKpT35lJEQsRRjnG0LW3neucsaMWLgKcwu3OHKNeYugfw+Z20BXBSEs2Lg==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz", + "integrity": "sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA==", "dev": true, "requires": { - "@babel/types": "^7.15.0" + "@babel/types": "^7.15.4" } }, "@babel/helper-module-imports": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz", - "integrity": "sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz", + "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", "dev": true, "requires": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.15.4" } }, "@babel/helper-module-transforms": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.0.tgz", - "integrity": "sha512-RkGiW5Rer7fpXv9m1B3iHIFDZdItnO2/BLfWVW/9q7+KqQSDY5kUfQEbzdXM1MVhJGcugKV7kRrNVzNxmk7NBg==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.4.tgz", + "integrity": "sha512-9fHHSGE9zTC++KuXLZcB5FKgvlV83Ox+NLUmQTawovwlJ85+QMhk1CnVk406CQVj97LaWod6KVjl2Sfgw9Aktw==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.14.5", - "@babel/helper-replace-supers": "^7.15.0", - "@babel/helper-simple-access": "^7.14.8", - "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/helper-module-imports": "^7.15.4", + "@babel/helper-replace-supers": "^7.15.4", + "@babel/helper-simple-access": "^7.15.4", + "@babel/helper-split-export-declaration": "^7.15.4", "@babel/helper-validator-identifier": "^7.14.9", - "@babel/template": "^7.14.5", - "@babel/traverse": "^7.15.0", - "@babel/types": "^7.15.0" + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4" } }, "@babel/helper-optimise-call-expression": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz", - "integrity": "sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz", + "integrity": "sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw==", "dev": true, "requires": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.15.4" } }, "@babel/helper-replace-supers": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.0.tgz", - "integrity": "sha512-6O+eWrhx+HEra/uJnifCwhwMd6Bp5+ZfZeJwbqUTuqkhIT6YcRhiZCOOFChRypOIe0cV46kFrRBlm+t5vHCEaA==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz", + "integrity": "sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.15.0", - "@babel/helper-optimise-call-expression": "^7.14.5", - "@babel/traverse": "^7.15.0", - "@babel/types": "^7.15.0" + "@babel/helper-member-expression-to-functions": "^7.15.4", + "@babel/helper-optimise-call-expression": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4" } }, "@babel/helper-simple-access": { - "version": "7.14.8", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.14.8.tgz", - "integrity": "sha512-TrFN4RHh9gnWEU+s7JloIho2T76GPwRHhdzOWLqTrMnlas8T9O7ec+oEDNsRXndOmru9ymH9DFrEOxpzPoSbdg==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz", + "integrity": "sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg==", "dev": true, "requires": { - "@babel/types": "^7.14.8" + "@babel/types": "^7.15.4" } }, "@babel/helper-split-export-declaration": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", - "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", + "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", "dev": true, "requires": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.15.4" } }, "@babel/helper-validator-identifier": { @@ -7764,14 +7770,14 @@ "dev": true }, "@babel/helpers": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.3.tgz", - "integrity": "sha512-HwJiz52XaS96lX+28Tnbu31VeFSQJGOeKHJeaEPQlTl7PnlhFElWPj8tUXtqFIzeN86XxXoBr+WFAyK2PPVz6g==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.4.tgz", + "integrity": "sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ==", "dev": true, "requires": { - "@babel/template": "^7.14.5", - "@babel/traverse": "^7.15.0", - "@babel/types": "^7.15.0" + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4" } }, "@babel/highlight": { @@ -7844,25 +7850,25 @@ } }, "@babel/parser": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.3.tgz", - "integrity": "sha512-O0L6v/HvqbdJawj0iBEfVQMc3/6WP+AeOsovsIgBFyJaG+W2w7eqvZB7puddATmWuARlm1SX7DwxJ/JJUnDpEA==" + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.4.tgz", + "integrity": "sha512-xmzz+7fRpjrvDUj+GV7zfz/R3gSK2cOxGlazaXooxspCr539cbTXJKvBJzSVI2pPhcRGquoOtaIkKCsHQUiO3w==" }, "@babel/standalone": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/standalone/-/standalone-7.15.3.tgz", - "integrity": "sha512-Bst2YWEyQ2ROyO0+jxPVnnkSmUh44/x54+LSbe5M4N5LGfOkxpajEUKVE4ndXtIVrLlHCyuiqCPwv3eC1ItnCg==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/standalone/-/standalone-7.15.4.tgz", + "integrity": "sha512-UO0QCTFjX5NSuwX/i8+/pesmRPoRTtf46Cpn8VHcXvNinEr2lxqe8Ix10TfU/UK5qsaOrcKk24We8wH1G0nTZA==", "dev": true }, "@babel/template": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", - "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", + "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", "dev": true, "requires": { "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.14.5", - "@babel/types": "^7.14.5" + "@babel/parser": "^7.15.4", + "@babel/types": "^7.15.4" }, "dependencies": { "@babel/code-frame": { @@ -7877,18 +7883,18 @@ } }, "@babel/traverse": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.0.tgz", - "integrity": "sha512-392d8BN0C9eVxVWd8H6x9WfipgVH5IaIoLp23334Sc1vbKKWINnvwRpb4us0xtPaCumlwbTtIYNA0Dv/32sVFw==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", + "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", "dev": true, "requires": { "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.0", - "@babel/helper-function-name": "^7.14.5", - "@babel/helper-hoist-variables": "^7.14.5", - "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/parser": "^7.15.0", - "@babel/types": "^7.15.0", + "@babel/generator": "^7.15.4", + "@babel/helper-function-name": "^7.15.4", + "@babel/helper-hoist-variables": "^7.15.4", + "@babel/helper-split-export-declaration": "^7.15.4", + "@babel/parser": "^7.15.4", + "@babel/types": "^7.15.4", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -7911,9 +7917,9 @@ } }, "@babel/types": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.0.tgz", - "integrity": "sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.4.tgz", + "integrity": "sha512-0f1HJFuGmmbrKTCZtbm3cU+b/AqdEYk5toj5iQur58xkVMlS0JWaKxTBSmCXd47uiN7vbcozAupm6Mvs80GNhw==", "requires": { "@babel/helper-validator-identifier": "^7.14.9", "to-fast-properties": "^2.0.0" @@ -7968,8 +7974,7 @@ "@fortawesome/vue-fontawesome": { "version": "3.0.0-4", "resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-3.0.0-4.tgz", - "integrity": "sha512-dQVhhMRcUPCb0aqk5ohm0KGk5OJ7wFZ9aYapLzJB3Z+xs7LhkRWLTb87reelUAG5PFDjutDAXuloT9hi6cz72A==", - "requires": {} + "integrity": "sha512-dQVhhMRcUPCb0aqk5ohm0KGk5OJ7wFZ9aYapLzJB3Z+xs7LhkRWLTb87reelUAG5PFDjutDAXuloT9hi6cz72A==" }, "@humanwhocodes/config-array": { "version": "0.5.0", @@ -8115,9 +8120,9 @@ } }, "@popperjs/core": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.9.3.tgz", - "integrity": "sha512-xDu17cEfh7Kid/d95kB6tZsLOmSWKCZKtprnhVepjsSaCij+lM3mItSJDuuHDMbCWTh8Ejmebwb+KONcCJ0eXQ==" + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.10.1.tgz", + "integrity": "sha512-HnUhk1Sy9IuKrxEMdIRCxpIqPw6BFsbYSEUO9p/hNw5sMld/+3OLMWQP80F8/db9qsv3qUjs7ZR5bS/R+iinXw==" }, "@stylelint/postcss-css-in-js": { "version": "0.37.2", @@ -8156,9 +8161,9 @@ } }, "@types/bootstrap": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.1.2.tgz", - "integrity": "sha512-dSQvMi2dMyNwJU6LZjP0pimuBowsMUvGScYdfqqeiDUoj9TxXZCpfu0cTl94U0Zvw/tdH9j/9ToOhi4LKNLZhg==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.1.4.tgz", + "integrity": "sha512-VAY+o6sCKrJ7Xix/lugdvQz0PpOn7Go+fQzCXOZvIdp7E/TDaiJddInVhNB/84bk9NX6uuKFSfl2pqslNYH9aA==", "dev": true, "requires": { "@popperjs/core": "^2.9.2", @@ -8299,9 +8304,9 @@ "dev": true }, "@types/node": { - "version": "16.7.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.8.tgz", - "integrity": "sha512-8upnoQU0OPzbIkm+ZMM0zCeFCkw2s3mS0IWdx0+AAaWqm4fkBb0UJp8Edl7FVKRamYbpJC/aVsHpKWBIbiC7Zg==" + "version": "16.7.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.10.tgz", + "integrity": "sha512-S63Dlv4zIPb8x6MMTgDq5WWRJQe56iBEY0O3SOFA9JrRienkOVDXSXBjjJw6HTNQYSE2JI6GMCR6LVbIMHJVvA==" }, "@types/normalize-package-data": { "version": "2.4.1", @@ -8347,9 +8352,9 @@ "dev": true }, "@vitejs/plugin-legacy": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-legacy/-/plugin-legacy-1.5.1.tgz", - "integrity": "sha512-g+0iy0X3NJRUSKZK+OCeSxNWnCuuE/6lsmr2WLWPOEt1vp6LdfHuNCYRooCm6s0ccTZ/SiumVk8vt9DWSYs+8A==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-legacy/-/plugin-legacy-1.5.2.tgz", + "integrity": "sha512-b1CaWY/wi7gQZnZaxH+ujPTPb91bEPgnnk7l0WIwxoQtW5UC5MQywRcAbFX+Ise62exXctOMBtsnXKJw2KajXw==", "dev": true, "requires": { "@babel/standalone": "^7.14.9", @@ -8363,44 +8368,43 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.6.0.tgz", "integrity": "sha512-n3i8htn8pTg9M+kM3cnEfsPZx/6ngInlTroth6fA1LQTJq5aTVQ8ggaE5pPoAy9vCgHPtcaXMzwpldhqRAkebQ==", - "dev": true, - "requires": {} + "dev": true }, "@vue/compiler-core": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.6.tgz", - "integrity": "sha512-vbwnz7+OhtLO5p5i630fTuQCL+MlUpEMTKHuX+RfetQ+3pFCkItt2JUH+9yMaBG2Hkz6av+T9mwN/acvtIwpbw==", + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.8.tgz", + "integrity": "sha512-Sx8qJ030+QM/NakUrkQuUGCeDEb+0d0AgFOl5W4qRvR6e+YgLnW2ew0jREf4T1hak9Fdk8Edl67StECHrhEuew==", "requires": { "@babel/parser": "^7.15.0", "@babel/types": "^7.15.0", - "@vue/shared": "3.2.6", + "@vue/shared": "3.2.8", "estree-walker": "^2.0.2", "source-map": "^0.6.1" } }, "@vue/compiler-dom": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.6.tgz", - "integrity": "sha512-+a/3oBAzFIXhHt8L5IHJOTP4a5egzvpXYyi13jR7CUYOR1S+Zzv7vBWKYBnKyJLwnrxTZnTQVjeHCgJq743XKg==", + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.8.tgz", + "integrity": "sha512-nxBW6k8FMWQ74294CRbqR+iEJRO5vIjx85I3YCOyZFD6FsDHyFL60g76TcJzucp+F2XXIDaYz+A+F4gQlDatjw==", "requires": { - "@vue/compiler-core": "3.2.6", - "@vue/shared": "3.2.6" + "@vue/compiler-core": "3.2.8", + "@vue/shared": "3.2.8" } }, "@vue/compiler-sfc": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.6.tgz", - "integrity": "sha512-Ariz1eDsf+2fw6oWXVwnBNtfKHav72RjlWXpEgozYBLnfRPzP+7jhJRw4Nq0OjSsLx2HqjF3QX7HutTjYB0/eA==", + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.8.tgz", + "integrity": "sha512-XClueQAXoWtN2EToKgfYH9FCL70Ac4bxx6OZFZzxYSg1bei8IB9srJP1UOfnJb2IpnM1heikAz1dp1HI1wHcyQ==", "dev": true, "requires": { "@babel/parser": "^7.15.0", "@babel/types": "^7.15.0", "@types/estree": "^0.0.48", - "@vue/compiler-core": "3.2.6", - "@vue/compiler-dom": "3.2.6", - "@vue/compiler-ssr": "3.2.6", - "@vue/ref-transform": "3.2.6", - "@vue/shared": "3.2.6", + "@vue/compiler-core": "3.2.8", + "@vue/compiler-dom": "3.2.8", + "@vue/compiler-ssr": "3.2.8", + "@vue/ref-transform": "3.2.8", + "@vue/shared": "3.2.8", "consolidate": "^0.16.0", "estree-walker": "^2.0.2", "hash-sum": "^2.0.0", @@ -8414,13 +8418,13 @@ } }, "@vue/compiler-ssr": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.6.tgz", - "integrity": "sha512-A7IKRKHSyPnTC4w1FxHkjzoyjXInsXkcs/oX22nBQ+6AWlXj2Tt1le96CWPOXy5vYlsTYkF1IgfBaKIdeN/39g==", + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.8.tgz", + "integrity": "sha512-QqyiFRiIl55W0abDNQ6cNG/7iIfBHmbXVtssUAjX3IlI87ELeT0xackmrCyTSnfIX12ixljg9AN0COIZwlvt5A==", "dev": true, "requires": { - "@vue/compiler-dom": "3.2.6", - "@vue/shared": "3.2.6" + "@vue/compiler-dom": "3.2.8", + "@vue/shared": "3.2.8" } }, "@vue/devtools-api": { @@ -8429,49 +8433,49 @@ "integrity": "sha512-quBx4Jjpexo6KDiNUGFr/zF/2A4srKM9S9v2uHgMXSU//hjgq1eGzqkIFql8T9gfX5ZaVOUzYBP3jIdIR3PKIA==" }, "@vue/reactivity": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.6.tgz", - "integrity": "sha512-8vIDD2wpCnYisNNZjmcIj+Rixn0uhZNY3G1vzlgdVdLygeRSuFjkmnZk6WwvGzUWpKfnG0e/NUySM3mVi59hAA==", + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.8.tgz", + "integrity": "sha512-/Hj3Uz28SG+xB5SDWPOXUs0emvHkq82EmTgk44/plTVFeswCZ3i3Hd7WmsrPT4rGajlDKd5uqMmW0ith1ED0FA==", "requires": { - "@vue/shared": "3.2.6" + "@vue/shared": "3.2.8" } }, "@vue/ref-transform": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@vue/ref-transform/-/ref-transform-3.2.6.tgz", - "integrity": "sha512-ie39+Y4nbirDLvH+WEq6Eo/l3n3mFATayqR+kEMSphrtMW6Uh/eEMx1Gk2Jnf82zmj3VLRq7dnmPx72JLcBYkQ==", + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@vue/ref-transform/-/ref-transform-3.2.8.tgz", + "integrity": "sha512-9LdADd4JM3klt+b2qNT8a7b7JvBETNBy2Btv5rDzyPrAVS4Vrw+1WWay6gZBgnxfJ9TPSvG8f/9zu6gNGHmJLA==", "dev": true, "requires": { "@babel/parser": "^7.15.0", - "@vue/compiler-core": "3.2.6", - "@vue/shared": "3.2.6", + "@vue/compiler-core": "3.2.8", + "@vue/shared": "3.2.8", "estree-walker": "^2.0.2", "magic-string": "^0.25.7" } }, "@vue/runtime-core": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.6.tgz", - "integrity": "sha512-3mqtgpj/YSGFxtvTufSERRApo92B16JNNxz9p+5eG6PPuqTmuRJz214MqhKBEgLEAIQ6R6YCbd83ZDtjQnyw2g==", + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.8.tgz", + "integrity": "sha512-hwzXLGw1njBEY5JSyRXIIdCtzMFFF6F38WcKMmoIE3p7da30jEbWt8EwwrBomjT8ZbqzElOGlewBcnXNOiiIUg==", "requires": { - "@vue/reactivity": "3.2.6", - "@vue/shared": "3.2.6" + "@vue/reactivity": "3.2.8", + "@vue/shared": "3.2.8" } }, "@vue/runtime-dom": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.6.tgz", - "integrity": "sha512-fq33urnP0BNCGm2O3KCzkJlKIHI80C94HJ4qDZbjsTtxyOn5IHqwKSqXVN3RQvO6epcQH+sWS+JNwcNDPzoasg==", + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.8.tgz", + "integrity": "sha512-A/aRrlGLJ5y4Z7eNbnO/xHwx2RiPijQo7D3OIwESroG3HNP+dpuoqamajo5TXS9ZGjbMOih82COoe7xb9P4BZw==", "requires": { - "@vue/runtime-core": "3.2.6", - "@vue/shared": "3.2.6", + "@vue/runtime-core": "3.2.8", + "@vue/shared": "3.2.8", "csstype": "^2.6.8" } }, "@vue/shared": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.6.tgz", - "integrity": "sha512-uwX0Qs2e6kdF+WmxwuxJxOnKs/wEkMArtYpHSm7W+VY/23Tl8syMRyjnzEeXrNCAP0/8HZxEGkHJsjPEDNRuHw==" + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.8.tgz", + "integrity": "sha512-E2DQQnG7Qr4GwTs3GlfPPlHliGVADoufTnhpwfoViw7JlyLMmYtjfnTwM6nXAwvSJWiF7D+7AxpnWBBT3VWo6Q==" }, "abbrev": { "version": "1.1.1", @@ -8497,8 +8501,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} + "dev": true }, "agent-base": { "version": "6.0.2", @@ -8557,9 +8560,9 @@ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", + "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", "requires": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" @@ -8853,8 +8856,7 @@ "bootstrap": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.0.tgz", - "integrity": "sha512-bs74WNI9BgBo3cEovmdMHikSKoXnDgA6VQjJ7TyTotU6L7d41ZyCEEelPwkYEzsG/Zjv3ie9IE3EMAje0W9Xew==", - "requires": {} + "integrity": "sha512-bs74WNI9BgBo3cEovmdMHikSKoXnDgA6VQjJ7TyTotU6L7d41ZyCEEelPwkYEzsG/Zjv3ie9IE3EMAje0W9Xew==" }, "brace-expansion": { "version": "1.1.11", @@ -8968,8 +8970,7 @@ "chartjs-adapter-dayjs": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/chartjs-adapter-dayjs/-/chartjs-adapter-dayjs-1.0.0.tgz", - "integrity": "sha512-EnbVqTJGFKLpg1TROLdCEufrzbmIa2oeLGx8O2Wdjw2EoMudoOo9+YFu+6CM0Z0hQ/v3yq/e/Y6efQMu22n8Jg==", - "requires": {} + "integrity": "sha512-EnbVqTJGFKLpg1TROLdCEufrzbmIa2oeLGx8O2Wdjw2EoMudoOo9+YFu+6CM0Z0hQ/v3yq/e/Y6efQMu22n8Jg==" }, "chokidar": { "version": "3.5.2", @@ -9107,15 +9108,15 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, "core-js": { - "version": "3.16.4", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.16.4.tgz", - "integrity": "sha512-Tq4GVE6XCjE+hcyW6hPy0ofN3hwtLudz5ZRdrlCnsnD/xkm/PWQRudzYHiKgZKUcefV6Q57fhDHjZHJP5dpfSg==", + "version": "3.17.2", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.17.2.tgz", + "integrity": "sha512-XkbXqhcXeMHPRk2ItS+zQYliAMilea2euoMsnpRRdDad6b2VY6CQQcwz1K8AnWesfw4p165RzY0bTnr3UrbYiA==", "dev": true }, "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, "cors": { "version": "2.8.5", @@ -9335,9 +9336,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "electron-to-chromium": { - "version": "1.3.824", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.824.tgz", - "integrity": "sha512-Fk+5aD0HDi9i9ZKt9n2VPOZO1dQy7PV++hz2wJ/KIn+CvVfu4fny39squHtyVDPuHNuoJGAZIbuReEklqYIqfA==", + "version": "1.3.830", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.830.tgz", + "integrity": "sha512-gBN7wNAxV5vl1430dG+XRcQhD4pIeYeak6p6rjdCtlz5wWNwDad8jwvphe5oi1chL5MV6RNRikfffBBiFuj+rQ==", "dev": true }, "emoji-regex": { @@ -9434,9 +9435,9 @@ } }, "esbuild": { - "version": "0.12.24", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.24.tgz", - "integrity": "sha512-C0ibY+HsXzYB6L/pLWEiWjMpghKsIc58Q5yumARwBQsHl9DXPakW+5NI/Y9w4YXiz0PEP6XTGTT/OV4Nnsmb4A==", + "version": "0.12.25", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.25.tgz", + "integrity": "sha512-woie0PosbRSoN8gQytrdCzUbS2ByKgO8nD1xCZkEup3D9q92miCze4PqEI9TZDYAuwn6CruEnQpJxgTRWdooAg==", "dev": true }, "escalade": { @@ -9861,9 +9862,9 @@ "dev": true }, "follow-redirects": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.2.tgz", - "integrity": "sha512-yLR6WaE2lbF0x4K2qE2p9PEXKLDjUjnR/xmjS3wHAYxtlsI9MLLBJUZirAHKzUZDGLxje7w/cXR49WOUo4rbsA==" + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.3.tgz", + "integrity": "sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw==" }, "forever-agent": { "version": "0.6.1", @@ -10261,8 +10262,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true, - "requires": {} + "dev": true }, "ignore": { "version": "4.0.6", @@ -10584,9 +10584,9 @@ "dev": true }, "knex": { - "version": "0.95.10", - "resolved": "https://registry.npmjs.org/knex/-/knex-0.95.10.tgz", - "integrity": "sha512-I60A8TXcMdeJlE6h7DSgEYyY37S7kgLObz1qlJ7QvPMD6vnKO5dtuLEht5pMia9Qf5BomqVgkWCdVTqcC/ImOA==", + "version": "0.95.11", + "resolved": "https://registry.npmjs.org/knex/-/knex-0.95.11.tgz", + "integrity": "sha512-grDetD91O8VoQVCFqeWTgkzdq5406W6rggF/lK1hHuwzmjDs/0m9KxyncGdZbklTi7aUgHvw3+Cfy4x7FvpdaQ==", "requires": { "colorette": "1.2.1", "commander": "^7.1.0", @@ -11463,8 +11463,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", - "dev": true, - "requires": {} + "dev": true }, "postcss-modules-local-by-default": { "version": "4.0.0", @@ -11783,8 +11782,7 @@ "version": "0.36.2", "resolved": "https://registry.npmjs.org/postcss-syntax/-/postcss-syntax-0.36.2.tgz", "integrity": "sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w==", - "dev": true, - "requires": {} + "dev": true }, "postcss-value-parser": { "version": "4.1.0", @@ -12002,9 +12000,9 @@ }, "dependencies": { "@types/node": { - "version": "14.17.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.12.tgz", - "integrity": "sha512-vhUqgjJR1qxwTWV5Ps5txuy2XMdf7Fw+OrdChRboy8BmWUPkckOhphaohzFG6b8DW7CrxaBMdrdJ47SYFq1okw==" + "version": "14.17.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.14.tgz", + "integrity": "sha512-rsAj2u8Xkqfc332iXV12SqIsjVi07H479bOP4q94NAcjzmAvapumEhuVIt53koEf7JFrpjgNKjBga5Pnn/GL8A==" } } }, @@ -12176,9 +12174,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sass": { - "version": "1.38.2", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.38.2.tgz", - "integrity": "sha512-Bz1fG6qiyF0FX6m/I+VxtdVKz1Dfmg/e9kfDy2PhWOkq3T384q2KxwIfP0fXpeI+EyyETdOauH+cRHQDFASllA==", + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.39.0.tgz", + "integrity": "sha512-F4o+RhJkNOIG0b6QudYU8c78ZADKZjKDk5cyrf8XTKWfrgbtyVVXImFstJrc+1pkQDCggyidIOytq6gS4gCCZg==", "dev": true, "requires": { "chokidar": ">=3.0.0 <4.0.0" @@ -12680,8 +12678,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-5.0.0.tgz", "integrity": "sha512-c8aubuARSu5A3vEHLBeOSJt1udOdS+1iue7BmJDTSXoCBmfEQmmWX+59vYIj3NQdJBY6a/QRv1ozVFpaB9jaqA==", - "dev": true, - "requires": {} + "dev": true }, "stylelint-config-standard": { "version": "22.0.0", @@ -13113,6 +13110,14 @@ "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" + }, + "dependencies": { + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "optional": true + } } }, "vfile": { @@ -13138,9 +13143,9 @@ } }, "vite": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-2.5.2.tgz", - "integrity": "sha512-JK5uhiVyMqHiAJbgBa8rCvpP8bEhAE9dKDv1gCmP+EUP2FSPmEeW3WXlCXauPB3MDa8behPW+ntyNXqnGaxslg==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-2.5.3.tgz", + "integrity": "sha512-1wMDnjflvtTTkMov8O/Xb5+w1/VW/Gw8oCf8f6dqgHn8lMOEqq0SaPtFEQeikFcOKCfSbiU0nEi0LDIx6DNsaQ==", "dev": true, "requires": { "esbuild": "^0.12.17", @@ -13151,19 +13156,19 @@ } }, "vue": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.6.tgz", - "integrity": "sha512-Zlb3LMemQS3Xxa6xPsecu45bNjr1hxO8Bh5FUmE0Dr6Ot0znZBKiM47rK6O7FTcakxOnvVN+NTXWJF6u8ajpCQ==", + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.8.tgz", + "integrity": "sha512-x7lwdnOSkceHQUXRVVHBaZzcp6v7M2CYtSZH75zZaT1mTjB4plC4KZHKP/5jAvdqOLBHZGwDSMkWXm3YbAufrA==", "requires": { - "@vue/compiler-dom": "3.2.6", - "@vue/runtime-dom": "3.2.6", - "@vue/shared": "3.2.6" + "@vue/compiler-dom": "3.2.8", + "@vue/runtime-dom": "3.2.8", + "@vue/shared": "3.2.8" } }, "vue-chart-3": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/vue-chart-3/-/vue-chart-3-0.5.7.tgz", - "integrity": "sha512-BccfPv2rodY6IOppYHvMluVmIJE1CHfp5uW2DXrHrm1kIzaafLwpQ5SwdrxuCevn/QhKoi7azzcxwRcoWbX9hg==", + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/vue-chart-3/-/vue-chart-3-0.5.8.tgz", + "integrity": "sha512-VJEBTdMgWOaYqekXtz4LVBIeYyIx3qDlQnFyY4Ao1GwizokYZBycCeRN3oKDcYbbZi5yxYqTy6+Tm+m+SOPUPA==", "requires": { "@vue/runtime-core": "latest", "@vue/runtime-dom": "latest", @@ -13176,14 +13181,12 @@ "vue-confirm-dialog": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/vue-confirm-dialog/-/vue-confirm-dialog-1.0.2.tgz", - "integrity": "sha512-gTo1bMDWOLd/6ihmWv8VlPxhc9QaKoE5YqlsKydUOfrrQ3Q3taljF6yI+1TMtAtJLrvZ8DYrePhgBhY1VCJzbQ==", - "requires": {} + "integrity": "sha512-gTo1bMDWOLd/6ihmWv8VlPxhc9QaKoE5YqlsKydUOfrrQ3Q3taljF6yI+1TMtAtJLrvZ8DYrePhgBhY1VCJzbQ==" }, "vue-demi": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.10.1.tgz", - "integrity": "sha512-L6Oi+BvmMv6YXvqv5rJNCFHEKSVu7llpWWJczqmAQYOdmPPw5PNYoz1KKS//Fxhi+4QP64dsPjtmvnYGo1jemA==", - "requires": {} + "integrity": "sha512-L6Oi+BvmMv6YXvqv5rJNCFHEKSVu7llpWWJczqmAQYOdmPPw5PNYoz1KKS//Fxhi+4QP64dsPjtmvnYGo1jemA==" }, "vue-eslint-parser": { "version": "7.10.0", @@ -13246,8 +13249,7 @@ "vue-toastification": { "version": "2.0.0-rc.1", "resolved": "https://registry.npmjs.org/vue-toastification/-/vue-toastification-2.0.0-rc.1.tgz", - "integrity": "sha512-hjauv/FyesNZdwcr5m1SCyvu1JmlB+Ts5bTptDLDmsYYlj6Oqv8NYakiElpCF+Abwkn9J/AChh6FwkTL1HOb7Q==", - "requires": {} + "integrity": "sha512-hjauv/FyesNZdwcr5m1SCyvu1JmlB+Ts5bTptDLDmsYYlj6Oqv8NYakiElpCF+Abwkn9J/AChh6FwkTL1HOb7Q==" }, "which": { "version": "2.0.2", @@ -13292,8 +13294,7 @@ "ws": { "version": "7.4.6", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", - "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", - "requires": {} + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==" }, "xmlhttprequest-ssl": { "version": "2.0.0", diff --git a/package.json b/package.json index f279351ec..c04be03f4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uptime-kuma", - "version": "1.5.2", + "version": "1.5.3", "license": "MIT", "repository": { "type": "git", @@ -16,15 +16,14 @@ "dev": "vite --host", "start": "npm run start-server", "start-server": "node server/server.js", - "start-demo-server": "set NODE_ENV=demo && node server/server.js", - "update": "", "build": "vite build", "vite-preview-dist": "vite preview --host", - "build-docker": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.5.2 --target release . --push", + "build-docker": "npm run build-docker-alpine && npm run build-docker-debian", + "build-docker-alpine": "docker buildx build -f dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:1.5.3-alpine --target release . --push", + "build-docker-debian": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.5.3 -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:1.5.3-debian --target release . --push", "build-docker-nightly": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push", "build-docker-nightly-amd64": "docker buildx build --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain", - "build-docker-1.5.0-debian": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:1.5.0-debian --target release . --push", - "setup": "git checkout 1.5.2 && npm install --legacy-peer-deps && node node_modules/esbuild/install.js && npm run build && npm prune", + "setup": "git checkout 1.5.3 && npm install --legacy-peer-deps && node node_modules/esbuild/install.js && npm run build && npm prune", "update-version": "node extra/update-version.js", "mark-as-nightly": "node extra/mark-as-nightly.js", "reset-password": "node extra/reset-password.js", @@ -54,19 +53,19 @@ "express": "^4.17.1", "express-basic-auth": "^1.2.0", "form-data": "^4.0.0", - "http-graceful-shutdown": "^3.1.3", + "http-graceful-shutdown": "^3.1.4", "jsonwebtoken": "^8.5.1", "nodemailer": "^6.6.3", "password-hash": "^1.2.2", "prom-client": "^13.2.0", "prometheus-api-metrics": "^3.2.0", "redbean-node": "0.1.2", - "socket.io": "^4.1.3", - "socket.io-client": "^4.1.3", + "socket.io": "^4.2.0", + "socket.io-client": "^4.2.0", "sqlite3": "github:mapbox/node-sqlite3#593c9d", "tcp-ping": "^0.1.1", "v-pagination-3": "^0.1.6", - "vue": "^3.2.2", + "vue": "^3.2.8", "vue-chart-3": "^0.5.7", "vue-confirm-dialog": "^1.0.2", "vue-i18n": "^9.1.7", @@ -76,18 +75,18 @@ }, "devDependencies": { "@babel/eslint-parser": "^7.15.0", - "@types/bootstrap": "^5.1.1", - "@vitejs/plugin-legacy": "^1.5.1", - "@vitejs/plugin-vue": "^1.4.0", - "@vue/compiler-sfc": "^3.2.2", - "core-js": "^3.16.1", + "@types/bootstrap": "^5.1.2", + "@vitejs/plugin-legacy": "^1.5.2", + "@vitejs/plugin-vue": "^1.6.0", + "@vue/compiler-sfc": "^3.2.6", + "core-js": "^3.17.0", "dns2": "^2.0.1", "eslint": "^7.32.0", - "eslint-plugin-vue": "^7.16.0", - "sass": "^1.37.5", + "eslint-plugin-vue": "^7.17.0", + "sass": "^1.38.2", "stylelint": "^13.13.1", "stylelint-config-standard": "^22.0.0", - "typescript": "^4.3.5", - "vite": "^2.4.4" + "typescript": "^4.4.2", + "vite": "^2.5.3" } } diff --git a/server/client.js b/server/client.js new file mode 100644 index 000000000..e83d1f59a --- /dev/null +++ b/server/client.js @@ -0,0 +1,88 @@ +/* + * For Client Socket + */ +const { TimeLogger } = require("../src/util"); +const { R } = require("redbean-node"); +const { io } = require("./server"); + +async function sendNotificationList(socket) { + const timeLogger = new TimeLogger(); + + let result = []; + let list = await R.find("notification", " user_id = ? ", [ + socket.userID, + ]); + + for (let bean of list) { + result.push(bean.export()) + } + + io.to(socket.userID).emit("notificationList", result) + + timeLogger.print("Send Notification List"); + + return list; +} + +/** + * Send Heartbeat History list to socket + * @param toUser True = send to all browsers with the same user id, False = send to the current browser only + * @param overwrite Overwrite client-side's heartbeat list + */ +async function sendHeartbeatList(socket, monitorID, toUser = false, overwrite = false) { + const timeLogger = new TimeLogger(); + + let list = await R.getAll(` + SELECT * FROM heartbeat + WHERE monitor_id = ? + ORDER BY time DESC + LIMIT 100 + `, [ + monitorID, + ]) + + let result = list.reverse(); + + if (toUser) { + io.to(socket.userID).emit("heartbeatList", monitorID, result, overwrite); + } else { + socket.emit("heartbeatList", monitorID, result, overwrite); + } + + timeLogger.print(`[Monitor: ${monitorID}] sendHeartbeatList`); +} + +/** + * Important Heart beat list (aka event list) + * @param socket + * @param monitorID + * @param toUser True = send to all browsers with the same user id, False = send to the current browser only + * @param overwrite Overwrite client-side's heartbeat list + */ +async function sendImportantHeartbeatList(socket, monitorID, toUser = false, overwrite = false) { + const timeLogger = new TimeLogger(); + + let list = await R.find("heartbeat", ` + monitor_id = ? + AND important = 1 + ORDER BY time DESC + LIMIT 500 + `, [ + monitorID, + ]) + + timeLogger.print(`[Monitor: ${monitorID}] sendImportantHeartbeatList`); + + if (toUser) { + io.to(socket.userID).emit("importantHeartbeatList", monitorID, list, overwrite); + } else { + socket.emit("importantHeartbeatList", monitorID, list, overwrite); + } + +} + +module.exports = { + sendNotificationList, + sendImportantHeartbeatList, + sendHeartbeatList, +} diff --git a/server/database.js b/server/database.js index c0a53a9b8..b76b38713 100644 --- a/server/database.js +++ b/server/database.js @@ -5,8 +5,9 @@ const { setSetting, setting } = require("./util-server"); class Database { static templatePath = "./db/kuma.db" - static path = "./data/kuma.db"; - static latestVersion = 8; + static dataDir; + static path; + static latestVersion = 9; static noReject = true; static sqliteInstance = null; @@ -35,7 +36,11 @@ class Database { // Change to WAL await R.exec("PRAGMA journal_mode = WAL"); + await R.exec("PRAGMA cache_size = -12000"); + + console.log("SQLite config:"); console.log(await R.getAll("PRAGMA journal_mode")); + console.log(await R.getAll("PRAGMA cache_size")); } static async patch() { @@ -56,7 +61,7 @@ class Database { console.info("Database patch is needed") console.info("Backup the db") - const backupPath = "./data/kuma.db.bak" + version; + const backupPath = this.dataDir + "kuma.db.bak" + version; fs.copyFileSync(Database.path, backupPath); const shmPath = Database.path + "-shm"; diff --git a/server/model/monitor.js b/server/model/monitor.js index 19f21d924..89208a3fd 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -309,7 +309,10 @@ class Monitor extends BeanModel { previousBeat = bean; - this.heartbeatInterval = setTimeout(beat, this.interval * 1000); + if (! this.isStop) { + this.heartbeatInterval = setTimeout(beat, this.interval * 1000); + } + } beat(); @@ -317,6 +320,7 @@ class Monitor extends BeanModel { stop() { clearTimeout(this.heartbeatInterval); + this.isStop = true; } /** @@ -405,58 +409,59 @@ class Monitor extends BeanModel { static async sendUptime(duration, io, monitorID, userID) { const timeLogger = new TimeLogger(); - let sec = duration * 3600; - - let heartbeatList = await R.getAll(` - SELECT duration, time, status + const startTime = R.isoDateTime(dayjs.utc().subtract(duration, "hour")); + + // Handle if heartbeat duration longer than the target duration + // e.g. If the last beat's duration is bigger that the 24hrs window, it will use the duration between the (beat time - window margin) (THEN case in SQL) + let result = await R.getRow(` + SELECT + -- SUM all duration, also trim off the beat out of time window + SUM( + CASE + WHEN (JULIANDAY(\`time\`) - JULIANDAY(?)) * 86400 < duration + THEN (JULIANDAY(\`time\`) - JULIANDAY(?)) * 86400 + ELSE duration + END + ) AS total_duration, + + -- SUM all uptime duration, also trim off the beat out of time window + SUM( + CASE + WHEN (status = 1) + THEN + CASE + WHEN (JULIANDAY(\`time\`) - JULIANDAY(?)) * 86400 < duration + THEN (JULIANDAY(\`time\`) - JULIANDAY(?)) * 86400 + ELSE duration + END + END + ) AS uptime_duration FROM heartbeat - WHERE time > DATETIME('now', ? || ' hours') - AND monitor_id = ? `, [ - -duration, + WHERE time > ? + AND monitor_id = ? + `, [ + startTime, startTime, startTime, startTime, startTime, monitorID, ]); timeLogger.print(`[Monitor: ${monitorID}][${duration}] sendUptime`); - let downtime = 0; - let total = 0; - let uptime; - - // Special handle for the first heartbeat only - if (heartbeatList.length === 1) { + let totalDuration = result.total_duration; + let uptimeDuration = result.uptime_duration; + let uptime = 0; - if (heartbeatList[0].status === 1) { - uptime = 1; - } else { + if (totalDuration > 0) { + uptime = uptimeDuration / totalDuration; + if (uptime < 0) { uptime = 0; } } else { - for (let row of heartbeatList) { - let value = parseInt(row.duration) - let time = row.time - - // Handle if heartbeat duration longer than the target duration - // e.g. Heartbeat duration = 28hrs, but target duration = 24hrs - if (value > sec) { - let trim = dayjs.utc().diff(dayjs(time), "second"); - value = sec - trim; - - if (value < 0) { - value = 0; - } - } - - total += value; - if (row.status === 0 || row.status === 2) { - downtime += value; - } - } - - uptime = (total - downtime) / total; - - if (uptime < 0) { - uptime = 0; + // Handle new monitor with only one beat, because the beat's duration = 0 + let status = parseInt(await R.getCell("SELECT `status` FROM heartbeat WHERE monitor_id = ?", [ monitorID ])); + console.log("here???" + status); + if (status === UP) { + uptime = 1; } } diff --git a/server/notification-providers/apprise.js b/server/notification-providers/apprise.js new file mode 100644 index 000000000..fdcd8d61b --- /dev/null +++ b/server/notification-providers/apprise.js @@ -0,0 +1,26 @@ +const NotificationProvider = require("./notification-provider"); +const child_process = 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 output = (s.stdout) ? s.stdout.toString() : "ERROR: maybe apprise not found"; + + if (output) { + + if (! output.includes("ERROR")) { + return "Sent Successfully"; + } + + throw new Error(output) + } else { + return "No output from apprise"; + } + } +} + +module.exports = Apprise; diff --git a/server/notification-providers/discord.js b/server/notification-providers/discord.js new file mode 100644 index 000000000..d6ee0afe1 --- /dev/null +++ b/server/notification-providers/discord.js @@ -0,0 +1,105 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); +const { DOWN, UP } = require("../../src/util"); + +class Discord extends NotificationProvider { + + name = "discord"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully. "; + + try { + const discordDisplayName = notification.discordUsername || "Uptime Kuma"; + + // If heartbeatJSON is null, assume we're testing. + if (heartbeatJSON == null) { + let discordtestdata = { + username: discordDisplayName, + content: msg, + } + await axios.post(notification.discordWebhookUrl, discordtestdata) + return okMsg; + } + + let url; + + if (monitorJSON["type"] === "port") { + url = monitorJSON["hostname"]; + if (monitorJSON["port"]) { + url += ":" + monitorJSON["port"]; + } + + } else { + url = monitorJSON["url"]; + } + + // If heartbeatJSON is not null, we go into the normal alerting loop. + if (heartbeatJSON["status"] == DOWN) { + let discorddowndata = { + username: discordDisplayName, + embeds: [{ + title: "❌ Your service " + monitorJSON["name"] + " went down. ❌", + color: 16711680, + timestamp: heartbeatJSON["time"], + fields: [ + { + name: "Service Name", + value: monitorJSON["name"], + }, + { + name: "Service URL", + value: url, + }, + { + name: "Time (UTC)", + value: heartbeatJSON["time"], + }, + { + name: "Error", + value: heartbeatJSON["msg"], + }, + ], + }], + } + await axios.post(notification.discordWebhookUrl, discorddowndata) + return okMsg; + + } else if (heartbeatJSON["status"] == UP) { + let discordupdata = { + username: discordDisplayName, + embeds: [{ + title: "✅ Your service " + monitorJSON["name"] + " is up! ✅", + color: 65280, + timestamp: heartbeatJSON["time"], + fields: [ + { + name: "Service Name", + value: monitorJSON["name"], + }, + { + name: "Service URL", + value: url.startsWith("http") ? "[Visit Service](" + url + ")" : url, + }, + { + name: "Time (UTC)", + value: heartbeatJSON["time"], + }, + { + name: "Ping", + value: heartbeatJSON["ping"] + "ms", + }, + ], + }], + } + await axios.post(notification.discordWebhookUrl, discordupdata) + return okMsg; + } + } catch (error) { + this.throwGeneralAxiosError(error) + } + } + +} + +module.exports = Discord; diff --git a/server/notification-providers/gotify.js b/server/notification-providers/gotify.js new file mode 100644 index 000000000..9d2d55aa5 --- /dev/null +++ b/server/notification-providers/gotify.js @@ -0,0 +1,28 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); + +class Gotify extends NotificationProvider { + + name = "gotify"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully. "; + try { + if (notification.gotifyserverurl && notification.gotifyserverurl.endsWith("/")) { + notification.gotifyserverurl = notification.gotifyserverurl.slice(0, -1); + } + await axios.post(`${notification.gotifyserverurl}/message?token=${notification.gotifyapplicationToken}`, { + "message": msg, + "priority": notification.gotifyPriority || 8, + "title": "Uptime-Kuma", + }) + + return okMsg; + + } catch (error) { + this.throwGeneralAxiosError(error); + } + } +} + +module.exports = Gotify; diff --git a/server/notification-providers/line.js b/server/notification-providers/line.js new file mode 100644 index 000000000..830969034 --- /dev/null +++ b/server/notification-providers/line.js @@ -0,0 +1,60 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); +const { DOWN, UP } = require("../../src/util"); + +class Line extends NotificationProvider { + + name = "line"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully. "; + try { + let lineAPIUrl = "https://api.line.me/v2/bot/message/push"; + let config = { + headers: { + "Content-Type": "application/json", + "Authorization": "Bearer " + notification.lineChannelAccessToken + } + }; + if (heartbeatJSON == null) { + let testMessage = { + "to": notification.lineUserID, + "messages": [ + { + "type": "text", + "text": "Test Successful!" + } + ] + } + await axios.post(lineAPIUrl, testMessage, config) + } else if (heartbeatJSON["status"] == DOWN) { + let downMessage = { + "to": notification.lineUserID, + "messages": [ + { + "type": "text", + "text": "UptimeKuma Alert: [🔴 Down]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"] + } + ] + } + await axios.post(lineAPIUrl, downMessage, config) + } else if (heartbeatJSON["status"] == UP) { + let upMessage = { + "to": notification.lineUserID, + "messages": [ + { + "type": "text", + "text": "UptimeKuma Alert: [✅ Up]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"] + } + ] + } + await axios.post(lineAPIUrl, upMessage, config) + } + return okMsg; + } catch (error) { + this.throwGeneralAxiosError(error) + } + } +} + +module.exports = Line; diff --git a/server/notification-providers/lunasea.js b/server/notification-providers/lunasea.js new file mode 100644 index 000000000..fb6cd2368 --- /dev/null +++ b/server/notification-providers/lunasea.js @@ -0,0 +1,48 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); +const { DOWN, UP } = require("../../src/util"); + +class LunaSea extends NotificationProvider { + + name = "lunasea"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully. "; + let lunaseadevice = "https://notify.lunasea.app/v1/custom/device/" + notification.lunaseaDevice + + try { + if (heartbeatJSON == null) { + let testdata = { + "title": "Uptime Kuma Alert", + "body": "Testing Successful.", + } + await axios.post(lunaseadevice, testdata) + return okMsg; + } + + if (heartbeatJSON["status"] == DOWN) { + let downdata = { + "title": "UptimeKuma Alert: " + monitorJSON["name"], + "body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], + } + await axios.post(lunaseadevice, downdata) + return okMsg; + } + + if (heartbeatJSON["status"] == UP) { + let updata = { + "title": "UptimeKuma Alert: " + monitorJSON["name"], + "body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], + } + await axios.post(lunaseadevice, updata) + return okMsg; + } + + } catch (error) { + this.throwGeneralAxiosError(error) + } + + } +} + +module.exports = LunaSea; diff --git a/server/notification-providers/mattermost.js b/server/notification-providers/mattermost.js new file mode 100644 index 000000000..97779435a --- /dev/null +++ b/server/notification-providers/mattermost.js @@ -0,0 +1,123 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); +const { DOWN, UP } = require("../../src/util"); + +class Mattermost extends NotificationProvider { + + name = "mattermost"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully. "; + try { + const mattermostUserName = notification.mattermostusername || "Uptime Kuma"; + // If heartbeatJSON is null, assume we're testing. + if (heartbeatJSON == null) { + let mattermostTestData = { + username: mattermostUserName, + text: msg, + } + await axios.post(notification.mattermostWebhookUrl, mattermostTestData) + return okMsg; + } + + const mattermostChannel = notification.mattermostchannel; + const mattermostIconEmoji = notification.mattermosticonemo; + const mattermostIconUrl = notification.mattermosticonurl; + + if (heartbeatJSON["status"] == DOWN) { + let mattermostdowndata = { + username: mattermostUserName, + text: "Uptime Kuma Alert", + channel: mattermostChannel, + icon_emoji: mattermostIconEmoji, + icon_url: mattermostIconUrl, + attachments: [ + { + fallback: + "Your " + + monitorJSON["name"] + + " service went down.", + color: "#FF0000", + title: + "❌ " + + monitorJSON["name"] + + " service went down. ❌", + title_link: monitorJSON["url"], + fields: [ + { + short: true, + title: "Service Name", + value: monitorJSON["name"], + }, + { + short: true, + title: "Time (UTC)", + value: heartbeatJSON["time"], + }, + { + short: false, + title: "Error", + value: heartbeatJSON["msg"], + }, + ], + }, + ], + }; + await axios.post( + notification.mattermostWebhookUrl, + mattermostdowndata + ); + return okMsg; + } else if (heartbeatJSON["status"] == UP) { + let mattermostupdata = { + username: mattermostUserName, + text: "Uptime Kuma Alert", + channel: mattermostChannel, + icon_emoji: mattermostIconEmoji, + icon_url: mattermostIconUrl, + attachments: [ + { + fallback: + "Your " + + monitorJSON["name"] + + " service went up!", + color: "#32CD32", + title: + "✅ " + + monitorJSON["name"] + + " service went up! ✅", + title_link: monitorJSON["url"], + fields: [ + { + short: true, + title: "Service Name", + value: monitorJSON["name"], + }, + { + short: true, + title: "Time (UTC)", + value: heartbeatJSON["time"], + }, + { + short: false, + title: "Ping", + value: heartbeatJSON["ping"] + "ms", + }, + ], + }, + ], + }; + await axios.post( + notification.mattermostWebhookUrl, + mattermostupdata + ); + return okMsg; + } + } catch (error) { + this.throwGeneralAxiosError(error); + } + + } +} + +module.exports = Mattermost; diff --git a/server/notification-providers/notification-provider.js b/server/notification-providers/notification-provider.js new file mode 100644 index 000000000..61c6242d7 --- /dev/null +++ b/server/notification-providers/notification-provider.js @@ -0,0 +1,36 @@ +class NotificationProvider { + + /** + * Notification Provider Name + * @type string + */ + name = undefined; + + /** + * @param notification : BeanModel + * @param msg : string General Message + * @param monitorJSON : object Monitor details (For Up/Down only) + * @param heartbeatJSON : object Heartbeat details (For Up/Down only) + * @returns {Promise} Return Successful Message + * Throw Error with fail msg + */ + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + throw new Error("Have to override Notification.send(...)"); + } + + throwGeneralAxiosError(error) { + let msg = "Error: " + error + " "; + + if (error.response && error.response.data) { + if (typeof error.response.data === "string") { + msg += error.response.data; + } else { + msg += JSON.stringify(error.response.data) + } + } + + throw new Error(msg) + } +} + +module.exports = NotificationProvider; diff --git a/server/notification-providers/octopush.js b/server/notification-providers/octopush.js new file mode 100644 index 000000000..40273f9b8 --- /dev/null +++ b/server/notification-providers/octopush.js @@ -0,0 +1,40 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); + +class Octopush extends NotificationProvider { + + name = "octopush"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully. "; + + try { + let config = { + headers: { + "api-key": notification.octopushAPIKey, + "api-login": notification.octopushLogin, + "cache-control": "no-cache" + } + }; + let data = { + "recipients": [ + { + "phone_number": notification.octopushPhoneNumber + } + ], + //octopush not supporting non ascii char + "text": msg.replace(/[^\x00-\x7F]/g, ""), + "type": notification.octopushSMSType, + "purpose": "alert", + "sender": notification.octopushSenderName + }; + + await axios.post("https://api.octopush.com/v1/public/sms-campaign/send", data, config) + return okMsg; + } catch (error) { + this.throwGeneralAxiosError(error); + } + } +} + +module.exports = Octopush; diff --git a/server/notification-providers/pushbullet.js b/server/notification-providers/pushbullet.js new file mode 100644 index 000000000..0ed6f0fdf --- /dev/null +++ b/server/notification-providers/pushbullet.js @@ -0,0 +1,50 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); + +const { DOWN, UP } = require("../../src/util"); + +class Pushbullet extends NotificationProvider { + + name = "pushbullet"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully. "; + + try { + let pushbulletUrl = "https://api.pushbullet.com/v2/pushes"; + let config = { + headers: { + "Access-Token": notification.pushbulletAccessToken, + "Content-Type": "application/json" + } + }; + if (heartbeatJSON == null) { + let testdata = { + "type": "note", + "title": "Uptime Kuma Alert", + "body": "Testing Successful.", + } + await axios.post(pushbulletUrl, testdata, config) + } 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) { + let updata = { + "type": "note", + "title": "UptimeKuma Alert: " + monitorJSON["name"], + "body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], + } + await axios.post(pushbulletUrl, updata, config) + } + return okMsg; + } catch (error) { + this.throwGeneralAxiosError(error) + } + } +} + +module.exports = Pushbullet; diff --git a/server/notification-providers/pushover.js b/server/notification-providers/pushover.js new file mode 100644 index 000000000..2133ca1cf --- /dev/null +++ b/server/notification-providers/pushover.js @@ -0,0 +1,49 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); + +class Pushover extends NotificationProvider { + + name = "pushover"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully. "; + let pushoverlink = "https://api.pushover.net/1/messages.json" + + try { + if (heartbeatJSON == null) { + let data = { + "message": "Uptime Kuma Pushover testing successful.", + "user": notification.pushoveruserkey, + "token": notification.pushoverapptoken, + "sound": notification.pushoversounds, + "priority": notification.pushoverpriority, + "title": notification.pushovertitle, + "retry": "30", + "expire": "3600", + "html": 1, + } + await axios.post(pushoverlink, data) + return okMsg; + } + + let data = { + "message": "Uptime Kuma Alert\n\nMessage:" + msg + "\nTime (UTC):" + heartbeatJSON["time"], + "user": notification.pushoveruserkey, + "token": notification.pushoverapptoken, + "sound": notification.pushoversounds, + "priority": notification.pushoverpriority, + "title": notification.pushovertitle, + "retry": "30", + "expire": "3600", + "html": 1, + } + await axios.post(pushoverlink, data) + return okMsg; + } catch (error) { + this.throwGeneralAxiosError(error) + } + + } +} + +module.exports = Pushover; diff --git a/server/notification-providers/pushy.js b/server/notification-providers/pushy.js new file mode 100644 index 000000000..431cf8c37 --- /dev/null +++ b/server/notification-providers/pushy.js @@ -0,0 +1,30 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); + +class Pushy extends NotificationProvider { + + name = "pushy"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully. "; + + try { + await axios.post(`https://api.pushy.me/push?api_key=${notification.pushyAPIKey}`, { + "to": notification.pushyToken, + "data": { + "message": "Uptime-Kuma" + }, + "notification": { + "body": msg, + "badge": 1, + "sound": "ping.aiff" + } + }) + return okMsg; + } catch (error) { + this.throwGeneralAxiosError(error) + } + } +} + +module.exports = Pushy; diff --git a/server/notification-providers/rocket-chat.js b/server/notification-providers/rocket-chat.js new file mode 100644 index 000000000..149189650 --- /dev/null +++ b/server/notification-providers/rocket-chat.js @@ -0,0 +1,46 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); + +class RocketChat extends NotificationProvider { + + name = "rocket.chat"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully. "; + try { + if (heartbeatJSON == null) { + let data = { + "text": "Uptime Kuma Rocket.chat testing successful.", + "channel": notification.rocketchannel, + "username": notification.rocketusername, + "icon_emoji": notification.rocketiconemo, + } + await axios.post(notification.rocketwebhookURL, data) + return okMsg; + } + + const time = heartbeatJSON["time"]; + let data = { + "text": "Uptime Kuma Alert", + "channel": notification.rocketchannel, + "username": notification.rocketusername, + "icon_emoji": notification.rocketiconemo, + "attachments": [ + { + "title": "Uptime Kuma Alert *Time (UTC)*\n" + time, + "title_link": notification.rocketbutton, + "text": "*Message*\n" + msg, + "color": "#32cd32" + } + ] + } + await axios.post(notification.rocketwebhookURL, data) + return okMsg; + } catch (error) { + this.throwGeneralAxiosError(error) + } + + } +} + +module.exports = RocketChat; diff --git a/server/notification-providers/signal.js b/server/notification-providers/signal.js new file mode 100644 index 000000000..ba5f87f9c --- /dev/null +++ b/server/notification-providers/signal.js @@ -0,0 +1,27 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); + +class Signal extends NotificationProvider { + + name = "signal"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully. "; + + try { + let data = { + "message": msg, + "number": notification.signalNumber, + "recipients": notification.signalRecipients.replace(/\s/g, "").split(","), + }; + let config = {}; + + await axios.post(notification.signalURL, data, config) + return okMsg; + } catch (error) { + this.throwGeneralAxiosError(error) + } + } +} + +module.exports = Signal; diff --git a/server/notification-providers/slack.js b/server/notification-providers/slack.js new file mode 100644 index 000000000..661df5a0f --- /dev/null +++ b/server/notification-providers/slack.js @@ -0,0 +1,70 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); + +class Slack extends NotificationProvider { + + name = "slack"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully. "; + try { + if (heartbeatJSON == null) { + let data = { + "text": "Uptime Kuma Slack testing successful.", + "channel": notification.slackchannel, + "username": notification.slackusername, + "icon_emoji": notification.slackiconemo, + } + await axios.post(notification.slackwebhookURL, data) + return okMsg; + } + + const time = heartbeatJSON["time"]; + let data = { + "text": "Uptime Kuma Alert", + "channel": notification.slackchannel, + "username": notification.slackusername, + "icon_emoji": notification.slackiconemo, + "blocks": [{ + "type": "header", + "text": { + "type": "plain_text", + "text": "Uptime Kuma Alert", + }, + }, + { + "type": "section", + "fields": [{ + "type": "mrkdwn", + "text": "*Message*\n" + msg, + }, + { + "type": "mrkdwn", + "text": "*Time (UTC)*\n" + time, + }], + }, + { + "type": "actions", + "elements": [ + { + "type": "button", + "text": { + "type": "plain_text", + "text": "Visit Uptime Kuma", + }, + "value": "Uptime-Kuma", + "url": notification.slackbutton || "https://github.com/louislam/uptime-kuma", + }, + ], + }], + } + await axios.post(notification.slackwebhookURL, data) + return okMsg; + } catch (error) { + this.throwGeneralAxiosError(error) + } + + } +} + +module.exports = Slack; diff --git a/server/notification-providers/smtp.js b/server/notification-providers/smtp.js new file mode 100644 index 000000000..ecb583eb7 --- /dev/null +++ b/server/notification-providers/smtp.js @@ -0,0 +1,48 @@ +const nodemailer = require("nodemailer"); +const NotificationProvider = require("./notification-provider"); + +class SMTP extends NotificationProvider { + + name = "smtp"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + + const config = { + host: notification.smtpHost, + port: notification.smtpPort, + secure: notification.smtpSecure, + }; + + // Should fix the issue in https://github.com/louislam/uptime-kuma/issues/26#issuecomment-896373904 + if (notification.smtpUsername || notification.smtpPassword) { + config.auth = { + user: notification.smtpUsername, + pass: notification.smtpPassword, + }; + } + + let transporter = nodemailer.createTransport(config); + + let bodyTextContent = msg; + if (heartbeatJSON) { + bodyTextContent = `${msg}\nTime (UTC): ${heartbeatJSON["time"]}`; + } + + // send mail with defined transport object + await transporter.sendMail({ + from: notification.smtpFrom, + cc: notification.smtpCC, + bcc: notification.smtpBCC, + to: notification.smtpTo, + subject: msg, + text: bodyTextContent, + tls: { + rejectUnauthorized: notification.smtpIgnoreTLSError || false, + }, + }); + + return "Sent Successfully."; + } +} + +module.exports = SMTP; diff --git a/server/notification-providers/telegram.js b/server/notification-providers/telegram.js new file mode 100644 index 000000000..f88dcf5de --- /dev/null +++ b/server/notification-providers/telegram.js @@ -0,0 +1,27 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); + +class Telegram extends NotificationProvider { + + name = "telegram"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully. "; + + try { + await axios.get(`https://api.telegram.org/bot${notification.telegramBotToken}/sendMessage`, { + params: { + chat_id: notification.telegramChatID, + text: msg, + }, + }) + return okMsg; + + } catch (error) { + let msg = (error.response.data.description) ? error.response.data.description : "Error without description" + throw new Error(msg) + } + } +} + +module.exports = Telegram; diff --git a/server/notification-providers/webhook.js b/server/notification-providers/webhook.js new file mode 100644 index 000000000..197e9f9f3 --- /dev/null +++ b/server/notification-providers/webhook.js @@ -0,0 +1,44 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); +const FormData = require("form-data"); + +class Webhook extends NotificationProvider { + + name = "webhook"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully. "; + + try { + let data = { + heartbeat: heartbeatJSON, + monitor: monitorJSON, + msg, + }; + let finalData; + let config = {}; + + if (notification.webhookContentType === "form-data") { + finalData = new FormData(); + finalData.append("data", JSON.stringify(data)); + + config = { + headers: finalData.getHeaders(), + } + + } else { + finalData = data; + } + + await axios.post(notification.webhookURL, finalData, config) + return okMsg; + + } catch (error) { + this.throwGeneralAxiosError(error) + } + + } + +} + +module.exports = Webhook; diff --git a/server/notification.js b/server/notification.js index 472012af7..83dabc537 100644 --- a/server/notification.js +++ b/server/notification.js @@ -1,600 +1,75 @@ -const axios = require("axios"); const { R } = require("redbean-node"); -const FormData = require("form-data"); -const nodemailer = require("nodemailer"); -const child_process = require("child_process"); +const Apprise = require("./notification-providers/apprise"); +const Discord = require("./notification-providers/discord"); +const Gotify = require("./notification-providers/gotify"); +const Line = require("./notification-providers/line"); +const LunaSea = require("./notification-providers/lunasea"); +const Mattermost = require("./notification-providers/mattermost"); +const Octopush = require("./notification-providers/octopush"); +const Pushbullet = require("./notification-providers/pushbullet"); +const Pushover = require("./notification-providers/pushover"); +const Pushy = require("./notification-providers/pushy"); +const RocketChat = require("./notification-providers/rocket-chat"); +const Signal = require("./notification-providers/signal"); +const Slack = require("./notification-providers/slack"); +const SMTP = require("./notification-providers/smtp"); +const Telegram = require("./notification-providers/telegram"); +const Webhook = require("./notification-providers/webhook"); class Notification { + providerList = {}; + + static init() { + console.log("Prepare Notification Providers"); + + this.providerList = {}; + + const list = [ + new Apprise(), + new Discord(), + new Gotify(), + new Line(), + new LunaSea(), + new Mattermost(), + new Octopush(), + new Pushbullet(), + new Pushover(), + new Pushy(), + new RocketChat(), + new Signal(), + new Slack(), + new SMTP(), + new Telegram(), + new Webhook(), + ]; + + for (let item of list) { + if (! item.name) { + throw new Error("Notification provider without name"); + } + + if (this.providerList[item.name]) { + throw new Error("Duplicate notification provider name"); + } + this.providerList[item.name] = item; + } + } + /** * - * @param notification - * @param msg - * @param monitorJSON - * @param heartbeatJSON + * @param notification : BeanModel + * @param msg : string General Message + * @param monitorJSON : object Monitor details (For Up/Down only) + * @param heartbeatJSON : object Heartbeat details (For Up/Down only) * @returns {Promise} Successful msg * Throw Error with fail msg */ static async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { - let okMsg = "Sent Successfully. "; - - if (notification.type === "telegram") { - try { - await axios.get(`https://api.telegram.org/bot${notification.telegramBotToken}/sendMessage`, { - params: { - chat_id: notification.telegramChatID, - text: msg, - }, - }) - return okMsg; - - } catch (error) { - let msg = (error.response.data.description) ? error.response.data.description : "Error without description" - throw new Error(msg) - } - - } else if (notification.type === "gotify") { - try { - if (notification.gotifyserverurl && notification.gotifyserverurl.endsWith("/")) { - notification.gotifyserverurl = notification.gotifyserverurl.slice(0, -1); - } - await axios.post(`${notification.gotifyserverurl}/message?token=${notification.gotifyapplicationToken}`, { - "message": msg, - "priority": notification.gotifyPriority || 8, - "title": "Uptime-Kuma", - }) - - return okMsg; - - } catch (error) { - throwGeneralAxiosError(error) - } - - } else if (notification.type === "webhook") { - try { - let data = { - heartbeat: heartbeatJSON, - monitor: monitorJSON, - msg, - }; - let finalData; - let config = {}; - - if (notification.webhookContentType === "form-data") { - finalData = new FormData(); - finalData.append("data", JSON.stringify(data)); - - config = { - headers: finalData.getHeaders(), - } - - } else { - finalData = data; - } - - await axios.post(notification.webhookURL, finalData, config) - return okMsg; - - } catch (error) { - throwGeneralAxiosError(error) - } - - } else if (notification.type === "smtp") { - return await Notification.smtp(notification, msg) - - } else if (notification.type === "discord") { - try { - const discordDisplayName = notification.discordUsername || "Uptime Kuma"; - - // If heartbeatJSON is null, assume we're testing. - if (heartbeatJSON == null) { - let discordtestdata = { - username: discordDisplayName, - content: msg, - } - await axios.post(notification.discordWebhookUrl, discordtestdata) - return okMsg; - } - - let url; - - if (monitorJSON["type"] === "port") { - url = monitorJSON["hostname"]; - if (monitorJSON["port"]) { - url += ":" + monitorJSON["port"]; - } - - } else { - url = monitorJSON["url"]; - } - - // If heartbeatJSON is not null, we go into the normal alerting loop. - if (heartbeatJSON["status"] == 0) { - let discorddowndata = { - username: discordDisplayName, - embeds: [{ - title: "❌ Your service " + monitorJSON["name"] + " went down. ❌", - color: 16711680, - timestamp: heartbeatJSON["time"], - fields: [ - { - name: "Service Name", - value: monitorJSON["name"], - }, - { - name: "Service URL", - value: url, - }, - { - name: "Time (UTC)", - value: heartbeatJSON["time"], - }, - { - name: "Error", - value: heartbeatJSON["msg"], - }, - ], - }], - } - await axios.post(notification.discordWebhookUrl, discorddowndata) - return okMsg; - - } else if (heartbeatJSON["status"] == 1) { - let discordupdata = { - username: discordDisplayName, - embeds: [{ - title: "✅ Your service " + monitorJSON["name"] + " is up! ✅", - color: 65280, - timestamp: heartbeatJSON["time"], - fields: [ - { - name: "Service Name", - value: monitorJSON["name"], - }, - { - name: "Service URL", - value: url.startsWith("http") ? "[Visit Service](" + url + ")" : url, - }, - { - name: "Time (UTC)", - value: heartbeatJSON["time"], - }, - { - name: "Ping", - value: heartbeatJSON["ping"] + "ms", - }, - ], - }], - } - await axios.post(notification.discordWebhookUrl, discordupdata) - return okMsg; - } - } catch (error) { - throwGeneralAxiosError(error) - } - - } else if (notification.type === "signal") { - try { - let data = { - "message": msg, - "number": notification.signalNumber, - "recipients": notification.signalRecipients.replace(/\s/g, "").split(","), - }; - let config = {}; - - await axios.post(notification.signalURL, data, config) - return okMsg; - } catch (error) { - throwGeneralAxiosError(error) - } - - } else if (notification.type === "pushy") { - try { - await axios.post(`https://api.pushy.me/push?api_key=${notification.pushyAPIKey}`, { - "to": notification.pushyToken, - "data": { - "message": "Uptime-Kuma" - }, - "notification": { - "body": msg, - "badge": 1, - "sound": "ping.aiff" - } - }) - return true; - } catch (error) { - console.log(error) - return false; - } - } else if (notification.type === "octopush") { - try { - let config = { - headers: { - "api-key": notification.octopushAPIKey, - "api-login": notification.octopushLogin, - "cache-control": "no-cache" - } - }; - let data = { - "recipients": [ - { - "phone_number": notification.octopushPhoneNumber - } - ], - //octopush not supporting non ascii char - "text": msg.replace(/[^\x00-\x7F]/g, ""), - "type": notification.octopushSMSType, - "purpose": "alert", - "sender": notification.octopushSenderName - }; - - await axios.post("https://api.octopush.com/v1/public/sms-campaign/send", data, config) - return true; - } catch (error) { - console.log(error) - return false; - } - } else if (notification.type === "slack") { - try { - if (heartbeatJSON == null) { - let data = { - "text": "Uptime Kuma Slack testing successful.", - "channel": notification.slackchannel, - "username": notification.slackusername, - "icon_emoji": notification.slackiconemo, - } - await axios.post(notification.slackwebhookURL, data) - return okMsg; - } - - const time = heartbeatJSON["time"]; - let data = { - "text": "Uptime Kuma Alert", - "channel": notification.slackchannel, - "username": notification.slackusername, - "icon_emoji": notification.slackiconemo, - "blocks": [{ - "type": "header", - "text": { - "type": "plain_text", - "text": "Uptime Kuma Alert", - }, - }, - { - "type": "section", - "fields": [{ - "type": "mrkdwn", - "text": "*Message*\n" + msg, - }, - { - "type": "mrkdwn", - "text": "*Time (UTC)*\n" + time, - }], - }, - { - "type": "actions", - "elements": [ - { - "type": "button", - "text": { - "type": "plain_text", - "text": "Visit Uptime Kuma", - }, - "value": "Uptime-Kuma", - "url": notification.slackbutton || "https://github.com/louislam/uptime-kuma", - }, - ], - }], - } - await axios.post(notification.slackwebhookURL, data) - return okMsg; - } catch (error) { - throwGeneralAxiosError(error) - } - - } else if (notification.type === "rocket.chat") { - try { - if (heartbeatJSON == null) { - let data = { - "text": "Uptime Kuma Rocket.chat testing successful.", - "channel": notification.rocketchannel, - "username": notification.rocketusername, - "icon_emoji": notification.rocketiconemo, - } - await axios.post(notification.rocketwebhookURL, data) - return okMsg; - } - - const time = heartbeatJSON["time"]; - let data = { - "text": "Uptime Kuma Alert", - "channel": notification.rocketchannel, - "username": notification.rocketusername, - "icon_emoji": notification.rocketiconemo, - "attachments": [ - { - "title": "Uptime Kuma Alert *Time (UTC)*\n" + time, - "title_link": notification.rocketbutton, - "text": "*Message*\n" + msg, - "color": "#32cd32" - } - ] - } - await axios.post(notification.rocketwebhookURL, data) - return okMsg; - } catch (error) { - throwGeneralAxiosError(error) - } - - } else if (notification.type === "mattermost") { - try { - const mattermostUserName = notification.mattermostusername || "Uptime Kuma"; - // If heartbeatJSON is null, assume we're testing. - if (heartbeatJSON == null) { - let mattermostTestData = { - username: mattermostUserName, - text: msg, - } - await axios.post(notification.mattermostWebhookUrl, mattermostTestData) - return okMsg; - } - - const mattermostChannel = notification.mattermostchannel; - const mattermostIconEmoji = notification.mattermosticonemo; - const mattermostIconUrl = notification.mattermosticonurl; - - if (heartbeatJSON["status"] == 0) { - let mattermostdowndata = { - username: mattermostUserName, - text: "Uptime Kuma Alert", - channel: mattermostChannel, - icon_emoji: mattermostIconEmoji, - icon_url: mattermostIconUrl, - attachments: [ - { - fallback: - "Your " + - monitorJSON["name"] + - " service went down.", - color: "#FF0000", - title: - "❌ " + - monitorJSON["name"] + - " service went down. ❌", - title_link: monitorJSON["url"], - fields: [ - { - short: true, - title: "Service Name", - value: monitorJSON["name"], - }, - { - short: true, - title: "Time (UTC)", - value: heartbeatJSON["time"], - }, - { - short: false, - title: "Error", - value: heartbeatJSON["msg"], - }, - ], - }, - ], - }; - await axios.post( - notification.mattermostWebhookUrl, - mattermostdowndata - ); - return okMsg; - } else if (heartbeatJSON["status"] == 1) { - let mattermostupdata = { - username: mattermostUserName, - text: "Uptime Kuma Alert", - channel: mattermostChannel, - icon_emoji: mattermostIconEmoji, - icon_url: mattermostIconUrl, - attachments: [ - { - fallback: - "Your " + - monitorJSON["name"] + - " service went up!", - color: "#32CD32", - title: - "✅ " + - monitorJSON["name"] + - " service went up! ✅", - title_link: monitorJSON["url"], - fields: [ - { - short: true, - title: "Service Name", - value: monitorJSON["name"], - }, - { - short: true, - title: "Time (UTC)", - value: heartbeatJSON["time"], - }, - { - short: false, - title: "Ping", - value: heartbeatJSON["ping"] + "ms", - }, - ], - }, - ], - }; - await axios.post( - notification.mattermostWebhookUrl, - mattermostupdata - ); - return okMsg; - } - } catch (error) { - throwGeneralAxiosError(error); - } - - } else if (notification.type === "pushover") { - let pushoverlink = "https://api.pushover.net/1/messages.json" - try { - if (heartbeatJSON == null) { - let data = { - "message": "Uptime Kuma Pushover testing successful.", - "user": notification.pushoveruserkey, - "token": notification.pushoverapptoken, - "sound": notification.pushoversounds, - "priority": notification.pushoverpriority, - "title": notification.pushovertitle, - "retry": "30", - "expire": "3600", - "html": 1, - } - await axios.post(pushoverlink, data) - return okMsg; - } - - let data = { - "message": "Uptime Kuma Alert\n\nMessage:" + msg + "\nTime (UTC):" + heartbeatJSON["time"], - "user": notification.pushoveruserkey, - "token": notification.pushoverapptoken, - "sound": notification.pushoversounds, - "priority": notification.pushoverpriority, - "title": notification.pushovertitle, - "retry": "30", - "expire": "3600", - "html": 1, - } - await axios.post(pushoverlink, data) - return okMsg; - } catch (error) { - throwGeneralAxiosError(error) - } - - } else if (notification.type === "apprise") { - - return Notification.apprise(notification, msg) - - } else if (notification.type === "lunasea") { - let lunaseadevice = "https://notify.lunasea.app/v1/custom/device/" + notification.lunaseaDevice - - try { - if (heartbeatJSON == null) { - let testdata = { - "title": "Uptime Kuma Alert", - "body": "Testing Successful.", - } - await axios.post(lunaseadevice, testdata) - return okMsg; - } - - if (heartbeatJSON["status"] == 0) { - let downdata = { - "title": "UptimeKuma Alert:" + monitorJSON["name"], - "body": "[🔴 Down]" + heartbeatJSON["msg"] + "\nTime (UTC):" + heartbeatJSON["time"], - } - await axios.post(lunaseadevice, downdata) - return okMsg; - } - - if (heartbeatJSON["status"] == 1) { - let updata = { - "title": "UptimeKuma Alert:" + monitorJSON["name"], - "body": "[✅ Up]" + heartbeatJSON["msg"] + "\nTime (UTC):" + heartbeatJSON["time"], - } - await axios.post(lunaseadevice, updata) - return okMsg; - } - - } catch (error) { - throwGeneralAxiosError(error) - } - - } else if (notification.type === "pushbullet") { - try { - let pushbulletUrl = "https://api.pushbullet.com/v2/pushes"; - let config = { - headers: { - "Access-Token": notification.pushbulletAccessToken, - "Content-Type": "application/json" - } - }; - if (heartbeatJSON == null) { - let testdata = { - "type": "note", - "title": "Uptime Kuma Alert", - "body": "Testing Successful.", - } - await axios.post(pushbulletUrl, testdata, config) - } else if (heartbeatJSON["status"] == 0) { - 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"] == 1) { - let updata = { - "type": "note", - "title": "UptimeKuma Alert:" + monitorJSON["name"], - "body": "[✅ Up]" + heartbeatJSON["msg"] + "\nTime (UTC):" + heartbeatJSON["time"], - } - await axios.post(pushbulletUrl, updata, config) - } - return okMsg; - } catch (error) { - throwGeneralAxiosError(error) - } - } else if (notification.type === "line") { - try { - let lineAPIUrl = "https://api.line.me/v2/bot/message/push"; - let config = { - headers: { - "Content-Type": "application/json", - "Authorization": "Bearer " + notification.lineChannelAccessToken - } - }; - if (heartbeatJSON == null) { - let testMessage = { - "to": notification.lineUserID, - "messages": [ - { - "type": "text", - "text": "Test Successful!" - } - ] - } - await axios.post(lineAPIUrl, testMessage, config) - } else if (heartbeatJSON["status"] == 0) { - let downMessage = { - "to": notification.lineUserID, - "messages": [ - { - "type": "text", - "text": "UptimeKuma Alert: [🔴 Down]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"] - } - ] - } - await axios.post(lineAPIUrl, downMessage, config) - } else if (heartbeatJSON["status"] == 1) { - let upMessage = { - "to": notification.lineUserID, - "messages": [ - { - "type": "text", - "text": "UptimeKuma Alert: [✅ Up]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"] - } - ] - } - await axios.post(lineAPIUrl, upMessage, config) - } - return okMsg; - } catch (error) { - throwGeneralAxiosError(error) - } + if (this.providerList[notification.type]) { + return this.providerList[notification.type].send(notification, msg, monitorJSON, heartbeatJSON); } else { - throw new Error("Notification type is not supported") + throw new Error("Notification type is not supported"); } } @@ -617,8 +92,15 @@ class Notification { bean.name = notification.name; bean.user_id = userID; - bean.config = JSON.stringify(notification) + bean.config = JSON.stringify(notification); + bean.is_default = notification.isDefault || false; await R.store(bean) + + if (notification.applyExisting) { + await applyNotificationEveryMonitor(bean.id, userID); + } + + return bean; } static async delete(notificationID, userID) { @@ -634,52 +116,6 @@ class Notification { await R.trash(bean) } - static async smtp(notification, msg) { - - const config = { - host: notification.smtpHost, - port: notification.smtpPort, - secure: notification.smtpSecure, - }; - - // Should fix the issue in https://github.com/louislam/uptime-kuma/issues/26#issuecomment-896373904 - if (notification.smtpUsername || notification.smtpPassword) { - config.auth = { - user: notification.smtpUsername, - pass: notification.smtpPassword, - }; - } - - let transporter = nodemailer.createTransport(config); - - // send mail with defined transport object - await transporter.sendMail({ - from: `"Uptime Kuma" <${notification.smtpFrom}>`, - to: notification.smtpTo, - subject: msg, - text: msg, - }); - - return "Sent Successfully."; - } - - static async apprise(notification, msg) { - let s = child_process.spawnSync("apprise", [ "-vv", "-b", msg, notification.appriseURL]) - - let output = (s.stdout) ? s.stdout.toString() : "ERROR: maybe apprise not found"; - - if (output) { - - if (! output.includes("ERROR")) { - return "Sent Successfully"; - } - - throw new Error(output) - } else { - return "" - } - } - static checkApprise() { let commandExistsSync = require("command-exists").sync; let exists = commandExistsSync("apprise"); @@ -688,18 +124,24 @@ class Notification { } -function throwGeneralAxiosError(error) { - let msg = "Error: " + error + " "; +async function applyNotificationEveryMonitor(notificationID, userID) { + let monitors = await R.getAll("SELECT id FROM monitor WHERE user_id = ?", [ + userID + ]); - if (error.response && error.response.data) { - if (typeof error.response.data === "string") { - msg += error.response.data; - } else { - msg += JSON.stringify(error.response.data) + for (let i = 0; i < monitors.length; i++) { + let checkNotification = await R.findOne("monitor_notification", " monitor_id = ? AND notification_id = ? ", [ + monitors[i].id, + notificationID, + ]) + + if (! checkNotification) { + let relation = R.dispense("monitor_notification"); + relation.monitor_id = monitors[i].id; + relation.notification_id = notificationID; + await R.store(relation) } } - - throw new Error(msg) } module.exports = { diff --git a/server/server.js b/server/server.js index d4fe668b3..2949c4be7 100644 --- a/server/server.js +++ b/server/server.js @@ -6,6 +6,7 @@ const { sleep, debug, TimeLogger, getRandomInt } = require("../src/util"); console.log("Importing Node libraries") const fs = require("fs"); const http = require("http"); +const https = require("https"); console.log("Importing 3rd-party libraries") debug("Importing express"); @@ -26,8 +27,11 @@ debug("Importing Monitor"); const Monitor = require("./model/monitor"); debug("Importing Settings"); const { getSettings, setSettings, setting, initJWTSecret } = require("./util-server"); + debug("Importing Notification"); const { Notification } = require("./notification"); +Notification.init(); + debug("Importing Database"); const Database = require("./database"); @@ -45,11 +49,48 @@ console.info("Version: " + checkVersion.version); const hostname = process.env.HOST || args.host; const port = parseInt(process.env.PORT || args.port || 3001); +// SSL +const sslKey = process.env.SSL_KEY || args["ssl-key"] || undefined; +const sslCert = process.env.SSL_CERT || args["ssl-cert"] || undefined; + +// Demo Mode? +const demoMode = args["demo"] || false; + +if (demoMode) { + console.log("==== Demo Mode ===="); +} + +// Data Directory (must be end with "/") +Database.dataDir = process.env.DATA_DIR || args["data-dir"] || "./data/"; +Database.path = Database.dataDir + "kuma.db"; +if (! fs.existsSync(Database.dataDir)) { + fs.mkdirSync(Database.dataDir, { recursive: true }); +} +console.log(`Data Dir: ${Database.dataDir}`); + console.log("Creating express and socket.io instance") const app = express(); -const server = http.createServer(app); + +let server; + +if (sslKey && sslCert) { + console.log("Server Type: HTTPS"); + server = https.createServer({ + key: fs.readFileSync(sslKey), + cert: fs.readFileSync(sslCert) + }, app); +} else { + console.log("Server Type: HTTP"); + server = http.createServer(app); +} + const io = new Server(server); -app.use(express.json()) +module.exports.io = io; + +// Must be after io instantiation +const { sendNotificationList, sendHeartbeatList, sendImportantHeartbeatList } = require("./client"); + +app.use(express.json()); /** * Total WebSocket client connected to server currently, no actual use @@ -486,12 +527,13 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString(); try { checkLogin(socket) - await Notification.save(notification, notificationID, socket.userID) + let notificationBean = await Notification.save(notification, notificationID, socket.userID) await sendNotificationList(socket) callback({ ok: true, msg: "Saved", + id: notificationBean.id, }); } catch (e) { @@ -552,6 +594,152 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString(); } }); + socket.on("uploadBackup", async (uploadedJSON, callback) => { + try { + checkLogin(socket) + + let backupData = JSON.parse(uploadedJSON); + + console.log(`Importing Backup, User ID: ${socket.userID}, Version: ${backupData.version}`) + + let notificationList = backupData.notificationList; + let monitorList = backupData.monitorList; + + if (notificationList.length >= 1) { + for (let i = 0; i < notificationList.length; i++) { + let notification = JSON.parse(notificationList[i].config); + await Notification.save(notification, null, socket.userID) + } + } + + if (monitorList.length >= 1) { + for (let i = 0; i < monitorList.length; i++) { + let monitor = { + name: monitorList[i].name, + type: monitorList[i].type, + url: monitorList[i].url, + interval: monitorList[i].interval, + hostname: monitorList[i].hostname, + maxretries: monitorList[i].maxretries, + port: monitorList[i].port, + keyword: monitorList[i].keyword, + ignoreTls: monitorList[i].ignoreTls, + upsideDown: monitorList[i].upsideDown, + maxredirects: monitorList[i].maxredirects, + accepted_statuscodes: monitorList[i].accepted_statuscodes, + dns_resolve_type: monitorList[i].dns_resolve_type, + dns_resolve_server: monitorList[i].dns_resolve_server, + notificationIDList: {}, + } + + let bean = R.dispense("monitor") + + let notificationIDList = monitor.notificationIDList; + delete monitor.notificationIDList; + + monitor.accepted_statuscodes_json = JSON.stringify(monitor.accepted_statuscodes); + delete monitor.accepted_statuscodes; + + bean.import(monitor) + bean.user_id = socket.userID + await R.store(bean) + + await updateMonitorNotification(bean.id, notificationIDList) + + if (monitorList[i].active == 1) { + await startMonitor(socket.userID, bean.id); + } else { + await pauseMonitor(socket.userID, bean.id); + } + } + + await sendNotificationList(socket) + await sendMonitorList(socket); + } + + callback({ + ok: true, + msg: "Backup successfully restored.", + }); + + } catch (e) { + callback({ + ok: false, + msg: e.message, + }); + } + }); + + socket.on("clearEvents", async (monitorID, callback) => { + try { + checkLogin(socket) + + console.log(`Clear Events Monitor: ${monitorID} User ID: ${socket.userID}`) + + await R.exec("UPDATE heartbeat SET msg = ?, important = ? WHERE monitor_id = ? ", [ + "", + "0", + monitorID, + ]); + + await sendImportantHeartbeatList(socket, monitorID, true, true); + + callback({ + ok: true, + }); + + } catch (e) { + callback({ + ok: false, + msg: e.message, + }); + } + }); + + socket.on("clearHeartbeats", async (monitorID, callback) => { + try { + checkLogin(socket) + + console.log(`Clear Heartbeats Monitor: ${monitorID} User ID: ${socket.userID}`) + + await R.exec("DELETE FROM heartbeat WHERE monitor_id = ?", [ + monitorID + ]); + + await sendHeartbeatList(socket, monitorID, true, true); + + callback({ + ok: true, + }); + + } catch (e) { + callback({ + ok: false, + msg: e.message, + }); + } + }); + + socket.on("clearStatistics", async (callback) => { + try { + checkLogin(socket) + + console.log(`Clear Statistics User ID: ${socket.userID}`) + + await R.exec("DELETE FROM heartbeat"); + + callback({ + ok: true, + }); + + } catch (e) { + callback({ + ok: false, + msg: e.message, + }); + } + }); + debug("added all socket handlers") // *************************** @@ -620,25 +808,6 @@ async function sendMonitorList(socket) { return list; } -async function sendNotificationList(socket) { - const timeLogger = new TimeLogger(); - - let result = []; - let list = await R.find("notification", " user_id = ? ", [ - socket.userID, - ]); - - for (let bean of list) { - result.push(bean.export()) - } - - io.to(socket.userID).emit("notificationList", result) - - timeLogger.print("Send Notification List"); - - return list; -} - async function afterLogin(socket, user) { socket.userID = user.id; socket.join(user.id) @@ -773,48 +942,6 @@ async function startMonitors() { } } -/** - * Send Heartbeat History list to socket - */ -async function sendHeartbeatList(socket, monitorID) { - const timeLogger = new TimeLogger(); - - let list = await R.find("heartbeat", ` - monitor_id = ? - ORDER BY time DESC - LIMIT 100 - `, [ - monitorID, - ]) - - let result = []; - - for (let bean of list) { - result.unshift(bean.toJSON()) - } - - socket.emit("heartbeatList", monitorID, result) - - timeLogger.print(`[Monitor: ${monitorID}] sendHeartbeatList`) -} - -async function sendImportantHeartbeatList(socket, monitorID) { - const timeLogger = new TimeLogger(); - - let list = await R.find("heartbeat", ` - monitor_id = ? - AND important = 1 - ORDER BY time DESC - LIMIT 500 - `, [ - monitorID, - ]) - - timeLogger.print(`[Monitor: ${monitorID}] sendImportantHeartbeatList`); - - socket.emit("importantHeartbeatList", monitorID, list) -} - async function shutdownFunction(signal) { console.log("Shutdown requested"); console.log("Called signal: " + signal); diff --git a/src/assets/app.scss b/src/assets/app.scss index 41248a5c4..581645735 100644 --- a/src/assets/app.scss +++ b/src/assets/app.scss @@ -71,6 +71,14 @@ h2 { } } +.btn-warning { + color: white; + + &:hover, &:active, &:focus, &.active { + color: white; + } +} + .btn-info { color: white; @@ -144,7 +152,7 @@ h2 { } .form-switch .form-check-input { - background-color: #131a21; + background-color: #232f3b; } a, @@ -186,6 +194,14 @@ h2 { color: white; } + .btn-warning { + color: $dark-font-color2; + + &:hover, &:active, &:focus, &.active { + color: $dark-font-color2; + } + } + .btn-close { box-shadow: none; filter: invert(1); diff --git a/src/assets/vars.scss b/src/assets/vars.scss index 9d487eb26..2f4369832 100644 --- a/src/assets/vars.scss +++ b/src/assets/vars.scss @@ -16,3 +16,5 @@ $dark-border-color: #1d2634; $easing-in: cubic-bezier(0.54, 0.78, 0.55, 0.97); $easing-out: cubic-bezier(0.25, 0.46, 0.45, 0.94); $easing-in-out: cubic-bezier(0.79, 0.14, 0.15, 0.86); + +$dropdown-border-radius: 0.5rem; diff --git a/src/components/HeartbeatBar.vue b/src/components/HeartbeatBar.vue index 33b003db3..fb6086d00 100644 --- a/src/components/HeartbeatBar.vue +++ b/src/components/HeartbeatBar.vue @@ -31,7 +31,7 @@ export default { beatWidth: 10, beatHeight: 30, hoverScale: 1.5, - beatMargin: 3, // Odd number only, even = blurry + beatMargin: 4, move: false, maxBeat: -1, } @@ -122,11 +122,25 @@ export default { this.$root.heartbeatList[this.monitorId] = []; } }, + mounted() { if (this.size === "small") { - this.beatWidth = 5.6; - this.beatMargin = 2.4; - this.beatHeight = 16 + this.beatWidth = 5; + this.beatHeight = 16; + this.beatMargin = 2; + } + + // Suddenly, have an idea how to handle it universally. + // If the pixel * ratio != Integer, then it causes render issue, round it to solve it!! + const actualWidth = this.beatWidth * window.devicePixelRatio; + const actualMargin = this.beatMargin * window.devicePixelRatio; + + if (! Number.isInteger(actualWidth)) { + this.beatWidth = Math.round(actualWidth) / window.devicePixelRatio; + } + + if (! Number.isInteger(actualMargin)) { + this.beatMargin = Math.round(actualMargin) / window.devicePixelRatio; } window.addEventListener("resize", this.resize); diff --git a/src/components/HiddenInput.vue b/src/components/HiddenInput.vue new file mode 100644 index 000000000..2b588af19 --- /dev/null +++ b/src/components/HiddenInput.vue @@ -0,0 +1,78 @@ + + + diff --git a/src/components/NotificationDialog.vue b/src/components/NotificationDialog.vue index 2fdb8fe18..ffb7ba71d 100644 --- a/src/components/NotificationDialog.vue +++ b/src/components/NotificationDialog.vue @@ -11,8 +11,8 @@