diff --git a/.github/ISSUE_TEMPLATE/ask-for-help.md b/.github/ISSUE_TEMPLATE/ask-for-help.md index 79ec21c66..3031e077a 100644 --- a/.github/ISSUE_TEMPLATE/ask-for-help.md +++ b/.github/ISSUE_TEMPLATE/ask-for-help.md @@ -6,7 +6,7 @@ labels: help assignees: '' --- -**Is it a duplicate question?** +**Is it a duplicated question?** Please search in Issues without filters: https://github.com/louislam/uptime-kuma/issues?q= **Describe your problem** diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 370b88b8b..069ed6cc0 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,7 +7,7 @@ assignees: '' --- -**Is it a duplicate question?** +**Is it a duplicated question?** Please search in Issues without filters: https://github.com/louislam/uptime-kuma/issues?q= **Describe the bug** diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 9141130ae..4794cc242 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -6,7 +6,7 @@ labels: enhancement assignees: '' --- -**Is it a duplicate question?** +**Is it a duplicated question?** Please search in Issues without filters: https://github.com/louislam/uptime-kuma/issues?q= **Is your feature request related to a problem? Please describe.** diff --git a/.github/workflows/stale-bot b/.github/workflows/stale-bot new file mode 100644 index 000000000..5dc501369 --- /dev/null +++ b/.github/workflows/stale-bot @@ -0,0 +1,22 @@ +name: 'Automatically close stale issues and PRs' +on: + schedule: + - cron: '0 0 * * *' +#Run once a day at midnight + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v4 + with: + stale-issue-message: 'We are clearing up our old issues and your ticket has been open for 6 months with no activity. Remove stale label or comment or this will be closed in 7 days.' + stale-pr-message: 'We are clearing up our old Pull Requests and yours has been open for 6 months with no activity. Remove stale label or comment or this will be closed in 7 days.' + close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity.' + close-pr-message: 'This PR was closed because it has been stalled for 7 days with no activity.' + days-before-stale: 180 + days-before-close: 7 + exempt-issue-labels: 'News,Medium,High,discussion,bug,doc,' + exempt-pr-labels: 'awaiting-approval,work-in-progress,enhancement,' + exempt-issue-assignees: 'louislam' + exempt-pr-assignees: 'louislam' diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index b618a2c47..746334e6f 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -60,7 +60,7 @@ representative at an online or offline event. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at -louis@uptimekuma.louislam.net. +uptime@kuma.pet. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9c4d5dc4f..1d9b37a3c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -178,3 +178,24 @@ Patch release = the third digit ([Semantic Versioning](https://semver.org/)) ## Translations Please read: https://github.com/louislam/uptime-kuma/tree/master/src/languages + + +## Maintainer + +Check the latest issues and pull requests: +https://github.com/louislam/uptime-kuma/issues?q=sort%3Aupdated-desc + +### Release Procedures +1. Draft a release note +1. Make sure the repo is cleared +1. `npm run update-version 1.X.X` +1. `npm run build-docker` +1. git push +1. Publish the release note as 1.X.X +1. npm run upload-artifacts +1. SSH to demo site server and update to 1.X.X + +Checking: +- Check all tags is fine on https://hub.docker.com/r/louislam/uptime-kuma/tags +- Try the Docker image with tag 1.X.X (Clean install / amd64 / arm64 / armv7) +- Try clean install with Node.js diff --git a/README.md b/README.md index 1dc492bf5..6caa1a85d 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Try it! https://demo.uptime.kuma.pet -It is a 10 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. +It is a temporary live demo, all data will be deleted after 10 minutes. The server is located at Tokyo, so if you live far from there it may affect your experience. I suggest that you should install and try it out for the best demo experience. VPS is sponsored by Uptime Kuma sponsors on [Open Collective](https://opencollective.com/uptime-kuma)! Thank you so much! @@ -25,7 +25,7 @@ VPS is sponsored by Uptime Kuma sponsors on [Open Collective](https://opencollec * Monitoring uptime for HTTP(s) / TCP / Ping / DNS Record / Push. * 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/tree/master/src/components/notifications). -* 20 seconds interval. +* 20 second intervals. * [Multi Languages](https://github.com/louislam/uptime-kuma/tree/master/src/languages) * Simple Status Page * Ping Chart @@ -40,7 +40,7 @@ docker volume create uptime-kuma docker run -d --restart=always -p 3001:3001 -v uptime-kuma:/app/data --name uptime-kuma louislam/uptime-kuma:1 ``` -Browse to http://localhost:3001 after started. +Browse to http://localhost:3001 after starting. ### 💪🏻 Without Docker @@ -58,11 +58,11 @@ npm run setup node server/server.js # (Recommended) Option 2. Run in background using PM2 -# Install PM2 if you don't have: npm install pm2 -g +# Install PM2 if you don't have it: npm install pm2 -g pm2 start server/server.js --name uptime-kuma ``` -Browse to http://localhost:3001 after started. +Browse to http://localhost:3001 after starting. ### Advanced Installation @@ -124,7 +124,7 @@ You can discuss or ask for help in [Issues](https://github.com/louislam/uptime-k ### Subreddit My Reddit account: louislamlam -You can mention me if you ask question on Reddit. +You can mention me if you ask a question on Reddit. https://www.reddit.com/r/UptimeKuma/ ## Contribute diff --git a/SECURITY.md b/SECURITY.md index 48ab9a72f..a0b2562f4 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -9,8 +9,8 @@ currently being supported with security updates. | Version | Supported | | ------- | ------------------ | -| 1.7.X | :white_check_mark: | -| < 1.7 | ❌ | +| 1.8.X | :white_check_mark: | +| <= 1.7.X | ❌ | ### Upgradable Docker Tags diff --git a/db/patch-http-monitor-method-body-and-headers.sql b/db/patch-http-monitor-method-body-and-headers.sql new file mode 100644 index 000000000..dc2526b4f --- /dev/null +++ b/db/patch-http-monitor-method-body-and-headers.sql @@ -0,0 +1,13 @@ +-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. +BEGIN TRANSACTION; + +ALTER TABLE monitor + ADD method TEXT default 'GET' not null; + +ALTER TABLE monitor + ADD body TEXT default null; + +ALTER TABLE monitor + ADD headers TEXT default null; + +COMMIT; diff --git a/docker/dockerfile b/docker/dockerfile index 97655748e..e2a3725fa 100644 --- a/docker/dockerfile +++ b/docker/dockerfile @@ -31,14 +31,15 @@ WORKDIR / RUN apt update && \ apt --yes install curl file +COPY --from=build /app /app + +ARG VERSION=1.9.1 ARG GITHUB_TOKEN ARG TARGETARCH ARG PLATFORM=debian -ARG VERSION ARG FILE=$PLATFORM-$TARGETARCH-$VERSION.tar.gz ARG DIST=dist.tar.gz -COPY --from=build /app /app RUN chmod +x /app/extra/upload-github-release-asset.sh # Full Build diff --git a/extra/reset-password.js b/extra/reset-password.js index be0395897..1b48dffd7 100644 --- a/extra/reset-password.js +++ b/extra/reset-password.js @@ -12,50 +12,59 @@ const rl = readline.createInterface({ output: process.stdout }); -(async () => { +const main = async () => { Database.init(args); await Database.connect(); try { - const user = await R.findOne("user"); - - if (! user) { - throw new Error("user not found, have you installed?"); - } - - console.log("Found user: " + user.username); - - while (true) { - let password = await question("New Password: "); - let confirmPassword = await question("Confirm New Password: "); - - if (password === confirmPassword) { - await user.resetPassword(password); - - // Reset all sessions by reset jwt secret - await initJWTSecret(); - - rl.close(); - break; - } else { - console.log("Passwords do not match, please try again."); + // No need to actually reset the password for testing, just make sure no connection problem. It is ok for now. + if (!process.env.TEST_BACKEND) { + const user = await R.findOne("user"); + if (! user) { + throw new Error("user not found, have you installed?"); } - } - console.log("Password reset successfully."); + console.log("Found user: " + user.username); + + while (true) { + let password = await question("New Password: "); + let confirmPassword = await question("Confirm New Password: "); + + if (password === confirmPassword) { + await user.resetPassword(password); + + // Reset all sessions by reset jwt secret + await initJWTSecret(); + + break; + } else { + console.log("Passwords do not match, please try again."); + } + } + console.log("Password reset successfully."); + } } catch (e) { console.error("Error: " + e.message); } await Database.close(); + rl.close(); - console.log("Finished. You should restart the Uptime Kuma server.") -})(); + console.log("Finished."); +}; function question(question) { return new Promise((resolve) => { rl.question(question, (answer) => { resolve(answer); - }) + }); }); } + +if (!process.env.TEST_BACKEND) { + main(); +} + +module.exports = { + main, +}; diff --git a/extra/update-language-files/index.js b/extra/update-language-files/index.js index a90f9f363..7ba30cc05 100644 --- a/extra/update-language-files/index.js +++ b/extra/update-language-files/index.js @@ -26,10 +26,12 @@ const copyRecursiveSync = function (src, dest) { } }; -console.log("Arguments:", process.argv) +console.log("Arguments:", process.argv); const baseLangCode = process.argv[2] || "en"; console.log("Base Lang: " + baseLangCode); -fs.rmdirSync("./languages", { recursive: true }); +if (fs.existsSync("./languages")) { + fs.rmdirSync("./languages", { recursive: true }); +} copyRecursiveSync("../../src/languages", "./languages"); const en = (await import("./languages/en.js")).default; @@ -39,7 +41,7 @@ console.log("Files:", files); for (const file of files) { if (!file.endsWith(".js")) { - console.log("Skipping " + file) + console.log("Skipping " + file); continue; } diff --git a/kubernetes/README.md b/kubernetes/README.md deleted file mode 100644 index e85b0c4cd..000000000 --- a/kubernetes/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# Uptime-Kuma K8s Deployment - -⚠ Warning: K8s deployment is provided by contributors. I have no experience with K8s and I can't fix error in the future. I only test Docker and Node.js. Use at your own risk. - -## How does it work? - -Kustomize is a tool which builds a complete deployment file for all config elements. -You can edit the files in the ```uptime-kuma``` folder except the ```kustomization.yml``` until you know what you're doing. -If you want to choose another namespace you can edit the ```kustomization.yml``` in the ```kubernetes```-Folder and change the ```namespace: uptime-kuma``` to something you like. - -It creates a certificate with the specified Issuer and creates the Ingress for the Uptime-Kuma ClusterIP-Service. - -## What do I have to edit? - -You have to edit the ```ingressroute.yml``` to your needs. -This ingressroute.yml is for the [nginx-ingress-controller](https://kubernetes.github.io/ingress-nginx/) in combination with the [cert-manager](https://cert-manager.io/). - -- Host -- Secrets and secret names -- (Cluster)Issuer (optional) -- The Version in the Deployment-File - - Update: - - Change to newer version and run the above commands, it will update the pods one after another - -## How To use - -- Install [kustomize](https://kubectl.docs.kubernetes.io/installation/kustomize/) -- Edit files mentioned above to your needs -- Run ```kustomize build > apply.yml``` -- Run ```kubectl apply -f apply.yml``` - -Now you should see some k8s magic and Uptime-Kuma should be available at the specified address. diff --git a/kubernetes/kustomization.yml b/kubernetes/kustomization.yml deleted file mode 100644 index 0daf10f4d..000000000 --- a/kubernetes/kustomization.yml +++ /dev/null @@ -1,10 +0,0 @@ -namespace: uptime-kuma -namePrefix: uptime-kuma- - -commonLabels: - app: uptime-kuma - -bases: - - uptime-kuma - - diff --git a/kubernetes/uptime-kuma/deployment.yml b/kubernetes/uptime-kuma/deployment.yml deleted file mode 100644 index b97ece210..000000000 --- a/kubernetes/uptime-kuma/deployment.yml +++ /dev/null @@ -1,45 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - component: uptime-kuma - name: deployment -spec: - selector: - matchLabels: - component: uptime-kuma - replicas: 1 - strategy: - type: Recreate - - template: - metadata: - labels: - component: uptime-kuma - spec: - containers: - - name: app - image: louislam/uptime-kuma:1 - ports: - - containerPort: 3001 - volumeMounts: - - mountPath: /app/data - name: storage - livenessProbe: - exec: - command: - - node - - extra/healthcheck.js - initialDelaySeconds: 180 - periodSeconds: 60 - timeoutSeconds: 30 - readinessProbe: - httpGet: - path: / - port: 3001 - scheme: HTTP - - volumes: - - name: storage - persistentVolumeClaim: - claimName: pvc diff --git a/kubernetes/uptime-kuma/ingressroute.yml b/kubernetes/uptime-kuma/ingressroute.yml deleted file mode 100644 index 71f7027ff..000000000 --- a/kubernetes/uptime-kuma/ingressroute.yml +++ /dev/null @@ -1,39 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - annotations: - kubernetes.io/ingress.class: nginx - cert-manager.io/cluster-issuer: letsencrypt-prod - nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" - nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" - nginx.ingress.kubernetes.io/server-snippets: | - location / { - proxy_set_header Upgrade $http_upgrade; - proxy_http_version 1.1; - proxy_set_header X-Forwarded-Host $http_host; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-For $remote_addr; - proxy_set_header Host $host; - proxy_set_header Connection "upgrade"; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Upgrade $http_upgrade; - proxy_cache_bypass $http_upgrade; - } - name: ingress -spec: - tls: - - hosts: - - example.com - secretName: example-com-tls - rules: - - host: example.com - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: service - port: - number: 3001 diff --git a/kubernetes/uptime-kuma/kustomization.yml b/kubernetes/uptime-kuma/kustomization.yml deleted file mode 100644 index 638a2ab69..000000000 --- a/kubernetes/uptime-kuma/kustomization.yml +++ /dev/null @@ -1,5 +0,0 @@ -resources: - - deployment.yml - - service.yml - - ingressroute.yml - - pvc.yml \ No newline at end of file diff --git a/kubernetes/uptime-kuma/pvc.yml b/kubernetes/uptime-kuma/pvc.yml deleted file mode 100644 index eda3b8be5..000000000 --- a/kubernetes/uptime-kuma/pvc.yml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: pvc -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 4Gi diff --git a/kubernetes/uptime-kuma/service.yml b/kubernetes/uptime-kuma/service.yml deleted file mode 100644 index 5fa812e18..000000000 --- a/kubernetes/uptime-kuma/service.yml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: service -spec: - selector: - component: uptime-kuma - type: ClusterIP - ports: - - name: http - port: 3001 - targetPort: 3001 - protocol: TCP diff --git a/package-lock.json b/package-lock.json index 296362633..551504d75 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "uptime-kuma", - "version": "1.8.0", + "version": "1.9.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "uptime-kuma", - "version": "1.8.0", + "version": "1.9.1", "license": "MIT", "dependencies": { "@fortawesome/fontawesome-svg-core": "~1.2.36", @@ -19,8 +19,11 @@ "axios": "~0.21.4", "bcryptjs": "~2.4.3", "bootstrap": "~5.1.1", + "bree": "~6.3.1", + "chardet": "^1.3.0", "chart.js": "~3.5.1", "chartjs-adapter-dayjs": "~1.0.0", + "check-password-strength": "^2.0.3", "command-exists": "~1.2.9", "compare-versions": "~3.6.0", "dayjs": "~1.10.7", @@ -28,7 +31,9 @@ "express-basic-auth": "~1.2.0", "form-data": "~4.0.0", "http-graceful-shutdown": "~3.1.4", + "iconv-lite": "^0.6.3", "jsonwebtoken": "~8.5.1", + "limiter": "^2.1.0", "nodemailer": "~6.6.5", "notp": "~2.0.3", "password-hash": "~1.2.2", @@ -64,7 +69,7 @@ "@vitejs/plugin-legacy": "~1.6.1", "@vitejs/plugin-vue": "~1.9.2", "@vue/compiler-sfc": "~3.2.19", - "babel-plugin-rewire": "^1.2.0", + "babel-plugin-rewire": "~1.2.0", "core-js": "~3.18.1", "cross-env": "~7.0.3", "dns2": "~2.0.1", @@ -1714,7 +1719,6 @@ "version": "7.15.4", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz", "integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==", - "dev": true, "dependencies": { "regenerator-runtime": "^0.13.4" }, @@ -1817,6 +1821,14 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@breejs/later": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@breejs/later/-/later-4.0.2.tgz", + "integrity": "sha512-EN0SlbyYouBdtZis1htdsgGlwFePzkXPwdIeqaBaavxkJT1G2/bitc2LSixjv45z2njXslxlJI1mW2O/Gmrb+A==", + "engines": { + "node": ">= 10" + } + }, "node_modules/@eslint/eslintrc": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", @@ -2662,6 +2674,11 @@ "@types/koa": "*" } }, + "node_modules/@types/lodash": { + "version": "4.14.175", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.175.tgz", + "integrity": "sha512-XmdEOrKQ8a1Y/yxQFOMbC47G/V2VDO1GvMRnl4O75M4GW/abC5tnfzadQYkqEveqRM1dEJGFFegfPNA2vvx2iw==" + }, "node_modules/@types/mdast": { "version": "3.0.10", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", @@ -3551,11 +3568,27 @@ "ms": "2.0.0" } }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "node_modules/boolean": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.1.4.tgz", + "integrity": "sha512-3hx0kwU3uzG6ReQ3pnaFQPSktpBw6RHN3/ivDKEuU8g1XSfafowyvDnadjv1xp8IZqhtSukxlwv9bF6FhX8m0w==" + }, "node_modules/bootstrap": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.1.tgz", @@ -3589,6 +3622,29 @@ "node": ">=8" } }, + "node_modules/bree": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/bree/-/bree-6.3.1.tgz", + "integrity": "sha512-FADpEV5c+3ZuFIBothyyRUxZClJD2PetIo0lmqAFJ3ZMI9WsSmQmmstZ86Dy0G4Gyw3nPNdfYTjV7+9pPtlB8g==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@breejs/later": "^4.0.2", + "boolean": "^3.0.2", + "bthreads": "^0.5.1", + "combine-errors": "^3.0.3", + "cron-validate": "^1.4.1", + "debug": "^4.3.1", + "human-interval": "^2.0.0", + "is-string-and-not-blank": "^0.0.2", + "is-valid-path": "^0.1.1", + "ms": "^2.1.2", + "p-wait-for": "3.1.0", + "safe-timers": "^1.1.0" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/browser-process-hrtime": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", @@ -3627,6 +3683,17 @@ "node-int64": "^0.4.0" } }, + "node_modules/bthreads": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/bthreads/-/bthreads-0.5.1.tgz", + "integrity": "sha512-nK7Jo9ll+r1FRMNPWEFRTZMQrX6HhX8JjPAofxmbTNILHqWVIJPmWzCi9JlX/K0DL5AKZTFZg2Qser5C6gVs9A==", + "dependencies": { + "bufio": "~1.0.5" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -3688,6 +3755,14 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, + "node_modules/bufio": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/bufio/-/bufio-1.0.7.tgz", + "integrity": "sha512-bd1dDQhiC+bEbEfg56IdBv7faWa6OipMs/AFFFvtFnB3wAYjlwQpQRZ0pm6ZkgtfL0pILRXhKxOiQj6UzoMR7A==", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", @@ -3813,6 +3888,11 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chardet": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-1.3.0.tgz", + "integrity": "sha512-cyTQGGptIjIT+CMGT5J/0l9c6Fb+565GCFjjeUTKxUO7w3oR+FcNCMEKTn5xtVKaLFmladN7QF68IiQsv5Fbdw==" + }, "node_modules/chart.js": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.5.1.tgz", @@ -3827,6 +3907,11 @@ "dayjs": "^1.8.15" } }, + "node_modules/check-password-strength": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/check-password-strength/-/check-password-strength-2.0.3.tgz", + "integrity": "sha512-UW3YgMUne9QuejgnNWjWwYi4QhWArVj+1OXqDR1NkEQcmMKKO74O3P5ZvXr9JZNbTBfcwlK3yurYCMuJsck83A==" + }, "node_modules/chokidar": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", @@ -3975,6 +4060,15 @@ "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==" }, + "node_modules/combine-errors": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/combine-errors/-/combine-errors-3.0.3.tgz", + "integrity": "sha1-9N9nQAg+VwOjGBEQwrEFUfAD2oY=", + "dependencies": { + "custom-error-instance": "2.1.1", + "lodash.uniqby": "4.5.0" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -4128,6 +4222,14 @@ "node": ">=10" } }, + "node_modules/cron-validate": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cron-validate/-/cron-validate-1.4.3.tgz", + "integrity": "sha512-N+qKw019oQBEPIP5Qwi8Z5XelQ00ThN6Maahwv+9UGu2u/b/MPb35zngMQI0T8pBoNiBrIXGlhvsmspNSYae/w==", + "dependencies": { + "yup": "0.32.9" + } + }, "node_modules/cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -4201,6 +4303,11 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.18.tgz", "integrity": "sha512-RSU6Hyeg14am3Ah4VZEmeX8H7kLwEEirXe6aU2IPfKNvhXwTflK5HQRDNI0ypQXoqmm+QPyG2IaPuQE5zMwSIQ==" }, + "node_modules/custom-error-instance": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/custom-error-instance/-/custom-error-instance-2.1.1.tgz", + "integrity": "sha1-PPY5FIemYppiR+sMoM4ACBt+Nho=" + }, "node_modules/cwd": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/cwd/-/cwd-0.10.0.tgz", @@ -6293,6 +6400,14 @@ "node": ">= 6" } }, + "node_modules/human-interval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/human-interval/-/human-interval-2.0.1.tgz", + "integrity": "sha512-r4Aotzf+OtKIGQCB3odUowy4GfUDTy3aTWTfLd7ZF2gBCy3XW3v/dJLRefZnOFFnjqs5B1TypvS8WarpBkYUNQ==", + "dependencies": { + "numbered": "^1.1.0" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -6303,11 +6418,11 @@ } }, "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" @@ -6577,6 +6692,36 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-invalid-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-invalid-path/-/is-invalid-path-0.1.0.tgz", + "integrity": "sha1-MHqFWzzxqTi0TqcNLGEQYFNxTzQ=", + "dependencies": { + "is-glob": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-invalid-path/node_modules/is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-invalid-path/node_modules/is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -6634,6 +6779,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-string-and-not-blank": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/is-string-and-not-blank/-/is-string-and-not-blank-0.0.2.tgz", + "integrity": "sha512-FyPGAbNVyZpTeDCTXnzuwbu9/WpNXbCfbHXLpCRpN4GANhS00eEIP5Ef+k5HYSNIzIhdN9zRDoBj6unscECvtQ==", + "dependencies": { + "is-string-blank": "^1.0.1" + }, + "engines": { + "node": ">=6.4.0" + } + }, + "node_modules/is-string-blank": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-string-blank/-/is-string-blank-1.0.1.tgz", + "integrity": "sha512-9H+ZBCVs3L9OYqv8nuUAzpcT9OTgMD1yAWrG7ihlnibdkbtB850heAmYWxHuXc4CHy4lKeK69tN+ny1K7gBIrw==" + }, "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -6652,6 +6813,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-valid-path": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-valid-path/-/is-valid-path-0.1.1.tgz", + "integrity": "sha1-EQ+f90w39mPh7HkV60UfLbk6yd8=", + "dependencies": { + "is-invalid-path": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-windows": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", @@ -7650,6 +7822,11 @@ "verror": "1.10.0" } }, + "node_modules/just-performance": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/just-performance/-/just-performance-4.3.0.tgz", + "integrity": "sha512-L7RjvtJsL0QO8xFs5wEoDDzzJwoiowRw6Rn/GnvldlchS2JQr9wFYPiwZcDfrbbujEKqKN0tvENdbjXdYhDp5Q==" + }, "node_modules/jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", @@ -7786,6 +7963,14 @@ "node": ">= 0.8.0" } }, + "node_modules/limiter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-2.1.0.tgz", + "integrity": "sha512-361TYz6iay6n+9KvUUImqdLuFigK+K79qrUtBsXhJTLdH4rIt/r1y8r1iozwh8KbZNpujbFTSh74mJ7bwbAMOw==", + "dependencies": { + "just-performance": "4.3.0" + } + }, "node_modules/lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -7809,6 +7994,51 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, + "node_modules/lodash._baseiteratee": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash._baseiteratee/-/lodash._baseiteratee-4.7.0.tgz", + "integrity": "sha1-NKm1VDVycnw9sueO2uPA6eZr0QI=", + "dependencies": { + "lodash._stringtopath": "~4.8.0" + } + }, + "node_modules/lodash._basetostring": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-4.12.0.tgz", + "integrity": "sha1-kyfJ3FFYhmt/pLnUL0Y45XZt2d8=" + }, + "node_modules/lodash._baseuniq": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz", + "integrity": "sha1-DrtE5FaBSveQXGIS+iybLVG4Qeg=", + "dependencies": { + "lodash._createset": "~4.0.0", + "lodash._root": "~3.0.0" + } + }, + "node_modules/lodash._createset": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/lodash._createset/-/lodash._createset-4.0.3.tgz", + "integrity": "sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY=" + }, + "node_modules/lodash._root": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", + "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=" + }, + "node_modules/lodash._stringtopath": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/lodash._stringtopath/-/lodash._stringtopath-4.8.0.tgz", + "integrity": "sha1-lBvPDmQmbl/B1m/tCmlZVExXaCQ=", + "dependencies": { + "lodash._basetostring": "~4.12.0" + } + }, "node_modules/lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", @@ -7873,6 +8103,15 @@ "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", "dev": true }, + "node_modules/lodash.uniqby": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.5.0.tgz", + "integrity": "sha1-o6F7v2LutiQPSRhG6XwcTipeHiE=", + "dependencies": { + "lodash._baseiteratee": "~4.7.0", + "lodash._baseuniq": "~4.6.0" + } + }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -8283,6 +8522,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/nanoclone": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz", + "integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==" + }, "node_modules/nanoid": { "version": "3.1.28", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.28.tgz", @@ -8524,6 +8768,11 @@ "node": ">=0.10.0" } }, + "node_modules/numbered": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/numbered/-/numbered-1.1.0.tgz", + "integrity": "sha512-pv/ue2Odr7IfYOO0byC1KgBI10wo5YDauLhxY6/saNzAdAs0r1SotGCPzzCLNPL0xtrAwWRialLu23AAu9xO1g==" + }, "node_modules/nwsapi": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", @@ -8634,6 +8883,14 @@ "node": ">=0.10.0" } }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "engines": { + "node": ">=4" + } + }, "node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -8660,6 +8917,17 @@ "node": ">=8" } }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -8668,6 +8936,17 @@ "node": ">=6" } }, + "node_modules/p-wait-for": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-wait-for/-/p-wait-for-3.1.0.tgz", + "integrity": "sha512-0Uy19uhxbssHelu9ynDMcON6BmMk6pH8551CvxROhiz3Vx+yC4RqxjyIDk2V4ll0g9177RKT++PK4zcV58uJ7A==", + "dependencies": { + "p-timeout": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -9160,6 +9439,11 @@ "node": ">= 6" } }, + "node_modules/property-expr": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.4.tgz", + "integrity": "sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg==" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -9527,6 +9811,17 @@ "node": ">= 0.8" } }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -9704,8 +9999,7 @@ "node_modules/regenerator-runtime": { "version": "0.13.9", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", - "dev": true + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" }, "node_modules/regenerator-transform": { "version": "0.14.5", @@ -10124,6 +10418,11 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "node_modules/safe-timers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-timers/-/safe-timers-1.1.0.tgz", + "integrity": "sha1-xYroMl2407BnMi8KTvOgytZ6rYM=" + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -11187,6 +11486,11 @@ "node": ">=0.6" } }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=" + }, "node_modules/tough-cookie": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", @@ -11978,6 +12282,18 @@ "iconv-lite": "0.4.24" } }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/whatwg-mimetype": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", @@ -12232,6 +12548,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yup": { + "version": "0.32.9", + "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.9.tgz", + "integrity": "sha512-Ci1qN+i2H0XpY7syDQ0k5zKQ/DoxO0LzPg8PAR/X4Mpj6DqaeCoIYEEjDJwhArh3Fa7GWbQQVDZKeXYlSH4JMg==", + "dependencies": { + "@babel/runtime": "^7.10.5", + "@types/lodash": "^4.14.165", + "lodash": "^4.17.20", + "lodash-es": "^4.17.15", + "nanoclone": "^0.2.1", + "property-expr": "^2.0.4", + "toposort": "^2.0.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/zwitch": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", @@ -13386,7 +13719,6 @@ "version": "7.15.4", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz", "integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==", - "dev": true, "requires": { "regenerator-runtime": "^0.13.4" } @@ -13469,6 +13801,11 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "@breejs/later": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@breejs/later/-/later-4.0.2.tgz", + "integrity": "sha512-EN0SlbyYouBdtZis1htdsgGlwFePzkXPwdIeqaBaavxkJT1G2/bitc2LSixjv45z2njXslxlJI1mW2O/Gmrb+A==" + }, "@eslint/eslintrc": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", @@ -14170,6 +14507,11 @@ "@types/koa": "*" } }, + "@types/lodash": { + "version": "4.14.175", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.175.tgz", + "integrity": "sha512-XmdEOrKQ8a1Y/yxQFOMbC47G/V2VDO1GvMRnl4O75M4GW/abC5tnfzadQYkqEveqRM1dEJGFFegfPNA2vvx2iw==" + }, "@types/mdast": { "version": "3.0.10", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", @@ -14905,6 +15247,14 @@ "ms": "2.0.0" } }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -14912,6 +15262,11 @@ } } }, + "boolean": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.1.4.tgz", + "integrity": "sha512-3hx0kwU3uzG6ReQ3pnaFQPSktpBw6RHN3/ivDKEuU8g1XSfafowyvDnadjv1xp8IZqhtSukxlwv9bF6FhX8m0w==" + }, "bootstrap": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.1.tgz", @@ -14936,6 +15291,26 @@ "fill-range": "^7.0.1" } }, + "bree": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/bree/-/bree-6.3.1.tgz", + "integrity": "sha512-FADpEV5c+3ZuFIBothyyRUxZClJD2PetIo0lmqAFJ3ZMI9WsSmQmmstZ86Dy0G4Gyw3nPNdfYTjV7+9pPtlB8g==", + "requires": { + "@babel/runtime": "^7.12.5", + "@breejs/later": "^4.0.2", + "boolean": "^3.0.2", + "bthreads": "^0.5.1", + "combine-errors": "^3.0.3", + "cron-validate": "^1.4.1", + "debug": "^4.3.1", + "human-interval": "^2.0.0", + "is-string-and-not-blank": "^0.0.2", + "is-valid-path": "^0.1.1", + "ms": "^2.1.2", + "p-wait-for": "3.1.0", + "safe-timers": "^1.1.0" + } + }, "browser-process-hrtime": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", @@ -14964,6 +15339,14 @@ "node-int64": "^0.4.0" } }, + "bthreads": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/bthreads/-/bthreads-0.5.1.tgz", + "integrity": "sha512-nK7Jo9ll+r1FRMNPWEFRTZMQrX6HhX8JjPAofxmbTNILHqWVIJPmWzCi9JlX/K0DL5AKZTFZg2Qser5C6gVs9A==", + "requires": { + "bufio": "~1.0.5" + } + }, "buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -15008,6 +15391,11 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, + "bufio": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/bufio/-/bufio-1.0.7.tgz", + "integrity": "sha512-bd1dDQhiC+bEbEfg56IdBv7faWa6OipMs/AFFFvtFnB3wAYjlwQpQRZ0pm6ZkgtfL0pILRXhKxOiQj6UzoMR7A==" + }, "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", @@ -15090,6 +15478,11 @@ "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", "dev": true }, + "chardet": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-1.3.0.tgz", + "integrity": "sha512-cyTQGGptIjIT+CMGT5J/0l9c6Fb+565GCFjjeUTKxUO7w3oR+FcNCMEKTn5xtVKaLFmladN7QF68IiQsv5Fbdw==" + }, "chart.js": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.5.1.tgz", @@ -15101,6 +15494,11 @@ "integrity": "sha512-EnbVqTJGFKLpg1TROLdCEufrzbmIa2oeLGx8O2Wdjw2EoMudoOo9+YFu+6CM0Z0hQ/v3yq/e/Y6efQMu22n8Jg==", "requires": {} }, + "check-password-strength": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/check-password-strength/-/check-password-strength-2.0.3.tgz", + "integrity": "sha512-UW3YgMUne9QuejgnNWjWwYi4QhWArVj+1OXqDR1NkEQcmMKKO74O3P5ZvXr9JZNbTBfcwlK3yurYCMuJsck83A==" + }, "chokidar": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", @@ -15221,6 +15619,15 @@ "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==" }, + "combine-errors": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/combine-errors/-/combine-errors-3.0.3.tgz", + "integrity": "sha1-9N9nQAg+VwOjGBEQwrEFUfAD2oY=", + "requires": { + "custom-error-instance": "2.1.1", + "lodash.uniqby": "4.5.0" + } + }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -15343,6 +15750,14 @@ "yaml": "^1.10.0" } }, + "cron-validate": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cron-validate/-/cron-validate-1.4.3.tgz", + "integrity": "sha512-N+qKw019oQBEPIP5Qwi8Z5XelQ00ThN6Maahwv+9UGu2u/b/MPb35zngMQI0T8pBoNiBrIXGlhvsmspNSYae/w==", + "requires": { + "yup": "0.32.9" + } + }, "cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -15397,6 +15812,11 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.18.tgz", "integrity": "sha512-RSU6Hyeg14am3Ah4VZEmeX8H7kLwEEirXe6aU2IPfKNvhXwTflK5HQRDNI0ypQXoqmm+QPyG2IaPuQE5zMwSIQ==" }, + "custom-error-instance": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/custom-error-instance/-/custom-error-instance-2.1.1.tgz", + "integrity": "sha1-PPY5FIemYppiR+sMoM4ACBt+Nho=" + }, "cwd": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/cwd/-/cwd-0.10.0.tgz", @@ -16960,6 +17380,14 @@ "debug": "4" } }, + "human-interval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/human-interval/-/human-interval-2.0.1.tgz", + "integrity": "sha512-r4Aotzf+OtKIGQCB3odUowy4GfUDTy3aTWTfLd7ZF2gBCy3XW3v/dJLRefZnOFFnjqs5B1TypvS8WarpBkYUNQ==", + "requires": { + "numbered": "^1.1.0" + } + }, "human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -16967,11 +17395,11 @@ "dev": true }, "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "ieee754": { @@ -17154,6 +17582,29 @@ "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", "dev": true }, + "is-invalid-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-invalid-path/-/is-invalid-path-0.1.0.tgz", + "integrity": "sha1-MHqFWzzxqTi0TqcNLGEQYFNxTzQ=", + "requires": { + "is-glob": "^2.0.0" + }, + "dependencies": { + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "requires": { + "is-extglob": "^1.0.0" + } + } + } + }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -17193,6 +17644,19 @@ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true }, + "is-string-and-not-blank": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/is-string-and-not-blank/-/is-string-and-not-blank-0.0.2.tgz", + "integrity": "sha512-FyPGAbNVyZpTeDCTXnzuwbu9/WpNXbCfbHXLpCRpN4GANhS00eEIP5Ef+k5HYSNIzIhdN9zRDoBj6unscECvtQ==", + "requires": { + "is-string-blank": "^1.0.1" + } + }, + "is-string-blank": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-string-blank/-/is-string-blank-1.0.1.tgz", + "integrity": "sha512-9H+ZBCVs3L9OYqv8nuUAzpcT9OTgMD1yAWrG7ihlnibdkbtB850heAmYWxHuXc4CHy4lKeK69tN+ny1K7gBIrw==" + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -17205,6 +17669,14 @@ "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true }, + "is-valid-path": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-valid-path/-/is-valid-path-0.1.1.tgz", + "integrity": "sha1-EQ+f90w39mPh7HkV60UfLbk6yd8=", + "requires": { + "is-invalid-path": "^0.1.0" + } + }, "is-windows": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", @@ -17999,6 +18471,11 @@ "verror": "1.10.0" } }, + "just-performance": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/just-performance/-/just-performance-4.3.0.tgz", + "integrity": "sha512-L7RjvtJsL0QO8xFs5wEoDDzzJwoiowRw6Rn/GnvldlchS2JQr9wFYPiwZcDfrbbujEKqKN0tvENdbjXdYhDp5Q==" + }, "jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", @@ -18093,6 +18570,14 @@ "type-check": "~0.4.0" } }, + "limiter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-2.1.0.tgz", + "integrity": "sha512-361TYz6iay6n+9KvUUImqdLuFigK+K79qrUtBsXhJTLdH4rIt/r1y8r1iozwh8KbZNpujbFTSh74mJ7bwbAMOw==", + "requires": { + "just-performance": "4.3.0" + } + }, "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -18113,6 +18598,51 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, + "lodash._baseiteratee": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash._baseiteratee/-/lodash._baseiteratee-4.7.0.tgz", + "integrity": "sha1-NKm1VDVycnw9sueO2uPA6eZr0QI=", + "requires": { + "lodash._stringtopath": "~4.8.0" + } + }, + "lodash._basetostring": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-4.12.0.tgz", + "integrity": "sha1-kyfJ3FFYhmt/pLnUL0Y45XZt2d8=" + }, + "lodash._baseuniq": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz", + "integrity": "sha1-DrtE5FaBSveQXGIS+iybLVG4Qeg=", + "requires": { + "lodash._createset": "~4.0.0", + "lodash._root": "~3.0.0" + } + }, + "lodash._createset": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/lodash._createset/-/lodash._createset-4.0.3.tgz", + "integrity": "sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY=" + }, + "lodash._root": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", + "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=" + }, + "lodash._stringtopath": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/lodash._stringtopath/-/lodash._stringtopath-4.8.0.tgz", + "integrity": "sha1-lBvPDmQmbl/B1m/tCmlZVExXaCQ=", + "requires": { + "lodash._basetostring": "~4.12.0" + } + }, "lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", @@ -18177,6 +18707,15 @@ "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", "dev": true }, + "lodash.uniqby": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.5.0.tgz", + "integrity": "sha1-o6F7v2LutiQPSRhG6XwcTipeHiE=", + "requires": { + "lodash._baseiteratee": "~4.7.0", + "lodash._baseuniq": "~4.6.0" + } + }, "log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -18470,6 +19009,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "nanoclone": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz", + "integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==" + }, "nanoid": { "version": "3.1.28", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.28.tgz", @@ -18657,6 +19201,11 @@ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, + "numbered": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/numbered/-/numbered-1.1.0.tgz", + "integrity": "sha512-pv/ue2Odr7IfYOO0byC1KgBI10wo5YDauLhxY6/saNzAdAs0r1SotGCPzzCLNPL0xtrAwWRialLu23AAu9xO1g==" + }, "nwsapi": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", @@ -18737,6 +19286,11 @@ "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -18754,11 +19308,27 @@ "p-limit": "^2.2.0" } }, + "p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "requires": { + "p-finally": "^1.0.0" + } + }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, + "p-wait-for": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-wait-for/-/p-wait-for-3.1.0.tgz", + "integrity": "sha512-0Uy19uhxbssHelu9ynDMcON6BmMk6pH8551CvxROhiz3Vx+yC4RqxjyIDk2V4ll0g9177RKT++PK4zcV58uJ7A==", + "requires": { + "p-timeout": "^3.0.0" + } + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -19124,6 +19694,11 @@ "sisteransi": "^1.0.5" } }, + "property-expr": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.4.tgz", + "integrity": "sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg==" + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -19393,6 +19968,16 @@ "http-errors": "1.7.2", "iconv-lite": "0.4.24", "unpipe": "1.0.0" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } } }, "react-is": { @@ -19550,8 +20135,7 @@ "regenerator-runtime": { "version": "0.13.9", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", - "dev": true + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" }, "regenerator-transform": { "version": "0.14.5", @@ -19856,6 +20440,11 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "safe-timers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-timers/-/safe-timers-1.1.0.tgz", + "integrity": "sha1-xYroMl2407BnMi8KTvOgytZ6rYM=" + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -20717,6 +21306,11 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, + "toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=" + }, "tough-cookie": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", @@ -21251,6 +21845,17 @@ "dev": true, "requires": { "iconv-lite": "0.4.24" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } } }, "whatwg-mimetype": { @@ -21446,6 +22051,20 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" }, + "yup": { + "version": "0.32.9", + "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.9.tgz", + "integrity": "sha512-Ci1qN+i2H0XpY7syDQ0k5zKQ/DoxO0LzPg8PAR/X4Mpj6DqaeCoIYEEjDJwhArh3Fa7GWbQQVDZKeXYlSH4JMg==", + "requires": { + "@babel/runtime": "^7.10.5", + "@types/lodash": "^4.14.165", + "lodash": "^4.17.20", + "lodash-es": "^4.17.15", + "nanoclone": "^0.2.1", + "property-expr": "^2.0.4", + "toposort": "^2.0.2" + } + }, "zwitch": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", diff --git a/package.json b/package.json index 03112518a..4f3d32ea7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uptime-kuma", - "version": "1.8.0", + "version": "1.9.1", "license": "MIT", "repository": { "type": "git", @@ -30,13 +30,13 @@ "build-docker": "npm run build-docker-debian && npm run build-docker-alpine", "build-docker-alpine-base": "docker buildx build -f docker/alpine-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base-alpine . --push", "build-docker-debian-base": "docker buildx build -f docker/debian-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base-debian . --push", - "build-docker-alpine": "docker buildx build -f docker/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.8.0-alpine --target release . --push", - "build-docker-debian": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.8.0 -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:1.8.0-debian --target release . --push", + "build-docker-alpine": "docker buildx build -f docker/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.9.1-alpine --target release . --push", + "build-docker-debian": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.9.1 -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:1.9.1-debian --target release . --push", "build-docker-nightly": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push", - "build-docker-nightly-alpine": "docker buildx build -f dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly-alpine --target nightly . --push", + "build-docker-nightly-alpine": "docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly-alpine --target nightly . --push", "build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain", - "upload-artifacts": "docker buildx build --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain", - "setup": "git checkout 1.8.0 && npm ci --production && npm run download-dist", + "upload-artifacts": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain", + "setup": "git checkout 1.9.1 && npm ci --production && npm run download-dist", "download-dist": "node extra/download-dist.js", "update-version": "node extra/update-version.js", "mark-as-nightly": "node extra/mark-as-nightly.js", @@ -60,11 +60,13 @@ "@popperjs/core": "~2.10.2", "args-parser": "~1.3.0", "axios": "~0.21.4", - "babel-plugin-rewire": "~1.2.0", "bcryptjs": "~2.4.3", "bootstrap": "~5.1.1", + "bree": "~6.3.1", + "chardet": "^1.3.0", "chart.js": "~3.5.1", "chartjs-adapter-dayjs": "~1.0.0", + "check-password-strength": "^2.0.3", "command-exists": "~1.2.9", "compare-versions": "~3.6.0", "dayjs": "~1.10.7", @@ -72,7 +74,9 @@ "express-basic-auth": "~1.2.0", "form-data": "~4.0.0", "http-graceful-shutdown": "~3.1.4", + "iconv-lite": "^0.6.3", "jsonwebtoken": "~8.5.1", + "limiter": "^2.1.0", "nodemailer": "~6.6.5", "notp": "~2.0.3", "password-hash": "~1.2.2", @@ -108,6 +112,7 @@ "@vitejs/plugin-legacy": "~1.6.1", "@vitejs/plugin-vue": "~1.9.2", "@vue/compiler-sfc": "~3.2.19", + "babel-plugin-rewire": "~1.2.0", "core-js": "~3.18.1", "cross-env": "~7.0.3", "dns2": "~2.0.1", diff --git a/server/config.js b/server/config.js new file mode 100644 index 000000000..24ccfaa14 --- /dev/null +++ b/server/config.js @@ -0,0 +1,7 @@ +const args = require("args-parser")(process.argv); +const demoMode = args["demo"] || false; + +module.exports = { + args, + demoMode +}; diff --git a/server/database.js b/server/database.js index 47eca2835..aa3638464 100644 --- a/server/database.js +++ b/server/database.js @@ -49,10 +49,11 @@ class Database { "patch-incident-table.sql": true, "patch-group-table.sql": true, "patch-monitor-push_token.sql": true, + "patch-http-monitor-method-body-and-headers.sql": true, } /** - * The finally version should be 10 after merged tag feature + * The final version should be 10 after merged tag feature * @deprecated Use patchList for any new feature */ static latestVersion = 10; @@ -130,7 +131,7 @@ class Database { console.info("Latest database version: " + this.latestVersion); if (version === this.latestVersion) { - console.info("Database no need to patch"); + console.info("Database patch not needed"); } else if (version > this.latestVersion) { console.info("Warning: Database version is newer than expected"); } else { @@ -151,8 +152,8 @@ class Database { await Database.close(); console.error(ex); - console.error("Start Uptime-Kuma failed due to patch db failed"); - console.error("Please submit the bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues"); + console.error("Start Uptime-Kuma failed due to issue patching the database"); + console.error("Please submit a bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues"); this.restore(); process.exit(1); @@ -190,7 +191,7 @@ class Database { await Database.close(); console.error(ex); - console.error("Start Uptime-Kuma failed due to patch db failed"); + console.error("Start Uptime-Kuma failed due to issue patching the database"); console.error("Please submit the bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues"); this.restore(); @@ -231,7 +232,7 @@ class Database { this.patched = true; await this.importSQLFile("./db/" + sqlFilename); databasePatchedFiles[sqlFilename] = true; - console.log(sqlFilename + " is patched successfully"); + console.log(sqlFilename + " was patched successfully"); } else { debug(sqlFilename + " is already patched, skip"); @@ -286,7 +287,7 @@ class Database { }; process.addListener("unhandledRejection", listener); - console.log("Closing DB"); + console.log("Closing the database"); while (true) { Database.noReject = true; @@ -296,7 +297,7 @@ class Database { if (Database.noReject) { break; } else { - console.log("Waiting to close the db"); + console.log("Waiting to close the database"); } } console.log("SQLite closed"); @@ -311,7 +312,7 @@ class Database { */ static backup(version) { if (! this.backupPath) { - console.info("Backup the db"); + console.info("Backing up the database"); this.backupPath = this.dataDir + "kuma.db.bak" + version; fs.copyFileSync(Database.path, this.backupPath); @@ -334,7 +335,7 @@ class Database { */ static restore() { if (this.backupPath) { - console.error("Patch db failed!!! Restoring the backup"); + console.error("Patching the database failed!!! Restoring the backup"); const shmPath = Database.path + "-shm"; const walPath = Database.path + "-wal"; @@ -353,7 +354,7 @@ class Database { fs.unlinkSync(walPath); } } catch (e) { - console.log("Restore failed, you may need to restore the backup manually"); + console.log("Restore failed; you may need to restore the backup manually"); process.exit(1); } diff --git a/server/jobs.js b/server/jobs.js new file mode 100644 index 000000000..8a768b91b --- /dev/null +++ b/server/jobs.js @@ -0,0 +1,31 @@ +const path = require("path"); +const Bree = require("bree"); +const { SHARE_ENV } = require("worker_threads"); + +const jobs = [ + { + name: "clear-old-data", + interval: "at 03:14", + } +]; + +const initBackgroundJobs = function (args) { + const bree = new Bree({ + root: path.resolve("server", "jobs"), + jobs, + worker: { + env: SHARE_ENV, + workerData: args, + }, + workerMessageHandler: (message) => { + console.log("[Background Job]:", message); + } + }); + + bree.start(); + return bree; +}; + +module.exports = { + initBackgroundJobs +}; diff --git a/server/jobs/clear-old-data.js b/server/jobs/clear-old-data.js new file mode 100644 index 000000000..7c368014d --- /dev/null +++ b/server/jobs/clear-old-data.js @@ -0,0 +1,40 @@ +const { log, exit, connectDb } = require("./util-worker"); +const { R } = require("redbean-node"); +const { setSetting, setting } = require("../util-server"); + +const DEFAULT_KEEP_PERIOD = 180; + +(async () => { + await connectDb(); + + let period = await setting("keepDataPeriodDays"); + + // Set Default Period + if (period == null) { + await setSetting("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general"); + period = DEFAULT_KEEP_PERIOD; + } + + // Try parse setting + let parsedPeriod; + try { + parsedPeriod = parseInt(period); + } catch (_) { + log("Failed to parse setting, resetting to default.."); + await setSetting("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general"); + parsedPeriod = DEFAULT_KEEP_PERIOD; + } + + log(`Clearing Data older than ${parsedPeriod} days...`); + + try { + await R.exec( + "DELETE FROM heartbeat WHERE time < DATETIME('now', '-' || ? || ' days') ", + [parsedPeriod] + ); + } catch (e) { + log(`Failed to clear old data: ${e.message}`); + } + + exit(); +})(); diff --git a/server/jobs/util-worker.js b/server/jobs/util-worker.js new file mode 100644 index 000000000..9426840d7 --- /dev/null +++ b/server/jobs/util-worker.js @@ -0,0 +1,39 @@ +const { parentPort, workerData } = require("worker_threads"); +const Database = require("../database"); +const path = require("path"); + +const log = function (any) { + if (parentPort) { + parentPort.postMessage(any); + } +}; + +const exit = function (error) { + if (error && error != 0) { + process.exit(error); + } else { + if (parentPort) { + parentPort.postMessage("done"); + } else { + process.exit(0); + } + } +}; + +const connectDb = async function () { + const dbPath = path.join( + process.env.DATA_DIR || workerData["data-dir"] || "./data/" + ); + + Database.init({ + "data-dir": dbPath, + }); + + await Database.connect(); +}; + +module.exports = { + log, + exit, + connectDb, +}; diff --git a/server/model/monitor.js b/server/model/monitor.js index 161815277..fb736291c 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -7,11 +7,11 @@ dayjs.extend(timezone); const axios = require("axios"); const { Prometheus } = require("../prometheus"); const { debug, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util"); -const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom } = require("../util-server"); +const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting } = require("../util-server"); const { R } = require("redbean-node"); const { BeanModel } = require("redbean-node/dist/bean-model"); const { Notification } = require("../notification"); -const { demoMode } = require("../server"); +const { demoMode } = require("../config"); const version = require("../../package.json").version; const apicache = require("../modules/apicache"); @@ -55,6 +55,9 @@ class Monitor extends BeanModel { id: this.id, name: this.name, url: this.url, + method: this.method, + body: this.body, + headers: this.headers, hostname: this.hostname, port: this.port, maxretries: this.maxretries, @@ -138,11 +141,15 @@ class Monitor extends BeanModel { // Do not do any queries/high loading things before the "bean.ping" let startTime = dayjs().valueOf(); - let res = await axios.get(this.url, { + const options = { + url: this.url, + method: (this.method || "get").toLowerCase(), + ...(this.body ? { data: JSON.parse(this.body) } : {}), timeout: this.interval * 1000 * 0.8, headers: { "Accept": "*/*", "User-Agent": "Uptime-Kuma/" + version, + ...(this.headers ? JSON.parse(this.headers) : {}), }, httpsAgent: new https.Agent({ maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940) @@ -152,7 +159,8 @@ class Monitor extends BeanModel { validateStatus: (status) => { return checkStatusCode(status, this.getAcceptedStatuscodes()); }, - }); + }; + let res = await axios.request(options); bean.msg = `${res.status} - ${res.statusText}`; bean.ping = dayjs().valueOf() - startTime; @@ -172,6 +180,10 @@ class Monitor extends BeanModel { debug("Cert Info Query Time: " + (dayjs().valueOf() - certInfoStartTime) + "ms"); } + if (process.env.UPTIME_KUMA_LOG_RESPONSE_BODY_MONITOR_ID == this.id) { + console.log(res.data); + } + if (this.type === "http") { bean.status = UP; } else { @@ -260,6 +272,46 @@ class Monitor extends BeanModel { return; } + } else if (this.type === "steam") { + const steamApiUrl = "https://api.steampowered.com/IGameServersService/GetServerList/v1/"; + const steamAPIKey = await setting("steamAPIKey"); + const filter = `addr\\${this.hostname}:${this.port}`; + + if (!steamAPIKey) { + throw new Error("Steam API Key not found"); + } + + let res = await axios.get(steamApiUrl, { + timeout: this.interval * 1000 * 0.8, + headers: { + "Accept": "*/*", + "User-Agent": "Uptime-Kuma/" + version, + }, + httpsAgent: new https.Agent({ + maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940) + rejectUnauthorized: ! this.getIgnoreTls(), + }), + maxRedirects: this.maxredirects, + validateStatus: (status) => { + return checkStatusCode(status, this.getAcceptedStatuscodes()); + }, + params: { + filter: filter, + key: steamAPIKey, + } + }); + + if (res.data.response && res.data.response.servers && res.data.response.servers.length > 0) { + bean.status = UP; + bean.msg = res.data.response.servers[0].name; + + try { + bean.ping = await ping(this.hostname); + } catch (_) { } + } else { + throw new Error("Server not found on Steam"); + } + } else { bean.msg = "Unknown Monitor Type"; bean.status = PENDING; @@ -292,54 +344,13 @@ class Monitor extends BeanModel { let beatInterval = this.interval; - // * ? -> ANY STATUS = important [isFirstBeat] - // UP -> PENDING = not important - // * UP -> DOWN = important - // UP -> UP = not important - // PENDING -> PENDING = not important - // * PENDING -> DOWN = important - // PENDING -> UP = not important - // DOWN -> PENDING = this case not exists - // DOWN -> DOWN = not important - // * DOWN -> UP = important - let isImportant = isFirstBeat || - (previousBeat.status === UP && bean.status === DOWN) || - (previousBeat.status === DOWN && bean.status === UP) || - (previousBeat.status === PENDING && bean.status === DOWN); + let isImportant = Monitor.isImportantBeat(isFirstBeat, previousBeat?.status, bean.status); // Mark as important if status changed, ignore pending pings, // Don't notify if disrupted changes to up if (isImportant) { bean.important = true; - - // Send only if the first beat is DOWN - if (!isFirstBeat || bean.status === DOWN) { - let notificationList = await R.getAll("SELECT notification.* FROM notification, monitor_notification WHERE monitor_id = ? AND monitor_notification.notification_id = notification.id ", [ - this.id, - ]); - - let text; - if (bean.status === UP) { - text = "✅ Up"; - } else { - text = "🔴 Down"; - } - - let msg = `[${this.name}] [${text}] ${bean.msg}`; - - for (let notification of notificationList) { - try { - await Notification.send(JSON.parse(notification.config), msg, await this.toJSON(), bean.toJSON()); - } catch (e) { - console.error("Cannot send notification to " + notification.name); - console.log(e); - } - } - - // Clear Status Page Cache - apicache.clear(); - } - + await Monitor.sendNotification(isFirstBeat, this, bean); } else { bean.important = false; } @@ -546,6 +557,53 @@ class Monitor extends BeanModel { io.to(userID).emit("uptime", monitorID, duration, uptime); } + static isImportantBeat(isFirstBeat, previousBeatStatus, currentBeatStatus) { + // * ? -> ANY STATUS = important [isFirstBeat] + // UP -> PENDING = not important + // * UP -> DOWN = important + // UP -> UP = not important + // PENDING -> PENDING = not important + // * PENDING -> DOWN = important + // PENDING -> UP = not important + // DOWN -> PENDING = this case not exists + // DOWN -> DOWN = not important + // * DOWN -> UP = important + let isImportant = isFirstBeat || + (previousBeatStatus === UP && currentBeatStatus === DOWN) || + (previousBeatStatus === DOWN && currentBeatStatus === UP) || + (previousBeatStatus === PENDING && currentBeatStatus === DOWN); + return isImportant; + } + + static async sendNotification(isFirstBeat, monitor, bean) { + if (!isFirstBeat || bean.status === DOWN) { + let notificationList = await R.getAll("SELECT notification.* FROM notification, monitor_notification WHERE monitor_id = ? AND monitor_notification.notification_id = notification.id ", [ + monitor.id, + ]); + + let text; + if (bean.status === UP) { + text = "✅ Up"; + } else { + text = "🔴 Down"; + } + + let msg = `[${monitor.name}] [${text}] ${bean.msg}`; + + for (let notification of notificationList) { + try { + await Notification.send(JSON.parse(notification.config), msg, await monitor.toJSON(), bean.toJSON()); + } catch (e) { + console.error("Cannot send notification to " + notification.name); + console.log(e); + } + } + + // Clear Status Page Cache + apicache.clear(); + } + } + } module.exports = Monitor; diff --git a/server/notification-providers/aliyun-sms.js b/server/notification-providers/aliyun-sms.js new file mode 100644 index 000000000..6a2063200 --- /dev/null +++ b/server/notification-providers/aliyun-sms.js @@ -0,0 +1,108 @@ +const NotificationProvider = require("./notification-provider"); +const { DOWN, UP } = require("../../src/util"); +const { default: axios } = require("axios"); +const Crypto = require("crypto"); +const qs = require("qs"); + +class AliyunSMS extends NotificationProvider { + name = "AliyunSMS"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully."; + + try { + if (heartbeatJSON != null) { + let msgBody = JSON.stringify({ + name: monitorJSON["name"], + time: heartbeatJSON["time"], + status: this.statusToString(heartbeatJSON["status"]), + msg: heartbeatJSON["msg"], + }); + if (this.sendSms(notification, msgBody)) { + return okMsg; + } + } else { + let msgBody = JSON.stringify({ + name: "", + time: "", + status: "", + msg: msg, + }); + if (this.sendSms(notification, msgBody)) { + return okMsg; + } + } + } catch (error) { + this.throwGeneralAxiosError(error); + } + } + + async sendSms(notification, msgbody) { + let params = { + PhoneNumbers: notification.phonenumber, + TemplateCode: notification.templateCode, + SignName: notification.signName, + TemplateParam: msgbody, + AccessKeyId: notification.accessKeyId, + Format: "JSON", + SignatureMethod: "HMAC-SHA1", + SignatureVersion: "1.0", + SignatureNonce: Math.random().toString(), + Timestamp: new Date().toISOString(), + Action: "SendSms", + Version: "2017-05-25", + }; + + params.Signature = this.sign(params, notification.secretAccessKey); + let config = { + method: "POST", + url: "http://dysmsapi.aliyuncs.com/", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + data: qs.stringify(params), + }; + + let result = await axios(config); + if (result.data.Message == "OK") { + return true; + } + return false; + } + + /** Aliyun request sign */ + sign(param, AccessKeySecret) { + let param2 = {}; + let data = []; + + let oa = Object.keys(param).sort(); + + for (let i = 0; i < oa.length; i++) { + let key = oa[i]; + param2[key] = param[key]; + } + + for (let key in param2) { + data.push(`${encodeURIComponent(key)}=${encodeURIComponent(param2[key])}`); + } + + let StringToSign = `POST&${encodeURIComponent("/")}&${encodeURIComponent(data.join("&"))}`; + return Crypto + .createHmac("sha1", `${AccessKeySecret}&`) + .update(Buffer.from(StringToSign)) + .digest("base64"); + } + + statusToString(status) { + switch (status) { + case DOWN: + return "DOWN"; + case UP: + return "UP"; + default: + return status; + } + } +} + +module.exports = AliyunSMS; diff --git a/server/notification-providers/dingding.js b/server/notification-providers/dingding.js new file mode 100644 index 000000000..f099192d8 --- /dev/null +++ b/server/notification-providers/dingding.js @@ -0,0 +1,79 @@ +const NotificationProvider = require("./notification-provider"); +const { DOWN, UP } = require("../../src/util"); +const { default: axios } = require("axios"); +const Crypto = require("crypto"); + +class DingDing extends NotificationProvider { + name = "DingDing"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully."; + + try { + if (heartbeatJSON != null) { + let params = { + msgtype: "markdown", + markdown: { + title: monitorJSON["name"], + text: `## [${this.statusToString(heartbeatJSON["status"])}] \n > ${heartbeatJSON["msg"]} \n > Time(UTC):${heartbeatJSON["time"]}`, + } + }; + if (this.sendToDingDing(notification, params)) { + return okMsg; + } + } else { + let params = { + msgtype: "text", + text: { + content: msg + } + }; + if (this.sendToDingDing(notification, params)) { + return okMsg; + } + } + } catch (error) { + this.throwGeneralAxiosError(error); + } + } + + async sendToDingDing(notification, params) { + let timestamp = Date.now(); + + let config = { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + url: `${notification.webHookUrl}×tamp=${timestamp}&sign=${encodeURIComponent(this.sign(timestamp, notification.secretKey))}`, + data: JSON.stringify(params), + }; + + let result = await axios(config); + if (result.data.errmsg == "ok") { + return true; + } + return false; + } + + /** DingDing sign */ + sign(timestamp, secretKey) { + return Crypto + .createHmac("sha256", Buffer.from(secretKey, "utf8")) + .update(Buffer.from(`${timestamp}\n${secretKey}`, "utf8")) + .digest("base64"); + } + + statusToString(status) { + switch (status) { + case DOWN: + return "DOWN"; + case UP: + return "UP"; + default: + return status; + } + } +} + +module.exports = DingDing; diff --git a/server/notification-providers/slack.js b/server/notification-providers/slack.js index 5132ba977..b4dad6fe3 100644 --- a/server/notification-providers/slack.js +++ b/server/notification-providers/slack.js @@ -39,8 +39,9 @@ class Slack extends NotificationProvider { } const time = heartbeatJSON["time"]; + const textMsg = "Uptime Kuma Alert"; let data = { - "text": "Uptime Kuma Alert", + "text": monitorJSON ? textMsg + `: ${monitorJSON.name}` : textMsg, "channel": notification.slackchannel, "username": notification.slackusername, "icon_emoji": notification.slackiconemo, diff --git a/server/notification-providers/smtp.js b/server/notification-providers/smtp.js index ecb583eb7..60068eb77 100644 --- a/server/notification-providers/smtp.js +++ b/server/notification-providers/smtp.js @@ -1,5 +1,6 @@ const nodemailer = require("nodemailer"); const NotificationProvider = require("./notification-provider"); +const { DOWN, UP } = require("../../src/util"); class SMTP extends NotificationProvider { @@ -20,6 +21,56 @@ class SMTP extends NotificationProvider { pass: notification.smtpPassword, }; } + // Lets start with default subject and empty string for custom one + let subject = msg; + + // Change the subject if: + // - The msg ends with "Testing" or + // - Actual Up/Down Notification + if ((monitorJSON && heartbeatJSON) || msg.endsWith("Testing")) { + let customSubject = ""; + + // Our subject cannot end with whitespace it's often raise spam score + // Once I got "Cannot read property 'trim' of undefined", better be safe than sorry + if (notification.customSubject) { + customSubject = notification.customSubject.trim(); + } + + // If custom subject is not empty, change subject for notification + if (customSubject !== "") { + + // Replace "MACROS" with corresponding variable + let replaceName = new RegExp("{{NAME}}", "g"); + let replaceHostnameOrURL = new RegExp("{{HOSTNAME_OR_URL}}", "g"); + let replaceStatus = new RegExp("{{STATUS}}", "g"); + + // Lets start with dummy values to simplify code + let monitorName = "Test"; + let monitorHostnameOrURL = "testing.hostname"; + let serviceStatus = "⚠️ Test"; + + if (monitorJSON !== null) { + monitorName = monitorJSON["name"]; + + if (monitorJSON["type"] === "http" || monitorJSON["type"] === "keyword") { + monitorHostnameOrURL = monitorJSON["url"]; + } else { + monitorHostnameOrURL = monitorJSON["hostname"]; + } + } + + if (heartbeatJSON !== null) { + serviceStatus = (heartbeatJSON["status"] === DOWN) ? "🔴 Down" : "✅ Up"; + } + + // Break replace to one by line for better readability + customSubject = customSubject.replace(replaceStatus, serviceStatus); + customSubject = customSubject.replace(replaceName, monitorName); + customSubject = customSubject.replace(replaceHostnameOrURL, monitorHostnameOrURL); + + subject = customSubject; + } + } let transporter = nodemailer.createTransport(config); @@ -34,7 +85,7 @@ class SMTP extends NotificationProvider { cc: notification.smtpCC, bcc: notification.smtpBCC, to: notification.smtpTo, - subject: msg, + subject: subject, text: bodyTextContent, tls: { rejectUnauthorized: notification.smtpIgnoreTLSError || false, diff --git a/server/notification.js b/server/notification.js index 41a0063c3..658216f91 100644 --- a/server/notification.js +++ b/server/notification.js @@ -19,6 +19,8 @@ const Teams = require("./notification-providers/teams"); const Telegram = require("./notification-providers/telegram"); const Webhook = require("./notification-providers/webhook"); const Feishu = require("./notification-providers/feishu"); +const AliyunSms = require("./notification-providers/aliyun-sms"); +const DingDing = require("./notification-providers/dingding"); class Notification { @@ -31,6 +33,8 @@ class Notification { const list = [ new Apprise(), + new AliyunSms(), + new DingDing(), new Discord(), new Teams(), new Gotify(), diff --git a/server/ping-lite.js b/server/ping-lite.js index 0af0e9706..b2d6405ad 100644 --- a/server/ping-lite.js +++ b/server/ping-lite.js @@ -4,10 +4,7 @@ const net = require("net"); const spawn = require("child_process").spawn; const events = require("events"); const fs = require("fs"); -const WIN = /^win/.test(process.platform); -const LIN = /^linux/.test(process.platform); -const MAC = /^darwin/.test(process.platform); -const FBSD = /^freebsd/.test(process.platform); +const util = require("./util-server"); module.exports = Ping; @@ -23,12 +20,12 @@ function Ping(host, options) { const timeout = 10; - if (WIN) { + if (util.WIN) { this._bin = "c:/windows/system32/ping.exe"; this._args = (options.args) ? options.args : [ "-n", "1", "-w", timeout * 1000, host ]; this._regmatch = /[><=]([0-9.]+?)ms/; - } else if (LIN) { + } else if (util.LIN) { this._bin = "/bin/ping"; const defaultArgs = [ "-n", "-w", timeout, "-c", "1", host ]; @@ -40,7 +37,7 @@ function Ping(host, options) { this._args = (options.args) ? options.args : defaultArgs; this._regmatch = /=([0-9.]+?) ms/; - } else if (MAC) { + } else if (util.MAC) { if (net.isIPv6(host) || options.ipv6) { this._bin = "/sbin/ping6"; @@ -51,7 +48,7 @@ function Ping(host, options) { this._args = (options.args) ? options.args : [ "-n", "-t", timeout, "-c", "1", host ]; this._regmatch = /=([0-9.]+?) ms/; - } else if (FBSD) { + } else if (util.FBSD) { this._bin = "/sbin/ping"; const defaultArgs = [ "-n", "-t", timeout, "-c", "1", host ]; @@ -101,6 +98,9 @@ Ping.prototype.send = function (callback) { }); this._ping.stdout.on("data", function (data) { // log stdout + if (util.WIN) { + data = convertOutput(data); + } this._stdout = (this._stdout || "") + data; }); @@ -112,6 +112,9 @@ Ping.prototype.send = function (callback) { }); this._ping.stderr.on("data", function (data) { // log stderr + if (util.WIN) { + data = convertOutput(data); + } this._stderr = (this._stderr || "") + data; }); @@ -157,3 +160,19 @@ Ping.prototype.start = function (callback) { Ping.prototype.stop = function () { clearInterval(this._i); }; + +/** + * Try to convert to UTF-8 for Windows, as the ping's output on Windows is not UTF-8 and could be in other languages + * Thank @pemassi + * https://github.com/louislam/uptime-kuma/issues/570#issuecomment-941984094 + * @param data + * @returns {string} + */ +function convertOutput(data) { + if (util.WIN) { + if (data) { + return util.convertToUTF8(data); + } + } + return data; +} diff --git a/server/routers/api-router.js b/server/routers/api-router.js index 0da1fd705..fbe8136e5 100644 --- a/server/routers/api-router.js +++ b/server/routers/api-router.js @@ -5,7 +5,7 @@ const server = require("../server"); const apicache = require("../modules/apicache"); const Monitor = require("../model/monitor"); const dayjs = require("dayjs"); -const { UP } = require("../../src/util"); +const { UP, flipStatus, debug } = require("../../src/util"); let router = express.Router(); let cache = apicache.middleware; @@ -18,9 +18,10 @@ router.get("/api/entry-page", async (_, response) => { router.get("/api/push/:pushToken", async (request, response) => { try { + let pushToken = request.params.pushToken; let msg = request.query.msg || "OK"; - let ping = request.query.ping; + let ping = request.query.ping || null; let monitor = await R.findOne("monitor", " push_token = ? AND active = 1 ", [ pushToken @@ -30,12 +31,40 @@ router.get("/api/push/:pushToken", async (request, response) => { throw new Error("Monitor not found or not active."); } + const previousHeartbeat = await R.getRow(` + SELECT status, time FROM heartbeat + WHERE id = (select MAX(id) from heartbeat where monitor_id = ?) + `, [ + monitor.id + ]); + + let status = UP; + if (monitor.isUpsideDown()) { + status = flipStatus(status); + } + + let isFirstBeat = true; + let previousStatus = status; + let duration = 0; + let bean = R.dispense("heartbeat"); - bean.monitor_id = monitor.id; bean.time = R.isoDateTime(dayjs.utc()); - bean.status = UP; + + if (previousHeartbeat) { + isFirstBeat = false; + previousStatus = previousHeartbeat.status; + duration = dayjs(bean.time).diff(dayjs(previousHeartbeat.time), "second"); + } + + debug("PreviousStatus: " + previousStatus); + debug("Current Status: " + status); + + bean.important = Monitor.isImportantBeat(isFirstBeat, previousStatus, status); + bean.monitor_id = monitor.id; + bean.status = status; bean.msg = msg; bean.ping = ping; + bean.duration = duration; await R.store(bean); @@ -45,6 +74,11 @@ router.get("/api/push/:pushToken", async (request, response) => { response.json({ ok: true, }); + + if (bean.important) { + await Monitor.sendNotification(isFirstBeat, monitor, bean); + } + } catch (e) { response.json({ ok: false, diff --git a/server/server.js b/server/server.js index 67095ff53..11f03061a 100644 --- a/server/server.js +++ b/server/server.js @@ -1,6 +1,7 @@ console.log("Welcome to Uptime Kuma"); const args = require("args-parser")(process.argv); const { sleep, debug, getRandomInt, genSecret } = require("../src/util"); +const config = require("./config"); debug(args); @@ -8,10 +9,6 @@ if (! process.env.NODE_ENV) { process.env.NODE_ENV = "production"; } -// Demo Mode? -const demoMode = args["demo"] || false; -exports.demoMode = demoMode; - console.log("Node Env: " + process.env.NODE_ENV); console.log("Importing Node libraries"); @@ -34,6 +31,7 @@ debug("Importing prometheus-api-metrics"); const prometheusAPIMetrics = require("prometheus-api-metrics"); debug("Importing compare-versions"); const compareVersions = require("compare-versions"); +const { passwordStrength } = require("check-password-strength"); debug("Importing 2FA Modules"); const notp = require("notp"); @@ -43,7 +41,7 @@ console.log("Importing this project modules"); debug("Importing Monitor"); const Monitor = require("./model/monitor"); debug("Importing Settings"); -const { getSettings, setSettings, setting, initJWTSecret, checkLogin, startUnitTest } = require("./util-server"); +const { getSettings, setSettings, setting, initJWTSecret, checkLogin, startUnitTest, FBSD } = require("./util-server"); debug("Importing Notification"); const { Notification } = require("./notification"); @@ -52,6 +50,9 @@ Notification.init(); debug("Importing Database"); const Database = require("./database"); +debug("Importing Background Jobs"); +const { initBackgroundJobs } = require("./jobs"); + const { basicAuth } = require("./auth"); const { login } = require("./auth"); const passwordHash = require("./password-hash"); @@ -61,12 +62,29 @@ console.info("Version: " + checkVersion.version); // If host is omitted, the server will accept connections on the unspecified IPv6 address (::) when IPv6 is available and the unspecified IPv4 address (0.0.0.0) otherwise. // Dual-stack support for (::) -const hostname = process.env.HOST || args.host; -const port = parseInt(process.env.PORT || args.port || 3001); +let hostname = process.env.UPTIME_KUMA_HOST || args.host; + +// Also read HOST if not FreeBSD, as HOST is a system environment variable in FreeBSD +if (!hostname && !FBSD) { + hostname = process.env.HOST; +} + +if (hostname) { + console.log("Custom hostname: " + hostname); +} + +const port = parseInt(process.env.UPTIME_KUMA_PORT || 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; +const sslKey = process.env.UPTIME_KUMA_SSL_KEY || process.env.SSL_KEY || args["ssl-key"] || undefined; +const sslCert = process.env.UPTIME_KUMA_SSL_CERT || process.env.SSL_CERT || args["ssl-cert"] || undefined; +const disableFrameSameOrigin = !!process.env.UPTIME_KUMA_DISABLE_FRAME_SAMEORIGIN || args["disable-frame-sameorigin"] || false; + +// 2FA / notp verification defaults +const twofa_verification_opts = { + "window": 1, + "time": 30 +}; /** * Run unit test after the server is ready @@ -74,7 +92,7 @@ const sslCert = process.env.SSL_CERT || args["ssl-cert"] || undefined; */ const testMode = !!args["test"] || false; -if (demoMode) { +if (config.demoMode) { console.log("==== Demo Mode ===="); } @@ -103,6 +121,15 @@ const { statusPageSocketHandler } = require("./socket-handlers/status-page-socke app.use(express.json()); +// Global Middleware +app.use(function (req, res, next) { + if (!disableFrameSameOrigin) { + res.setHeader("X-Frame-Options", "SAMEORIGIN"); + } + res.removeHeader("X-Powered-By"); + next(); +}); + /** * Total WebSocket client connected to server currently, no actual use * @type {number} @@ -131,7 +158,17 @@ let needSetup = false; * Cache Index HTML * @type {string} */ -let indexHTML = fs.readFileSync("./dist/index.html").toString(); +let indexHTML = ""; + +try { + indexHTML = fs.readFileSync("./dist/index.html").toString(); +} catch (e) { + // "dist/index.html" is not necessary for development + if (process.env.NODE_ENV !== "development") { + console.error("Error: Cannot find 'dist/index.html', did you install correctly?"); + process.exit(1); + } +} exports.entryPage = "dashboard"; @@ -176,7 +213,7 @@ exports.entryPage = "dashboard"; const apiRouter = require("./routers/api-router"); app.use(apiRouter); - // Universal Route Handler, must be at the end of all express route. + // Universal Route Handler, must be at the end of all express routes. app.get("*", async (_request, response) => { if (_request.originalUrl.startsWith("/upload/")) { response.status(404).send("File not found."); @@ -265,7 +302,7 @@ exports.entryPage = "dashboard"; } if (data.token) { - let verify = notp.totp.verify(data.token, user.twofa_secret); + let verify = notp.totp.verify(data.token, user.twofa_secret, twofa_verification_opts); if (verify && verify.delta == 0) { callback({ @@ -305,7 +342,7 @@ exports.entryPage = "dashboard"; ]); if (user.twofa_status == 0) { - let newSecret = await genSecret(); + let newSecret = genSecret(); let encodedSecret = base32.encode(newSecret); // Google authenticator doesn't like equal signs @@ -383,7 +420,7 @@ exports.entryPage = "dashboard"; socket.userID, ]); - let verify = notp.totp.verify(token, user.twofa_secret); + let verify = notp.totp.verify(token, user.twofa_secret, twofa_verification_opts); if (verify && verify.delta == 0) { callback({ @@ -432,8 +469,12 @@ exports.entryPage = "dashboard"; socket.on("setup", async (username, password, callback) => { try { + if (passwordStrength(password).value === "Too weak") { + throw new Error("Password is too weak. It should contain alphabetic and numeric characters. It must be at least 6 characters in length."); + } + if ((await R.count("user")) !== 0) { - throw new Error("Uptime Kuma has been setup. If you want to setup again, please delete the database."); + throw new Error("Uptime Kuma has been initialized. If you want to run setup again, please delete the database."); } let user = R.dispense("user"); @@ -509,6 +550,9 @@ exports.entryPage = "dashboard"; bean.name = monitor.name; bean.type = monitor.type; bean.url = monitor.url; + bean.method = monitor.method; + bean.body = monitor.body; + bean.headers = monitor.headers; bean.interval = monitor.interval; bean.retryInterval = monitor.retryInterval; bean.hostname = monitor.hostname; @@ -818,10 +862,14 @@ exports.entryPage = "dashboard"; try { checkLogin(socket); - if (! password.currentPassword) { + if (! password.newPassword) { throw new Error("Invalid new password"); } + if (passwordStrength(password.newPassword).value === "Too weak") { + throw new Error("Password is too weak. It should contain alphabetic and numeric characters. It must be at least 6 characters in length."); + } + let user = await R.findOne("user", " id = ? AND active = 1 ", [ socket.userID, ]); @@ -1034,6 +1082,9 @@ exports.entryPage = "dashboard"; name: monitorListData[i].name, type: monitorListData[i].type, url: monitorListData[i].url, + method: monitorListData[i].method || "GET", + body: monitorListData[i].body, + headers: monitorListData[i].headers, interval: monitorListData[i].interval, retryInterval: retryInterval, hostname: monitorListData[i].hostname, @@ -1239,6 +1290,8 @@ exports.entryPage = "dashboard"; } }); + initBackgroundJobs(args); + })(); async function updateMonitorNotification(monitorID, notificationIDList) { @@ -1315,7 +1368,7 @@ async function initDatabase() { fs.copyFileSync(Database.templatePath, Database.path); } - console.log("Connecting to Database"); + console.log("Connecting to the Database"); await Database.connect(); console.log("Connected"); @@ -1415,7 +1468,7 @@ async function shutdownFunction(signal) { } function finalFunction() { - console.log("Graceful shutdown successfully!"); + console.log("Graceful shutdown successful!"); } gracefulShutdown(server, { diff --git a/server/util-server.js b/server/util-server.js index 5620d674b..7be922dd5 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -6,6 +6,14 @@ const passwordHash = require("./password-hash"); const dayjs = require("dayjs"); const { Resolver } = require("dns"); const child_process = require("child_process"); +const iconv = require("iconv-lite"); +const chardet = require("chardet"); + +// From ping-lite +exports.WIN = /^win/.test(process.platform); +exports.LIN = /^linux/.test(process.platform); +exports.MAC = /^darwin/.test(process.platform); +exports.FBSD = /^freebsd/.test(process.platform); /** * Init or reset JWT secret @@ -116,7 +124,7 @@ exports.setting = async function (key) { } }; -exports.setSetting = async function (key, value) { +exports.setSetting = async function (key, value, type = null) { let bean = await R.findOne("setting", " `key` = ? ", [ key, ]); @@ -124,6 +132,7 @@ exports.setSetting = async function (key, value) { bean = R.dispense("setting"); bean.key = key; } + bean.type = type; bean.value = JSON.stringify(value); await R.store(bean); }; @@ -312,3 +321,14 @@ exports.startUnitTest = async () => { process.exit(code); }); }; + +/** + * @param body : Buffer + * @returns {string} + */ +exports.convertToUTF8 = (body) => { + const guessEncoding = chardet.detect(body); + //debug("Guess Encoding: " + guessEncoding); + const str = iconv.decode(body, guessEncoding); + return str.toString(); +}; diff --git a/src/assets/app.scss b/src/assets/app.scss index 2cbec5c01..e1a5d052d 100644 --- a/src/assets/app.scss +++ b/src/assets/app.scss @@ -14,6 +14,10 @@ h2 { font-size: 26px; } +textarea.form-control { + border-radius: 19px; +} + ::-webkit-scrollbar { width: 10px; } diff --git a/src/assets/multiselect.scss b/src/assets/multiselect.scss index 300230769..53b47c16e 100644 --- a/src/assets/multiselect.scss +++ b/src/assets/multiselect.scss @@ -21,7 +21,7 @@ } .multiselect__tag { - border-radius: 50rem; + border-radius: $border-radius; margin-bottom: 0; padding: 6px 26px 6px 10px; background: $primary !important; diff --git a/src/components/HeartbeatBar.vue b/src/components/HeartbeatBar.vue index 4dc2c712c..e62b95dfb 100644 --- a/src/components/HeartbeatBar.vue +++ b/src/components/HeartbeatBar.vue @@ -186,7 +186,7 @@ export default { .beat { display: inline-block; background-color: $primary; - border-radius: 50rem; + border-radius: $border-radius; &.empty { background-color: aliceblue; diff --git a/src/components/notifications/AliyunSms.vue b/src/components/notifications/AliyunSms.vue new file mode 100644 index 000000000..2c25a3a9c --- /dev/null +++ b/src/components/notifications/AliyunSms.vue @@ -0,0 +1,25 @@ + diff --git a/src/components/notifications/DingDing.vue b/src/components/notifications/DingDing.vue new file mode 100644 index 000000000..713859aca --- /dev/null +++ b/src/components/notifications/DingDing.vue @@ -0,0 +1,16 @@ + diff --git a/src/components/notifications/Feishu.vue b/src/components/notifications/Feishu.vue index 18dc26422..6e00a3140 100644 --- a/src/components/notifications/Feishu.vue +++ b/src/components/notifications/Feishu.vue @@ -5,7 +5,7 @@

*{{ $t("Required") }}

- +
- * + *
- * + *
- * + *
- *Required + *{{ $t("Required") }}

- You can find the internal room ID by looking in the advanced section of the room settings in your Matrix client. It should look like !QMdRCpUIfLwsfjxye6:home.server. -

-

- It is highly recommended you create a new user and do not use your own Matrix user's access token as it will allow full access to your account and all the rooms you joined. Instead, create a new user and only invite it to the room that you want to receive the notification in. You can get the access token by running curl -XPOST -d '{"type": "m.login.password", "identifier": {"user": "botusername", "type": "m.id.user"}, "password": "passwordforuser"}' "https://home.server/_matrix/client/r0/login". + {{ $t("matrixDesc1") }}

+ + curl -XPOST -d '{"type": "m.login.password", "identifier": {"user": "botusername", "type": "m.id.user"}, "password": "passwordforuser"}' "https://home.server/_matrix/client/r0/login". +
@@ -30,5 +30,5 @@ export default { components: { HiddenInput, }, -} +}; diff --git a/src/components/notifications/Octopush.vue b/src/components/notifications/Octopush.vue index c75e87d33..37629d390 100644 --- a/src/components/notifications/Octopush.vue +++ b/src/components/notifications/Octopush.vue @@ -6,7 +6,7 @@
- Do you use the legacy version of Octopush (2011-2020) or the new version? + {{ $t("octopushLegacyHint") }}
@@ -25,7 +26,6 @@
- - diff --git a/src/pages/Settings.vue b/src/pages/Settings.vue index 0f4fe5a8e..dc0198531 100644 --- a/src/pages/Settings.vue +++ b/src/pages/Settings.vue @@ -108,7 +108,7 @@
- +
@@ -119,6 +119,25 @@
+ +
+ + +
+ {{ $t("steamApiKeyDescription") }}https://steamcommunity.com/dev +
+
+ + +
+

{{ $t("Monitor History") }}

+
+ + +
+
+ +