Browse Source

Merge remote-tracking branch 'louislam/master' into feature/monitor-checks

bertyhell/feature/monitor-checks
Bert Verhelst 3 years ago
parent
commit
03ac511b05
  1. 22
      .github/workflows/stale-bot
  2. 2
      CODE_OF_CONDUCT.md
  3. 12
      README.md
  4. 5
      docker/dockerfile
  5. 8
      extra/update-language-files/index.js
  6. 32
      kubernetes/README.md
  7. 10
      kubernetes/kustomization.yml
  8. 45
      kubernetes/uptime-kuma/deployment.yml
  9. 39
      kubernetes/uptime-kuma/ingressroute.yml
  10. 5
      kubernetes/uptime-kuma/kustomization.yml
  11. 10
      kubernetes/uptime-kuma/pvc.yml
  12. 13
      kubernetes/uptime-kuma/service.yml
  13. 518
      package-lock.json
  14. 13
      package.json
  15. 22
      server/database.js
  16. 31
      server/jobs.js
  17. 40
      server/jobs/clear-old-data.js
  18. 39
      server/jobs/util-worker.js
  19. 44
      server/model/monitor.js
  20. 27
      server/server.js
  21. 3
      server/util-server.js
  22. 18
      src/components/notifications/Matrix.vue
  23. 2
      src/components/notifications/Octopush.vue
  24. 10
      src/components/notifications/PromoSMS.vue
  25. 6
      src/components/notifications/Telegram.vue
  26. 2
      src/components/notifications/Webhook.vue
  27. 77
      src/languages/en.js
  28. 9
      src/languages/es-ES.js
  29. 2
      src/languages/fr-FR.js
  30. 2
      src/languages/ko-KR.js
  31. 2
      src/languages/nb-NO.js
  32. 1
      src/languages/nl-NL.js
  33. 2
      src/languages/pl.js
  34. 130
      src/languages/zh-CN.js
  35. 8
      src/mixins/socket.js
  36. 28
      src/pages/EditMonitor.vue
  37. 25
      src/pages/Settings.vue
  38. 2
      src/pages/Setup.vue
  39. 57
      src/util.js
  40. 66
      src/util.ts

22
.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'

2
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

12
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

5
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

8
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;
}

32
kubernetes/README.md

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

10
kubernetes/kustomization.yml

@ -1,10 +0,0 @@
namespace: uptime-kuma
namePrefix: uptime-kuma-
commonLabels:
app: uptime-kuma
bases:
- uptime-kuma

45
kubernetes/uptime-kuma/deployment.yml

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

39
kubernetes/uptime-kuma/ingressroute.yml

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

5
kubernetes/uptime-kuma/kustomization.yml

@ -1,5 +0,0 @@
resources:
- deployment.yml
- service.yml
- ingressroute.yml
- pvc.yml

10
kubernetes/uptime-kuma/pvc.yml

@ -1,10 +0,0 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 4Gi

13
kubernetes/uptime-kuma/service.yml

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

518
package-lock.json

@ -19,6 +19,7 @@
"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",
@ -1716,7 +1717,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"
},
@ -1819,6 +1819,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",
@ -2664,6 +2672,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",
@ -3569,6 +3582,11 @@
"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",
@ -3602,6 +3620,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",
@ -3640,6 +3681,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",
@ -3701,6 +3753,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",
@ -3993,6 +4053,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",
@ -4146,6 +4215,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",
@ -4219,6 +4296,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",
@ -6311,6 +6393,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",
@ -6595,6 +6685,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",
@ -6652,6 +6772,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",
@ -6670,6 +6806,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",
@ -7827,6 +7974,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",
@ -7891,6 +8083,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",
@ -8301,6 +8502,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",
@ -8542,6 +8748,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",
@ -8652,6 +8863,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",
@ -8678,6 +8897,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",
@ -8686,6 +8916,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",
@ -9178,6 +9419,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",
@ -9733,8 +9979,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",
@ -10153,6 +10398,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",
@ -11216,6 +11466,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",
@ -12273,6 +12528,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",
@ -13427,7 +13699,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"
}
@ -13510,6 +13781,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",
@ -14211,6 +14487,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",
@ -14961,6 +15242,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",
@ -14985,6 +15271,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",
@ -15013,6 +15319,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",
@ -15057,6 +15371,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",
@ -15275,6 +15594,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",
@ -15397,6 +15725,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",
@ -15451,6 +15787,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",
@ -17014,6 +17355,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",
@ -17208,6 +17557,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",
@ -17247,6 +17619,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",
@ -17259,6 +17644,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",
@ -18167,6 +18560,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",
@ -18231,6 +18669,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",
@ -18524,6 +18971,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",
@ -18711,6 +19163,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",
@ -18791,6 +19248,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",
@ -18808,11 +19270,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",
@ -19178,6 +19656,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",
@ -19614,8 +20097,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",
@ -19920,6 +20402,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",
@ -20781,6 +21268,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",
@ -21521,6 +22013,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",

13
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",
@ -63,6 +63,7 @@
"bcryptjs": "~2.4.3",
"bootstrap": "~5.1.1",
"chardet": "^1.3.0",
"bree": "~6.3.1",
"chart.js": "~3.5.1",
"chartjs-adapter-dayjs": "~1.0.0",
"command-exists": "~1.2.9",

22
server/database.js

@ -55,7 +55,7 @@ class Database {
}
/**
* 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;
@ -134,7 +134,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 {
@ -155,8 +155,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);
@ -194,7 +194,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();
@ -235,7 +235,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");
@ -290,7 +290,7 @@ class Database {
};
process.addListener("unhandledRejection", listener);
console.log("Closing DB");
console.log("Closing the database");
while (true) {
Database.noReject = true;
@ -300,7 +300,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");
@ -315,7 +315,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);
@ -338,7 +338,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";
@ -357,7 +357,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);
}

31
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
};

40
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();
})();

39
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,
};

44
server/model/monitor.js

@ -23,7 +23,9 @@ const {
ping,
dnsResolve,
checkCertificate,
checkStatusCode,
getTotalClientInRoom,
setting,
} = require("../util-server");
const { R } = require("redbean-node");
const { BeanModel } = require("redbean-node/dist/bean-model");
@ -273,6 +275,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;
@ -305,7 +347,7 @@ class Monitor extends BeanModel {
let beatInterval = this.interval;
let isImportant = Monitor.isImportantBeat(isFirstBeat, previousBeat.status, bean.status);
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

27
server/server.js

@ -58,6 +58,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");
@ -83,12 +86,13 @@ const port = parseInt(process.env.UPTIME_KUMA_PORT || process.env.PORT || args.p
// SSL
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
@ -130,6 +134,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}
@ -203,7 +216,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.");
@ -332,7 +345,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
@ -460,7 +473,7 @@ exports.entryPage = "dashboard";
socket.on("setup", async (username, password, callback) => {
try {
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");
@ -1301,6 +1314,8 @@ exports.entryPage = "dashboard";
}
});
initBackgroundJobs(args);
})();
async function updateMonitorNotification(monitorID, notificationIDList) {
@ -1377,7 +1392,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");
@ -1477,7 +1492,7 @@ async function shutdownFunction(signal) {
}
function finalFunction() {
console.log("Graceful shutdown successfully!");
console.log("Graceful shutdown successful!");
}
gracefulShutdown(server, {

3
server/util-server.js

@ -124,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,
]);
@ -132,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);
};

18
src/components/notifications/Matrix.vue

@ -1,25 +1,25 @@
<template>
<div class="mb-3">
<label for="homeserver-url" class="form-label">Homeserver URL (with http(s):// and optionally port)</label><span style="color: red;"><sup>*</sup></span>
<label for="homeserver-url" class="form-label">{{ $t("matrixHomeserverURL") }}</label><span style="color: red;"><sup>*</sup></span>
<input id="homeserver-url" v-model="$parent.notification.homeserverUrl" type="text" class="form-control" :required="true">
</div>
<div class="mb-3">
<label for="internal-room-id" class="form-label">Internal Room Id</label><span style="color: red;"><sup>*</sup></span>
<label for="internal-room-id" class="form-label">{{ $t("Internal Room Id") }}</label><span style="color: red;"><sup>*</sup></span>
<input id="internal-room-id" v-model="$parent.notification.internalRoomId" type="text" class="form-control" required="true">
</div>
<div class="mb-3">
<label for="access-token" class="form-label">Access Token</label><span style="color: red;"><sup>*</sup></span>
<label for="access-token" class="form-label">{{ $t("Access Token") }}</label><span style="color: red;"><sup>*</sup></span>
<HiddenInput id="access-token" v-model="$parent.notification.accessToken" :required="true" autocomplete="one-time-code" :maxlength="500"></HiddenInput>
</div>
<div class="form-text">
<span style="color: red;"><sup>*</sup></span>Required
<span style="color: red;"><sup>*</sup></span>{{ $t("Required") }}
<p style="margin-top: 8px;">
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.
</p>
<p style="margin-top: 8px;">
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 <code>curl -XPOST -d '{"type": "m.login.password", "identifier": {"user": "botusername", "type": "m.id.user"}, "password": "passwordforuser"}' "https://home.server/_matrix/client/r0/login"</code>.
{{ $t("matrixDesc1") }}
</p>
<i18n-t tag="p" keypath="matrixDesc2" style="margin-top: 8px;">
<code>curl -XPOST -d '{"type": "m.login.password", "identifier": {"user": "botusername", "type": "m.id.user"}, "password": "passwordforuser"}' "https://home.server/_matrix/client/r0/login"</code>.
</i18n-t>
</div>
</template>
@ -30,5 +30,5 @@ export default {
components: {
HiddenInput,
},
}
};
</script>

2
src/components/notifications/Octopush.vue

@ -6,7 +6,7 @@
<option value="1">Legacy Octopush-DM (endpoint: www.octopush-dm.com)</option>
</select>
<div class="form-text">
Do you use the legacy version of Octopush (2011-2020) or the new version?
{{ $t("octopushLegacyHint") }}
</div>
</div>
<div class="mb-3">

10
src/components/notifications/PromoSMS.vue

@ -10,12 +10,13 @@
<select id="promosms-type-sms" v-model="$parent.notification.promosmsSMSType" class="form-select">
<option value="0">{{ $t("promosmsTypeFlash") }}</option>
<option value="1">{{ $t("promosmsTypeEco") }}</option>
<option value="2">{{ $t("promosmsTypeFull") }}</option>
<option value="3">{{ $t("promosmsTypeSpeed") }}</option>
<option value="3">{{ $t("promosmsTypeFull") }}</option>
<option value="4">{{ $t("promosmsTypeSpeed") }}</option>
</select>
<i18n-t tag="div" keypath="Check PromoSMS prices" class="form-text">
<div class="form-text">
{{ $t("checkPrice", [$t("promosms")]) }}
<a href="https://promosms.com/cennik/" target="_blank">https://promosms.com/cennik/</a>
</i18n-t>
</div>
</div>
<div class="mb-3">
<label for="promosms-phone-number" class="form-label">{{ $t("promosmsPhoneNumber") }}</label>
@ -25,7 +26,6 @@
<label for="promosms-sender-name" class="form-label">{{ $t("promosmsSMSSender") }}</label>
<input id="promosms-sender-name" v-model="$parent.notification.promosmsSenderName" type="text" minlength="3" maxlength="11" class="form-control">
</div>
</template>
<script>

6
src/components/notifications/Telegram.vue

@ -2,9 +2,9 @@
<div class="mb-3">
<label for="telegram-bot-token" class="form-label">{{ $t("Bot Token") }}</label>
<HiddenInput id="telegram-bot-token" v-model="$parent.notification.telegramBotToken" :required="true" autocomplete="one-time-code"></HiddenInput>
<div class="form-text">
{{ $t("You can get a token from") }} <a href="https://t.me/BotFather" target="_blank">https://t.me/BotFather</a>.
</div>
<i18n-t tag="div" keypath="wayToGetTelegramToken" class="form-text">
<a href="https://t.me/BotFather" target="_blank">https://t.me/BotFather</a>
</i18n-t>
</div>
<div class="mb-3">

2
src/components/notifications/Webhook.vue

@ -16,7 +16,7 @@
</select>
<div class="form-text">
<p>"application/json" is good for any modern http servers such as express.js</p>
<p>{{ $t("webhookJsonDesc", ["\"application/json\""]) }}</p>
<i18n-t tag="p" keypath="webhookFormDataDesc">
<template #multipart>"multipart/form-data"</template>
<template #decodeFunction>

77
src/languages/en.js

@ -1,28 +1,28 @@
export default {
languageName: "English",
checkEverySecond: "Check every {0} seconds.",
retryCheckEverySecond: "Retry every {0} seconds.",
checkEverySecond: "Check every {0} seconds",
retryCheckEverySecond: "Retry every {0} seconds",
retriesDescription: "Maximum retries before the service is marked as down and a notification is sent",
ignoreTLSError: "Ignore TLS/SSL error for HTTPS websites",
upsideDownModeDescription: "Flip the status upside down. If the service is reachable, it is DOWN.",
maxRedirectDescription: "Maximum number of redirects to follow. Set to 0 to disable redirects.",
acceptedStatusCodesDescription: "Select status codes which are considered as a successful response.",
passwordNotMatchMsg: "The repeat password does not match.",
notificationDescription: "Please assign a notification to monitor(s) to get it to work.",
keywordDescription: "Search keyword in plain html or JSON response and it is case-sensitive",
notificationDescription: "Notifications must be assigned to a monitor to function.",
keywordDescription: "Search keyword in plain HTML or JSON response. The search is case-sensitive.",
pauseDashboardHome: "Pause",
deleteMonitorMsg: "Are you sure want to delete this monitor?",
deleteNotificationMsg: "Are you sure want to delete this notification for all monitors?",
resoverserverDescription: "Cloudflare is the default server, you can change the resolver server anytime.",
rrtypeDescription: "Select the RR-Type you want to monitor",
resoverserverDescription: "Cloudflare is the default server. You can change the resolver server anytime.",
rrtypeDescription: "Select the RR type you want to monitor",
pauseMonitorMsg: "Are you sure want to pause?",
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.",
enableDefaultNotificationDescription: "This notification will be enabled by default for new monitors. You can still disable the notification separately for each monitor.",
clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure you want to delete ALL statistics?",
importHandleDescription: "Choose 'Skip existing' if you want to skip every monitor or notification with the same name. 'Overwrite' will delete every existing monitor and notification.",
confirmImportMsg: "Are you sure to import the backup? Please make sure you've selected the right import option.",
twoFAVerifyLabel: "Please type in your token to verify that 2FA is working",
confirmImportMsg: "Are you sure you want to import the backup? Please verify you've selected the correct import option.",
twoFAVerifyLabel: "Please enter your token to verify 2FA:",
tokenValidSettingsMsg: "Token is valid! You can now save the 2FA settings.",
confirmEnableTwoFAMsg: "Are you sure you want to enable 2FA?",
confirmDisableTwoFAMsg: "Are you sure you want to disable 2FA?",
@ -33,6 +33,7 @@ export default {
Appearance: "Appearance",
Theme: "Theme",
General: "General",
"Primary Base URL": "Primary Base URL",
Version: "Version",
"Check Update On GitHub": "Check Update On GitHub",
List: "List",
@ -75,6 +76,9 @@ export default {
"Upside Down Mode": "Upside Down Mode",
"Max. Redirects": "Max. Redirects",
"Accepted Status Codes": "Accepted Status Codes",
"Push URL": "Push URL",
needPushEvery: "You should call this URL every {0} seconds.",
pushOptionalParams: "Optional parameters: {0}",
Save: "Save",
Notifications: "Notifications",
"Not available, please setup.": "Not available, please setup.",
@ -131,9 +135,9 @@ export default {
Events: "Events",
Heartbeats: "Heartbeats",
"Auto Get": "Auto Get",
backupDescription: "You can backup all monitors and all notifications into a JSON file.",
backupDescription2: "PS: History and event data is not included.",
backupDescription3: "Sensitive data such as notification tokens is included in the export file, please keep it carefully.",
backupDescription: "You can backup all monitors and notifications into a JSON file.",
backupDescription2: "Note: history and event data is not included.",
backupDescription3: "Sensitive data such as notification tokens are included in the export file; please store export securely.",
alertNoFile: "Please select a file to import.",
alertWrongFileType: "Please select a JSON file.",
"Clear all statistics": "Clear all Statistics",
@ -153,8 +157,8 @@ export default {
"Show URI": "Show URI",
Tags: "Tags",
"Add New below or Select...": "Add New below or Select...",
"Tag with this name already exist.": "Tag with this name already exist.",
"Tag with this value already exist.": "Tag with this value already exist.",
"Tag with this name already exist.": "Tag with this name already exists.",
"Tag with this value already exist.": "Tag with this value already exists.",
color: "color",
"value (optional)": "value (optional)",
Gray: "Gray",
@ -185,17 +189,17 @@ export default {
"Required": "Required",
"telegram": "Telegram",
"Bot Token": "Bot Token",
"You can get a token from": "You can get a token from",
wayToGetTelegramToken: "You can get a token from {0}.",
"Chat ID": "Chat ID",
supportTelegramChatID: "Support Direct Chat / Group / Channel's Chat ID",
wayToGetTelegramChatID: "You can get your chat id by sending message to the bot and go to this url to view the chat_id:",
wayToGetTelegramChatID: "You can get your chat ID by sending a message to the bot and going to this URL to view the chat_id:",
"YOUR BOT TOKEN HERE": "YOUR BOT TOKEN HERE",
chatIDNotFound: "Chat ID is not found, please send a message to this bot first",
chatIDNotFound: "Chat ID is not found; please send a message to this bot first",
"webhook": "Webhook",
"Post URL": "Post URL",
"Content Type": "Content Type",
webhookJsonDesc: "{0} is good for any modern http servers such as express.js",
webhookFormDataDesc: "{multipart} is good for PHP, you just need to parse the json by {decodeFunction}",
webhookJsonDesc: "{0} is good for any modern HTTP servers such as Express.js",
webhookFormDataDesc: "{multipart} is good for PHP. The JSON will need to be parsed with {decodeFunction}",
"smtp": "Email (SMTP)",
secureOptionNone: "None / STARTTLS (25, 587)",
secureOptionTLS: "TLS (465)",
@ -213,12 +217,12 @@ export default {
"Hello @everyone is...": "Hello {'@'}everyone is...",
"teams": "Microsoft Teams",
"Webhook URL": "Webhook URL",
wayToGetTeamsURL: "You can learn how to create a webhook url {0}.",
wayToGetTeamsURL: "You can learn how to create a webhook URL {0}.",
"signal": "Signal",
"Number": "Number",
"Recipients": "Recipients",
needSignalAPI: "You need to have a signal client with REST API.",
wayToCheckSignalURL: "You can check this url to view how to setup one:",
wayToCheckSignalURL: "You can check this URL to view how to set one up:",
signalImportant: "IMPORTANT: You cannot mix groups and numbers in recipients!",
"gotify": "Gotify",
"Application Token": "Application Token",
@ -228,11 +232,11 @@ export default {
"Icon Emoji": "Icon Emoji",
"Channel Name": "Channel Name",
"Uptime Kuma URL": "Uptime Kuma URL",
aboutWebhooks: "More info about webhooks on: {0}",
aboutChannelName: "Enter the channel name on {0} Channel Name field if you want to bypass the webhook channel. Ex: #other-channel",
aboutKumaURL: "If you leave the Uptime Kuma URL field blank, it will default to the Project Github page.",
aboutWebhooks: "More info about Webhooks on: {0}",
aboutChannelName: "Enter the channel name on {0} Channel Name field if you want to bypass the Webhook channel. Ex: #other-channel",
aboutKumaURL: "If you leave the Uptime Kuma URL field blank, it will default to the Project GitHub page.",
emojiCheatSheet: "Emoji cheat sheet: {0}",
"rocket.chat": "Rocket.chat",
"rocket.chat": "Rocket.Chat",
pushover: "Pushover",
pushy: "Pushy",
octopush: "Octopush",
@ -251,7 +255,9 @@ export default {
pushoverDesc2: "If you want to send notifications to different devices, fill out Device field.",
"SMS Type": "SMS Type",
octopushTypePremium: "Premium (Fast - recommended for alerting)",
octopushTypeLowCost: "Low Cost (Slow, sometimes blocked by operator)",
octopushTypeLowCost: "Low Cost (Slow - sometimes blocked by operator)",
checkPrice: "Check {0} prices:",
octopushLegacyHint: "Do you use the legacy version of Octopush (2011-2020) or the new version?",
"Check octopush prices": "Check octopush prices {0}.",
octopushPhoneNumber: "Phone number (intl format, eg : +33612345678) ",
octopushSMSSender: "SMS Sender Name : 3-11 alphanumeric characters and space (a-zA-Z0-9)",
@ -270,18 +276,22 @@ export default {
"Basic Settings": "Basic Settings",
"User ID": "User ID",
"Messaging API": "Messaging API",
wayToGetLineChannelToken: "First access the {0}, create a provider and channel (Messaging API), then you can get the channel access token and user id from the above mentioned menu items.",
wayToGetLineChannelToken: "First access the {0}, create a provider and channel (Messaging API), then you can get the channel access token and user ID from the above mentioned menu items.",
"Icon URL": "Icon URL",
aboutIconURL: "You can provide a link to a picture in \"Icon URL\" to override the default profile picture. Will not be used if Icon Emoji is set.",
aboutMattermostChannelName: "You can override the default channel that webhook posts to by entering the channel name into \"Channel Name\" field. This needs to be enabled in Mattermost webhook settings. Ex: #other-channel",
aboutMattermostChannelName: "You can override the default channel that the Webhook posts to by entering the channel name into \"Channel Name\" field. This needs to be enabled in the Mattermost Webhook settings. Ex: #other-channel",
"matrix": "Matrix",
promosmsTypeEco: "SMS ECO - cheap but slow and often overloaded. Limited only to Polish recipients.",
promosmsTypeFlash: "SMS FLASH - Message will automatically show on recipient device. Limited only to Polish recipients.",
promosmsTypeFull: "SMS FULL - Premium tier of SMS, You can use Your Sender Name (You need to register name first). Reliable for alerts.",
promosmsTypeFull: "SMS FULL - Premium tier of SMS, You can use your Sender Name (You need to register name first). Reliable for alerts.",
promosmsTypeSpeed: "SMS SPEED - Highest priority in system. Very quick and reliable but costly (about twice of SMS FULL price).",
promosmsPhoneNumber: "Phone number (for Polish recipient You can skip area codes)",
promosmsSMSSender: "SMS Sender Name : Pre-registred name or one of defaults: InfoSMS, SMS Info, MaxSMS, INFO, SMS",
"Feishu WebHookUrl": "Feishu WebHookUrl",
"Feishu WebHookUrl": "Feishu WebHookURL",
matrixHomeserverURL: "Homeserver URL (with http(s):// and optionally port)",
"Internal Room Id": "Internal Room ID",
matrixDesc1: "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.",
matrixDesc2: "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 {0}",
// End notification form
Method: "Method",
Body: "Body",
@ -289,6 +299,13 @@ export default {
PushUrl: "Push URL",
HeadersInvalidFormat: "The request headers are not valid JSON: ",
BodyInvalidFormat: "The request body is not valid JSON: ",
"Monitor History": "Monitor History:",
clearDataOlderThan: "Keep monitor history data for {0} days.",
PasswordsDoNotMatch: "Passwords do not match.",
records: "records",
"One record": "One record",
"Showing {from} to {to} of {count} records": "Showing {from} to {to} of {count} records",
steamApiKeyDescription: "For monitoring a Steam Game Server you need a Steam Web-API key. You can register your API key here: ",
MonitorCheckTypeShould: "Should",
MonitorCheckTypeShouldNot: "Should not",
MonitorCheckTypeHttpStatusCodeShouldEqual: "equal status code",

9
src/languages/es-ES.js

@ -9,7 +9,7 @@ export default {
passwordNotMatchMsg: "La contraseña repetida no coincide.",
notificationDescription: "Por favor asigne una notificación a el/los monitor(es) para hacerlos funcional(es).",
keywordDescription: "Palabra clave en HTML plano o respuesta JSON y es sensible a mayúsculas",
pauseDashboardHome: "Pausar",
pauseDashboardHome: "Pausado",
deleteMonitorMsg: "¿Seguro que quieres eliminar este monitor?",
deleteNotificationMsg: "¿Seguro que quieres eliminar esta notificación para todos los monitores?",
resoverserverDescription: "Cloudflare es el servidor por defecto, puedes cambiar el servidor de resolución en cualquier momento.",
@ -32,7 +32,7 @@ export default {
Down: "Caído",
Pending: "Pendiente",
Unknown: "Desconocido",
Pause: "Pausa",
Pause: "Pausar",
Name: "Nombre",
Status: "Estado",
DateTime: "Fecha y Hora",
@ -198,4 +198,9 @@ export default {
pushbullet: "Pushbullet",
line: "Line Messenger",
mattermost: "Mattermost",
"Monitor History": "Historial de monitor:",
clearDataOlderThan: "Mantener los datos del historial del monitor durante {0} días.",
records: "registros",
"One record": "Un registro",
"Showing {from} to {to} of {count} records": "Mostrando desde {from} a {to} de {count} registros",
};

2
src/languages/fr-FR.js

@ -185,7 +185,7 @@ export default {
"Required": "Requis",
"telegram": "Telegram",
"Bot Token": "Bot Token",
"You can get a token from": "Vous pouvez obtenir un token depuis",
wayToGetTelegramToken: "Vous pouvez obtenir un token depuis {0}.",
"Chat ID": "Chat ID",
supportTelegramChatID: "Supporte les messages privés / en groupe / l'ID du salon",
wayToGetTelegramChatID: "Vous pouvez obtenir l'ID du chat en envoyant un message avec le bot puis en récupérant l'URL pour voir l'ID du salon :",

2
src/languages/ko-KR.js

@ -184,7 +184,7 @@ export default {
Required: "필수",
telegram: "Telegram",
"Bot Token": "봇 토큰",
"You can get a token from": "토큰은 여기서 얻을 수 있어요:",
wayToGetTelegramToken: "토큰은 여기서 얻을 수 있어요: {0}.",
"Chat ID": "채팅 ID",
supportTelegramChatID: "Direct Chat / Group / Channel's Chat ID를 지원해요.",
wayToGetTelegramChatID: "봇에 메시지를 보내 채팅 ID를 얻고 밑에 URL로 이동해 chat_id를 볼 수 있어요.",

2
src/languages/nb-NO.js

@ -185,7 +185,7 @@ export default {
"Required": "Obligatorisk",
"telegram": "Telegram",
"Bot Token": "Bot Token",
"You can get a token from": "Du kan få et token fra",
wayToGetTelegramToken: "Du kan få et token fra {0}.",
"Chat ID": "Chat ID",
supportTelegramChatID: "Support Direct Chat / Group / Channel's Chat ID",
wayToGetTelegramChatID: "Du kan få chat-ID-en din ved å sende meldingen til boten og gå til denne nettadressen for å se chat_id:",

1
src/languages/nl-NL.js

@ -203,6 +203,7 @@ export default {
Headers: "Headers",
PushUrl: "Push URL",
HeadersInvalidFormat: "The request headers is geen geldige JSON: ",
BodyInvalidFormat: "De request body is geen geldige JSON: "
BodyInvalidFormat: "De request body is geen geldige JSON: ",
BodyPlaceholder: "&lbrace;\n\t\"id\": 124357,\n\t\"gebruikersnaam\": \"admin\",\n\t\"wachtwoord\": \"mijnAdminWachtwoord\"\n&rbrace;",
HeadersPlaceholder: "&lbrace;\n\t\"Authorization\": \"Bearer abc123\",\n\t\"Content-Type\": \"application/json\"\n&rbrace;",

2
src/languages/pl.js

@ -185,7 +185,7 @@ export default {
"Required": "Wymagane",
"telegram": "Telegram",
"Bot Token": "Token Bota",
"You can get a token from": "Token można uzyskać z",
wayToGetTelegramToken: "Token można uzyskać z {0}.",
"Chat ID": "Identyfikator Czatu",
supportTelegramChatID: "Czat wsprarcia technicznego / Bezpośrednia Rozmowa / Czat Grupowy",
wayToGetTelegramChatID: "Możesz uzyskać swój identyfikator czatu, wysyłając wiadomość do bota i przechodząc pod ten adres URL, aby wyświetlić identyfikator czatu:",

130
src/languages/zh-CN.js

@ -22,7 +22,8 @@ export default {
Appearance: "外观设置",
Theme: "主题",
General: "基本设置",
Version: "Version",
"Primary Base URL": "站点地址(URL)",
Version: "版本",
"Check Update On GitHub": "检查更新",
List: "列表",
Add: "添加",
@ -43,7 +44,7 @@ export default {
Delete: "删除",
Current: "当前",
Uptime: "可用率",
"Cert Exp.": "证书期",
"Cert Exp.": "证书有效期",
days: "天",
day: "天",
"-day": " 天",
@ -63,6 +64,9 @@ export default {
"Upside Down Mode": "反向监控",
"Max. Redirects": "重定向次数",
"Accepted Status Codes": "有效状态码",
"Push URL": "推送链接",
needPushEvery: "你需要每 {0} 秒调用一次。",
pushOptionalParams: "可选参数:{0}",
Save: "保存",
Notifications: "消息通知",
"Not available, please setup.": "无可用通道,请先设置",
@ -103,10 +107,10 @@ export default {
"Certificate Info": "证书信息",
"Resolver Server": "解析服务器",
"Resource Record Type": "资源记录类型",
"Last Result": "Last Result",
"Last Result": "最后结果",
"Create your admin account": "创建管理员账号",
"Repeat Password": "重复密码",
respTime: "Resp. Time (ms)",
respTime: "响应时间(毫秒)",
notAvailableShort: "N/A",
Create: "创建",
clearEventsMsg: "确定要删除此监控项的所有事件吗?",
@ -126,21 +130,21 @@ export default {
backupDescription3: "导出的文件中可能包含敏感信息,如消息通知的 Token 信息,请小心存放!",
alertNoFile: "请选择一个文件导入",
alertWrongFileType: "请选择一个 JSON 格式的文件",
twoFAVerifyLabel: "请输入Token以验证2FA(二次验证)是否正常工作",
tokenValidSettingsMsg: "Token有效!您现在可以保存2FA(二次验证)设置",
confirmEnableTwoFAMsg: "确定要启用2FA(二次验证)吗?",
confirmDisableTwoFAMsg: "确定要禁用2FA(二次验证)吗?",
twoFAVerifyLabel: "请输入Token以验证 2FA(二次验证)是否正常工作",
tokenValidSettingsMsg: "Token有效!您现在可以保存 2FA(二次验证)设置",
confirmEnableTwoFAMsg: "确定要启用 2FA(二次验证)吗?",
confirmDisableTwoFAMsg: "确定要禁用 2FA(二次验证)吗?",
"Apply on all existing monitors": "应用到所有监控项",
"Verify Token": "验证Token",
"Setup 2FA": "设置2FA",
"Enable 2FA": "启用2FA",
"Disable 2FA": "禁用2FA",
"2FA Settings": "2FA设置",
"Verify Token": "验证 Token",
"Setup 2FA": "设置 2FA",
"Enable 2FA": "启用 2FA",
"Disable 2FA": "禁用 2FA",
"2FA Settings": "2FA 设置",
"Two Factor Authentication": "双因素认证",
Active: "效",
Inactive: "效",
Active: "效",
Inactive: "未生效",
Token: "Token",
"Show URI": "显示URI",
"Show URI": "显示链接",
"Clear all statistics": "清除所有统计数据",
retryCheckEverySecond: "重试间隔 {0} 秒",
importHandleDescription: "如果想跳过同名的监控项或通知,请选择“跳过”;“覆盖”将删除所有现有的监控项和通知。",
@ -167,7 +171,7 @@ export default {
Purple: "紫色",
Pink: "粉色",
"Search...": "搜索...",
"Avg. Ping": "平均Ping",
"Avg. Ping": "平均 Ping",
"Avg. Response": "平均响应",
"Entry Page": "入口页面",
statusPageNothing: "这里什么也没有,请添加一个分组或一个监控项。",
@ -182,7 +186,7 @@ export default {
"Status Page": "状态页",
telegram: "Telegram",
webhook: "Webhook",
smtp: "Email (SMTP)",
smtp: "电子邮件(SMTP)",
discord: "Discord",
teams: "Microsoft Teams",
signal: "Signal",
@ -194,9 +198,97 @@ export default {
octopush: "Octopush",
promosms: "PromoSMS",
lunasea: "LunaSea",
apprise: "Apprise (Support 50+ Notification services)",
apprise: "Apprise (支持50+种通知服务)",
pushbullet: "Pushbullet",
line: "Line Messenger",
mattermost: "Mattermost",
"Feishu WebHookUrl": "飞书 WebHook 地址",
defaultNotificationName: "{notification} 通知({number})",
here: "这里",
Required: "必填",
"Bot Token": "Bot Token",
wayToGetTelegramToken: "你可以从 {0} 获取 Token。",
"Chat ID": "Chat ID",
supportTelegramChatID: "支持对话/群组/频道的 ID",
wayToGetTelegramChatID: "你可以发送一条消息给你的机器人然后到下面的链接来查看你的 chat_id:",
"YOUR BOT TOKEN HERE": "这里替换成你的 BOT TOKEN",
chatIDNotFound: "没有找到 Chat ID,请先给你的机器人发送一条消息。",
"Post URL": "目标链接",
"Content Type": "Content Type",
webhookJsonDesc: "{0} 适合现代的服务,比如 express.js",
webhookFormDataDesc: "{multipart} 适合PHP,解码使用 {decodeFunction}",
secureOptionNone: "无 / STARTTLS(25,587)",
secureOptionTLS: "TLS(465)",
"Ignore TLS Error": "忽略 TLS 错误",
"From Email": "发信人",
"To Email": "收信人",
smtpCC: "抄送",
smtpBCC: "密送",
"Discord Webhook URL": "Discord Webhook 链接",
wayToGetDiscordURL: "获取方式:服务器设置 -> 整合 -> 创建 Webhook",
"Bot Display Name": "机器人显示名称",
"Prefix Custom Message": "自定义消息前缀",
"Hello @everyone is...": "{'@'}所有人,……",
"Webhook URL": "Webhook 链接",
wayToGetTeamsURL: "你可以在 {0} 获取 Webhook 链接。",
Number: "号码",
Recipients: "收件人",
needSignalAPI: "你需要有一个带 REST API 的 Signal 客户端。",
wayToCheckSignalURL: "你可以通过下面的链接来了解如何设置:",
signalImportant: "重要:你不能混合设定收件人的分组和号码!",
"Application Token": "Application Token",
"Server URL": "服务器链接",
Priority: "优先级",
"Icon Emoji": "Emoji 图标",
"Channel Name": "频道名称",
"Uptime Kuma URL": "Uptime Kuma 链接",
aboutWebhooks: "关于 Webhook 的更多信息:{0}",
aboutChannelName: "如果你想绕过 Webhook 设定的频道,请在设定 {0} 的频道名称字段为你想要的频道。例:#other-channel",
aboutKumaURL: "如果保留 Uptime Kuma 链接为空,将会默认指向项目的 Github 页面。",
emojiCheatSheet: "Emoji 参考表:{0}",
"User Key": "User Key",
Device: "设备",
"Message Title": "消息标题",
"Notification Sound": "通知铃声",
"More info on:": "更多信息:{0}",
pushoverDesc1: "紧急优先级(2)会在一小时内每30秒重试一次。",
pushoverDesc2: "如果你想发送通知给不同的设备,请填写“设备”字段。",
"SMS Type": "短信类型",
octopushTypePremium: "Premium(快 - 推荐用于警报)",
octopushTypeLowCost: "Low Cost(慢 - 有时会被运营商屏蔽)",
"Check octopush prices": "查看 Octopush 的价格 {0}。",
octopushPhoneNumber: "电话号码(国际格式,例:+33612345678)",
octopushSMSSender: "短信发送名称:3-11位大小写字母、数字和空格(a-zA-Z0-9)",
"LunaSea Device ID": "LunaSea 设备 ID",
"Apprise URL": "Apprise 链接",
"Example:": "例:{0}",
"Read more:": "了解更多:{0}",
"Status:": "状态:{0}",
"Read more": "了解更多",
appriseInstalled: "Apprise 已安装",
appriseNotInstalled: "Apprise 未安装。{0}",
"Access Token": "Access Token",
"Channel access token": "频道 access token",
"Line Developers Console": "Line Developers Console",
lineDevConsoleTo: "Line Developers Console - {0}",
"Basic Settings": "Basic Settings",
"User ID": "User ID",
"Messaging API": "Messaging API",
wayToGetLineChannelToken: "首先访问 {0},创建一个提供者和频道(Messaging API),然后你就可以从上面提到的地方获取频道的 access token 和用户 ID。",
"Icon URL": "图标链接",
aboutIconURL: "你可以在“Icon URL”中提供一个图片地址来覆盖默认的资料图片。如果设置了 Emoji 图标此字段会被忽略。",
aboutMattermostChannelName: "如果你想覆盖 Webhook 设定的频道,请在“频道名称”字段为你想要的频道。这需要在 Mattermost 的 Webhook 设定中启用。例:#other-channel",
matrix: "Matrix",
promosmsTypeEco: "SMS ECO - 便宜但是慢,并且容易超负荷。仅限波兰地区的收件人。",
promosmsTypeFlash: "SMS FLASH - 消息会自动显示在收件人设备上。仅限波兰地区的收件人。",
promosmsTypeFull: "SMS FULL - 高等级,你可以使用你自己的发件人名称(你需要先注册一个). 对于警报来说更可靠。",
promosmsTypeSpeed: "SMS SPEED - 最高优先级。非常快速可靠,但更贵(越两倍 SMS FULL 等级的价格)。",
promosmsPhoneNumber: "电话号码(波兰地区收件人可以不填区号)",
promosmsSMSSender: "短信发件人名称:已注册的名称或以下默认值之一:InfoSMS,SMS Info,MaxSMS,INFO,SMS",
checkPrice: "查看 {0} 的价格:",
octopushLegacyHint: "你是否在使用旧版本的 Octopush(2011-2020)?",
matrixHomeserverURL: "服务器链接(开头带 http(s):// 和可能的需要的端口号)",
"Internal Room Id": "Internal Room Id",
matrixDesc1: "你可以在 Matrix 客户端房间设置的高级选项找到 Internal Room Id。格式类似于 !QMdRCpUIfLwsfjxye6:home.server。",
matrixDesc2: "请不要使用你自己的 Access Token,这将开放你所有的账户权限和你加入的房间权限。你可以创建一个新的用户并邀请它至你允许的的房间中。你可以运行以下命令来获取 Access Token:{0}",
};

8
src/mixins/socket.js

@ -347,7 +347,7 @@ export default {
let result = {};
let unknown = {
text: "Unknown",
text: this.$t("Unknown"),
color: "secondary",
};
@ -358,17 +358,17 @@ export default {
result[monitorID] = unknown;
} else if (lastHeartBeat.status === 1) {
result[monitorID] = {
text: "Up",
text: this.$t("Up"),
color: "primary",
};
} else if (lastHeartBeat.status === 0) {
result[monitorID] = {
text: "Down",
text: this.$t("Down"),
color: "danger",
};
} else if (lastHeartBeat.status === 2) {
result[monitorID] = {
text: "Pending",
text: this.$t("Pending"),
color: "warning",
};
} else {

28
src/pages/EditMonitor.vue

@ -26,6 +26,9 @@
<option value="push">
Push
</option>
<option value="steam">
Steam Game Server
</option>
</select>
</div>
@ -46,23 +49,26 @@
<label for="push-url" class="form-label">{{ $t("PushUrl") }}</label>
<CopyableInput id="push-url" v-model="pushURL" type="url" disabled="disabled" />
<div class="form-text">
You should call this url every {{ monitor.interval }} seconds.<br />
Optional parameters: msg, ping
{{ $t("needPushEvery", [monitor.interval]) }}<br />
{{ $t("pushOptionalParams", ["msg, ping"]) }}
</div>
</div>
<!-- Hostname -->
<!-- TCP Port / Ping / DNS only -->
<div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' " class="my-3">
<div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' || monitor.type === 'steam'" class="my-3">
<label for="hostname" class="form-label">{{ $t("Hostname") }}</label>
<input id="hostname" v-model="monitor.hostname" type="text" class="form-control" :pattern="`${ipRegexPattern}|${hostnameRegexPattern}`" required>
</div>
<!-- For TCP Port Type -->
<div v-if="monitor.type === 'port' " class="my-3">
<!-- Port -->
<!-- For TCP Port / Steam Type -->
<div v-if="monitor.type === 'port' || monitor.type === 'steam'" class="my-3">
<label for="port" class="form-label">{{ $t("Port") }}</label>
<input id="port" v-model="monitor.port" type="number" class="form-control" required min="0" max="65535" step="1">
</div>
<!-- DNS Resolver Server -->
<!-- For DNS Type -->
<template v-if="monitor.type === 'dns'">
<div class="my-3">
@ -309,11 +315,11 @@ export default {
},
bodyPlaceholder() {
return this.decodeHtml("&lbrace;\n\t\"id\": 124357,\n\t\"username\": \"admin\",\n\t\"password\": \"myAdminPassword\"\n&rbrace;");
return "{\n\t\"id\": 124357,\n\t\"username\": \"admin\",\n\t\"password\": \"myAdminPassword\"\n}";
},
headersPlaceholder() {
return this.decodeHtml("&lbrace;\n\t\"Authorization\": \"Bearer abc123\",\n\t\"Content-Type\": \"application/json\"\n&rbrace;");
return "{\n\t\"Authorization\": \"Bearer abc123\",\n\t\"Content-Type\": \"application/json\"\n}";
}
},
@ -472,7 +478,7 @@ export default {
return;
}
// Beautiful the JSON format
// Beautify the JSON format
if (this.monitor.body) {
this.monitor.body = JSON.stringify(JSON.parse(this.monitor.body), null, 4);
}
@ -513,12 +519,6 @@ export default {
addedNotification(id) {
this.monitor.notificationIDList[id] = true;
},
decodeHtml(html) {
const txt = document.createElement("textarea");
txt.innerHTML = html;
return txt.value;
}
},
};
</script>

25
src/pages/Settings.vue

@ -108,7 +108,7 @@
<!-- Primary Base URL -->
<div class="mb-4">
<label class="form-label" for="primaryBaseURL">Primary Base URL</label>
<label class="form-label" for="primaryBaseURL">{{ $t("Primary Base URL") }}</label>
<div class="input-group mb-3">
<input id="primaryBaseURL" v-model="settings.primaryBaseURL" class="form-control" name="primaryBaseURL" placeholder="https://" pattern="https?://.+">
@ -119,6 +119,25 @@
</div>
</div>
<!-- Steam API Key -->
<div class="mb-4">
<label class="form-label" for="steamAPIKey">{{ $t("Steam API Key") }}</label>
<input id="steamAPIKey" v-model="settings.steamAPIKey" class="form-control" name="steamAPIKey">
<div class="form-text">
{{ $t("steamApiKeyDescription") }}<a href="https://steamcommunity.com/dev" target="_blank">https://steamcommunity.com/dev</a>
</div>
</div>
<!-- Monitor History -->
<div class="mb-4">
<h4 class="mt-4">{{ $t("Monitor History") }}</h4>
<div class="mt-2">
<label for="keepDataPeriodDays" class="form-label">{{ $t("clearDataOlderThan", [ settings.keepDataPeriodDays ]) }}</label>
<input id="keepDataPeriodDays" v-model="settings.keepDataPeriodDays" type="number" class="form-control" required min="1" step="1">
</div>
</div>
<!-- Save Button -->
<div>
<button class="btn btn-primary" type="submit">
{{ $t("Save") }}
@ -477,6 +496,10 @@ export default {
this.settings.entryPage = "dashboard";
}
if (this.settings.keepDataPeriodDays === undefined) {
this.settings.keepDataPeriodDays = 180;
}
this.loaded = true;
});
},

2
src/pages/Setup.vue

@ -75,7 +75,7 @@ export default {
this.processing = true;
if (this.password !== this.repeatPassword) {
toast.error("Repeat password do not match.");
toast.error(this.$t("PasswordsDoNotMatch"));
this.processing = false;
return;
}

57
src/util.js

@ -7,7 +7,7 @@
// Backend uses the compiled file util.js
// Frontend uses util.ts
Object.defineProperty(exports, "__esModule", { value: true });
exports.getMonitorRelativeURL = exports.genSecret = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.MONITOR_CHECK_MAP_INVERTED_TO_NORMAL = exports.MONITOR_CHECK_MAP_NORMAL_TO_INVERTED = exports.MONITOR_CHECK_INVERTED_TYPES = exports.MONITOR_CHECK_SELECTOR_TYPES = exports.MONITOR_CHECK_STRING_TYPES = exports.MONITOR_CHECK_HTTP_CODE_TYPES = exports.RESPONSE_SELECTOR_SHOULD_NOT_MATCH_REGEX = exports.RESPONSE_SELECTOR_SHOULD_MATCH_REGEX = exports.RESPONSE_SELECTOR_SHOULD_NOT_EQUAL = exports.RESPONSE_SELECTOR_SHOULD_EQUAL = exports.RESPONSE_SHOULD_NOT_MATCH_REGEX = exports.RESPONSE_SHOULD_MATCH_REGEX = exports.RESPONSE_SHOULD_NOT_CONTAIN_TEXT = exports.RESPONSE_SHOULD_CONTAIN_TEXT = exports.HTTP_STATUS_CODE_SHOULD_NOT_EQUAL = exports.HTTP_STATUS_CODE_SHOULD_EQUAL = exports.MONITOR_CHECK_SHOULD_NOT = exports.MONITOR_CHECK_SHOULD = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0;
exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0;
const _dayjs = require("dayjs");
const dayjs = _dayjs;
exports.isDev = process.env.NODE_ENV === "development";
@ -133,12 +133,61 @@ function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
exports.getRandomInt = getRandomInt;
/**
* Returns either the NodeJS crypto.randomBytes() function or its
* browser equivalent implemented via window.crypto.getRandomValues()
*/
let getRandomBytes = ((typeof window !== 'undefined' && window.crypto)
// Browsers
? function () {
return (numBytes) => {
let randomBytes = new Uint8Array(numBytes);
for (let i = 0; i < numBytes; i += 65536) {
window.crypto.getRandomValues(randomBytes.subarray(i, i + Math.min(numBytes - i, 65536)));
}
return randomBytes;
};
}
// Node
: function () {
return require("crypto").randomBytes;
})();
function getCryptoRandomInt(min, max) {
// synchronous version of: https://github.com/joepie91/node-random-number-csprng
const range = max - min;
if (range >= Math.pow(2, 32))
console.log("Warning! Range is too large.");
let tmpRange = range;
let bitsNeeded = 0;
let bytesNeeded = 0;
let mask = 1;
while (tmpRange > 0) {
if (bitsNeeded % 8 === 0)
bytesNeeded += 1;
bitsNeeded += 1;
mask = mask << 1 | 1;
tmpRange = tmpRange >>> 1;
}
const randomBytes = getRandomBytes(bytesNeeded);
let randomValue = 0;
for (let i = 0; i < bytesNeeded; i++) {
randomValue |= randomBytes[i] << 8 * i;
}
randomValue = randomValue & mask;
if (randomValue <= range) {
return min + randomValue;
}
else {
return getCryptoRandomInt(min, max);
}
}
exports.getCryptoRandomInt = getCryptoRandomInt;
function genSecret(length = 64) {
let secret = "";
let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let charsLength = chars.length;
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const charsLength = chars.length;
for (let i = 0; i < length; i++) {
secret += chars.charAt(Math.floor(Math.random() * charsLength));
secret += chars.charAt(getCryptoRandomInt(0, charsLength - 1));
}
return secret;
}

66
src/util.ts

@ -149,12 +149,72 @@ export function getRandomInt(min: number, max: number) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
/**
* Returns either the NodeJS crypto.randomBytes() function or its
* browser equivalent implemented via window.crypto.getRandomValues()
*/
let getRandomBytes = (
(typeof window !== 'undefined' && window.crypto)
// Browsers
? function () {
return (numBytes: number) => {
let randomBytes = new Uint8Array(numBytes);
for (let i = 0; i < numBytes; i += 65536) {
window.crypto.getRandomValues(randomBytes.subarray(i, i + Math.min(numBytes - i, 65536)));
}
return randomBytes;
};
}
// Node
: function() {
return require("crypto").randomBytes;
}
)();
export function getCryptoRandomInt(min: number, max: number):number {
// synchronous version of: https://github.com/joepie91/node-random-number-csprng
const range = max - min
if (range >= Math.pow(2, 32))
console.log("Warning! Range is too large.")
let tmpRange = range
let bitsNeeded = 0
let bytesNeeded = 0
let mask = 1
while (tmpRange > 0) {
if (bitsNeeded % 8 === 0) bytesNeeded += 1
bitsNeeded += 1
mask = mask << 1 | 1
tmpRange = tmpRange >>> 1
}
const randomBytes = getRandomBytes(bytesNeeded)
let randomValue = 0
for (let i = 0; i < bytesNeeded; i++) {
randomValue |= randomBytes[i] << 8 * i
}
randomValue = randomValue & mask;
if (randomValue <= range) {
return min + randomValue
} else {
return getCryptoRandomInt(min, max)
}
}
export function genSecret(length = 64) {
let secret = "";
let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let charsLength = chars.length;
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const charsLength = chars.length;
for ( let i = 0; i < length; i++ ) {
secret += chars.charAt(Math.floor(Math.random() * charsLength));
secret += chars.charAt(getCryptoRandomInt(0, charsLength - 1));
}
return secret;
}

Loading…
Cancel
Save