mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-09-11 05:16:55 +08:00
Compare commits
66 Commits
1.23.0
...
1.23.5-bet
Author | SHA1 | Date | |
---|---|---|---|
|
b7daebbd57 | ||
|
e9bc754b3f | ||
|
50cc91d2ca | ||
|
d56bf08cd7 | ||
|
291d5d7c55 | ||
|
8e3ff25f7b | ||
|
6e80c850f4 | ||
|
0608881954 | ||
|
38efd97b28 | ||
|
ce0ba6c0ca | ||
|
c43223a16d | ||
|
9f170a68d7 | ||
|
1a862e47ab | ||
|
e64bf0e3fe | ||
|
523d137e2b | ||
|
18169c59a1 | ||
|
4ccf263481 | ||
|
1c13a75970 | ||
|
c3e3f27457 | ||
|
794f1810bf | ||
|
168357d93c | ||
|
476deb9fec | ||
|
a36f2a75ca | ||
|
88afab6571 | ||
|
bd9c44cccf | ||
|
1b148786a5 | ||
|
66a10b8993 | ||
|
2ab21ccf8a | ||
|
90d0e8ccde | ||
|
16a396debb | ||
|
6b3d69e1d3 | ||
|
b3b8e9f3a0 | ||
|
e5345848a2 | ||
|
d8a8f6c08b | ||
|
f98a1ce077 | ||
|
86fa57449e | ||
|
ff51704cdf | ||
|
33804d8823 | ||
|
1e12ca4786 | ||
|
0af4ee6c34 | ||
|
1f29fabe64 | ||
|
c4e222d1e6 | ||
|
f2a1c26ef8 | ||
|
8772baad9a | ||
|
215c89e8d3 | ||
|
e6a055af19 | ||
|
88d71d2c7a | ||
|
cd2d5325df | ||
|
75a1245b70 | ||
|
f666eb6d83 | ||
|
cb10643f57 | ||
|
c9ba4e7e8b | ||
|
94187bca5d | ||
|
39b4aa5966 | ||
|
cd79df07e1 | ||
|
0c40f02584 | ||
|
db42c13e05 | ||
|
5f85d8f749 | ||
|
c0e273df5b | ||
|
4da1341aa5 | ||
|
e765e6a1b8 | ||
|
eee9a1f004 | ||
|
4d07b65bdd | ||
|
1772158d62 | ||
|
7bfdb82f5d | ||
|
8945316ce6 |
@@ -34,7 +34,12 @@ tsconfig.json
|
||||
/ecosystem.config.js
|
||||
/extra/healthcheck.exe
|
||||
/extra/healthcheck
|
||||
extra/exe-builder
|
||||
/extra/exe-builder
|
||||
/extra/push-examples
|
||||
/extra/uptime-kuma-push
|
||||
|
||||
# Comment the following line if you want to rebuild the healthcheck binary
|
||||
/extra/healthcheck-armv7
|
||||
|
||||
|
||||
### .gitignore content (commented rules are duplicated)
|
||||
|
8
.github/workflows/auto-test.yml
vendored
8
.github/workflows/auto-test.yml
vendored
@@ -5,11 +5,11 @@ name: Auto Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
branches: [ master, 1.23.X ]
|
||||
paths-ignore:
|
||||
- '*.md'
|
||||
pull_request:
|
||||
branches: [ master, 2.0.X ]
|
||||
branches: [ master, 1.23.X ]
|
||||
paths-ignore:
|
||||
- '*.md'
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- run: npm install npm@latest -g
|
||||
- run: npm install npm@9 -g
|
||||
- run: npm install
|
||||
- run: npm run build
|
||||
- run: npm test
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- run: npm install npm@latest -g
|
||||
- run: npm install npm@9 -g
|
||||
- run: npm ci --production
|
||||
|
||||
check-linters:
|
||||
|
@@ -2,13 +2,13 @@
|
||||
|
||||
First of all, I want to thank everyone who made pull requests for Uptime Kuma. I never thought the GitHub Community would be so nice! Because of this, I also never thought that other people would actually read and edit my code. It is not very well structured or commented, sorry about that.
|
||||
|
||||
The project was created with vite.js (vue3). Then I created a subdirectory called "server" for server part. Both frontend and backend share the same package.json.
|
||||
The project was created with vite.js (vue3). Then I created a subdirectory called "server" for the server part. Both frontend and backend share the same package.json.
|
||||
|
||||
The frontend code build into "dist" directory. The server (express.js) exposes the "dist" directory as root of the endpoint. This is how production is working.
|
||||
The frontend code builds into "dist" directory. The server (express.js) exposes the "dist" directory as the root of the endpoint. This is how production is working.
|
||||
|
||||
## Key Technical Skills
|
||||
|
||||
- Node.js (You should know what are promise, async/await and arrow function etc.)
|
||||
- Node.js (You should know about promise, async/await and arrow function etc.)
|
||||
- Socket.io
|
||||
- SCSS
|
||||
- Vue.js
|
||||
@@ -30,7 +30,7 @@ The frontend code build into "dist" directory. The server (express.js) exposes t
|
||||
|
||||
## Can I create a pull request for Uptime Kuma?
|
||||
|
||||
Yes or no, it depends on what you will try to do. Since I don't want to waste your time, be sure to **create an empty draft pull request or open an issue, so we can have a discussion first**. Especially for a large pull request or you don't know it will be merged or not.
|
||||
Yes or no, it depends on what you will try to do. Since I don't want to waste your time, be sure to **create an empty draft pull request or open an issue, so we can have a discussion first**. Especially for a large pull request or you don't know if it will be merged or not.
|
||||
|
||||
Here are some references:
|
||||
|
||||
@@ -46,8 +46,8 @@ Here are some references:
|
||||
- New features
|
||||
|
||||
### ❌ Won't be merged:
|
||||
- A dedicated pr for translating existing languages (see [these instructions](https://github.com/louislam/uptime-kuma/blob/master/src/lang/README.md))
|
||||
- Do not pass the auto test
|
||||
- A dedicated PR for translating existing languages (see [these instructions](https://github.com/louislam/uptime-kuma/blob/master/src/lang/README.md))
|
||||
- Do not pass the auto-test
|
||||
- Any breaking changes
|
||||
- Duplicated pull requests
|
||||
- Buggy
|
||||
@@ -61,9 +61,9 @@ The above cases may not cover all possible situations.
|
||||
|
||||
I (@louislam) have the final say. If your pull request does not meet my expectations, I will reject it, no matter how much time you spend on it. Therefore, it is essential to have a discussion beforehand.
|
||||
|
||||
I will mark your pull request in the [milestones](https://github.com/louislam/uptime-kuma/milestones), if I am plan to review and merge it.
|
||||
I will assign your pull request to a [milestone](https://github.com/louislam/uptime-kuma/milestones), if I plan to review and merge it.
|
||||
|
||||
Also, please don't rush or ask for ETA, because I have to understand the pull request, make sure it is no breaking changes and stick to my vision of this project, especially for large pull requests.
|
||||
Also, please don't rush or ask for an ETA, because I have to understand the pull request, make sure it is no breaking changes and stick to my vision of this project, especially for large pull requests.
|
||||
|
||||
|
||||
### Recommended Pull Request Guideline
|
||||
@@ -83,11 +83,11 @@ Before deep into coding, discussion first is preferred. Creating an empty pull r
|
||||
|
||||
## Project Styles
|
||||
|
||||
I personally do not like something that requires so many configurations before you can finally start the app. I hope Uptime Kuma installation could be as easy as like installing a mobile app.
|
||||
I personally do not like something that requires so many configurations before you can finally start the app. I hope Uptime Kuma installation will be as easy as like installing a mobile app.
|
||||
|
||||
- Easy to install for non-Docker users, no native build dependency is needed (for x86_64/armv7/arm64), no extra config, no extra effort required to get it running
|
||||
- Easy to install for non-Docker users, no native build dependency is needed (for x86_64/armv7/arm64), no extra config, and no extra effort required to get it running
|
||||
- Single container for Docker users, no very complex docker-compose file. Just map the volume and expose the port, then good to go
|
||||
- Settings should be configurable in the frontend. Environment variable is not encouraged, unless it is related to startup such as `DATA_DIR`
|
||||
- Settings should be configurable in the frontend. Environment variables are discouraged, unless it is related to startup such as `DATA_DIR`
|
||||
- Easy to use
|
||||
- The web UI styling should be consistent and nice
|
||||
|
||||
@@ -130,7 +130,7 @@ Port `3000` and port `3001` will be used.
|
||||
npm run dev
|
||||
```
|
||||
|
||||
But sometimes, you would like to keep restart the server, but not the frontend, you can run these command in two terminals:
|
||||
But sometimes, you would like to restart the server, but not the frontend, you can run these commands in two terminals:
|
||||
```
|
||||
npm run start-frontend-dev
|
||||
npm run start-server-dev
|
||||
@@ -146,13 +146,13 @@ It is mainly a socket.io app + express.js.
|
||||
express.js is used for:
|
||||
- entry point such as redirecting to a status page or the dashboard
|
||||
- serving the frontend built files (index.html, .js and .css etc.)
|
||||
- serving internal APIs of status page
|
||||
- serving internal APIs of the status page
|
||||
|
||||
|
||||
### Structure in /server/
|
||||
|
||||
- jobs/ (Jobs that are running in another process)
|
||||
- model/ (Object model, auto mapping to the database table name)
|
||||
- model/ (Object model, auto-mapping to the database table name)
|
||||
- modules/ (Modified 3rd-party modules)
|
||||
- monitor_types (Monitor Types)
|
||||
- notification-providers/ (individual notification logic)
|
||||
@@ -163,7 +163,7 @@ express.js is used for:
|
||||
|
||||
## Frontend Dev Server
|
||||
|
||||
It binds to `0.0.0.0:3000` by default. Frontend dev server is used for development only.
|
||||
It binds to `0.0.0.0:3000` by default. The frontend dev server is used for development only.
|
||||
|
||||
For production, it is not used. It will be compiled to `dist` directory instead.
|
||||
|
||||
@@ -181,7 +181,7 @@ Uptime Kuma Frontend is a single page application (SPA). Most paths are handled
|
||||
|
||||
The router is in `src/router.js`
|
||||
|
||||
As you can see, most data in frontend is stored in root level, even though you changed the current router to any other pages.
|
||||
As you can see, most data in the frontend is stored at the root level, even though you changed the current router to any other pages.
|
||||
|
||||
The data and socket logic are in `src/mixins/socket.js`.
|
||||
|
||||
@@ -210,7 +210,7 @@ Both frontend and backend share the same package.json. However, the frontend dep
|
||||
|
||||
### Update Dependencies
|
||||
|
||||
Since previously updating Vite 2.5.10 to 2.6.0 broke the application completely, from now on, it should update patch release version only.
|
||||
Since previously updating Vite 2.5.10 to 2.6.0 broke the application completely, from now on, it should update the patch release version only.
|
||||
|
||||
Patch release = the third digit ([Semantic Versioning](https://semver.org/))
|
||||
|
||||
@@ -218,17 +218,17 @@ If for security / bug / other reasons, a library must be updated, breaking chang
|
||||
|
||||
## Translations
|
||||
|
||||
Please add **all** the strings which are translatable to `src/lang/en.json` (If translation keys are ommited, they can not be translated).
|
||||
Please add **all** the strings which are translatable to `src/lang/en.json` (If translation keys are omitted, they can not be translated).
|
||||
|
||||
**Don't include any other languages in your inital Pull-Request** (even if this is your mother tounge), to avoid merge-conflicts between weblate and `master`.
|
||||
The translations can then (after merging a PR into `master`) be translated by awesome people donating their language-skills.
|
||||
**Don't include any other languages in your initial Pull-Request** (even if this is your mother tongue), to avoid merge-conflicts between weblate and `master`.
|
||||
The translations can then (after merging a PR into `master`) be translated by awesome people donating their language skills.
|
||||
|
||||
If you want to help by translating Uptime Kuma into your language, please visit the [instructions on how to translate using weblate](https://github.com/louislam/uptime-kuma/blob/master/src/lang/README.md).
|
||||
|
||||
## Spelling & Grammar
|
||||
|
||||
Feel free to correct the grammar in the documentation or code.
|
||||
My mother language is not english and my grammar is not that great.
|
||||
My mother language is not English and my grammar is not that great.
|
||||
|
||||
## Wiki
|
||||
|
||||
|
@@ -26,7 +26,7 @@ It is a temporary live demo, all data will be deleted after 10 minutes. Use the
|
||||
* Monitoring uptime for HTTP(s) / TCP / HTTP(s) Keyword / HTTP(s) Json Query / Ping / DNS Record / Push / Steam Game Server / Docker Containers
|
||||
* Fancy, Reactive, Fast UI/UX
|
||||
* Notifications via Telegram, Discord, Gotify, Slack, Pushover, Email (SMTP), and [90+ notification services, click here for the full list](https://github.com/louislam/uptime-kuma/tree/master/src/components/notifications)
|
||||
* 20 second intervals
|
||||
* 20-second intervals
|
||||
* [Multi Languages](https://github.com/louislam/uptime-kuma/tree/master/src/lang)
|
||||
* Multiple status pages
|
||||
* Map status pages to specific domains
|
||||
@@ -70,7 +70,7 @@ npm run setup
|
||||
# Option 1. Try it
|
||||
node server/server.js
|
||||
|
||||
# (Recommended) Option 2. Run in background using PM2
|
||||
# (Recommended) Option 2. Run in the background using PM2
|
||||
# Install PM2 if you don't have it:
|
||||
npm install pm2 -g && pm2 install pm2-logrotate
|
||||
|
||||
@@ -93,7 +93,7 @@ pm2 save && pm2 startup
|
||||
|
||||
### Windows Portable (x64)
|
||||
|
||||
https://github.com/louislam/uptime-kuma/files/11886108/uptime-kuma-win64-portable-1.0.1.zip
|
||||
https://github.com/louislam/uptime-kuma/releases/download/1.23.1/uptime-kuma-windows-x64-portable-1.23.1.zip
|
||||
|
||||
### Advanced Installation
|
||||
|
||||
@@ -109,7 +109,7 @@ https://github.com/louislam/uptime-kuma/wiki/%F0%9F%86%99-How-to-Update
|
||||
|
||||
## 🆕 What's Next?
|
||||
|
||||
I will mark requests/issues to the next milestone.
|
||||
I will assign requests/issues to the next milestone.
|
||||
|
||||
https://github.com/louislam/uptime-kuma/milestones
|
||||
|
||||
|
@@ -3,19 +3,19 @@
|
||||
## Reporting a Vulnerability
|
||||
|
||||
1. Please report security issues to https://github.com/louislam/uptime-kuma/security/advisories/new.
|
||||
1. Please also create a empty security issues for alerting me, as GitHub Advisory do not send a notification, I probably will miss without this. https://github.com/louislam/uptime-kuma/issues/new?assignees=&labels=help&template=security.md
|
||||
1. Please also create an empty security issue to alert me, as GitHub Advisories do not send a notification, I probably will miss it without this. https://github.com/louislam/uptime-kuma/issues/new?assignees=&labels=help&template=security.md
|
||||
|
||||
Do not use the public issue tracker or discuss it in the public as it will cause more damage.
|
||||
Do not use the public issue tracker or discuss it in public as it will cause more damage.
|
||||
|
||||
## Do you accept other 3rd-party bug bounty platforms?
|
||||
|
||||
At this moment, I DO NOT accept other bug bounty platforms, because I am not familiar with these platforms and someone have tried to send a phishing link to me by this already. To minimize my own risk, please report through GitHub Advisories only. I will ignore all 3rd-party bug bounty platforms emails.
|
||||
At this moment, I DO NOT accept other bug bounty platforms, because I am not familiar with these platforms and someone has tried to send a phishing link to me by doing this already. To minimize my own risk, please report through GitHub Advisories only. I will ignore all 3rd-party bug bounty platforms emails.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
### Uptime Kuma Versions
|
||||
|
||||
You should use or upgrade to the latest version of Uptime Kuma. All `1.X.X` versions are upgradable to the lastest version.
|
||||
You should use or upgrade to the latest version of Uptime Kuma. All `1.X.X` versions are upgradable to the latest version.
|
||||
|
||||
### Upgradable Docker Tags
|
||||
|
||||
|
32
db/patch-fix-kafka-producer-booleans.sql
Normal file
32
db/patch-fix-kafka-producer-booleans.sql
Normal file
@@ -0,0 +1,32 @@
|
||||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
-- Rename COLUMNs to another one (suffixed by `_old`)
|
||||
ALTER TABLE monitor
|
||||
RENAME COLUMN kafka_producer_ssl TO kafka_producer_ssl_old;
|
||||
|
||||
ALTER TABLE monitor
|
||||
RENAME COLUMN kafka_producer_allow_auto_topic_creation TO kafka_producer_allow_auto_topic_creation_old;
|
||||
|
||||
-- Add correct COLUMNs
|
||||
ALTER TABLE monitor
|
||||
ADD COLUMN kafka_producer_ssl BOOLEAN default 0 NOT NULL;
|
||||
|
||||
ALTER TABLE monitor
|
||||
ADD COLUMN kafka_producer_allow_auto_topic_creation BOOLEAN default 0 NOT NULL;
|
||||
|
||||
-- Set bring old values from `_old` COLUMNs to correct ones
|
||||
UPDATE monitor SET kafka_producer_allow_auto_topic_creation = monitor.kafka_producer_allow_auto_topic_creation_old
|
||||
WHERE monitor.kafka_producer_allow_auto_topic_creation_old IS NOT NULL;
|
||||
|
||||
UPDATE monitor SET kafka_producer_ssl = monitor.kafka_producer_ssl_old
|
||||
WHERE monitor.kafka_producer_ssl_old IS NOT NULL;
|
||||
|
||||
-- Remove old COLUMNs
|
||||
ALTER TABLE monitor
|
||||
DROP COLUMN kafka_producer_allow_auto_topic_creation_old;
|
||||
|
||||
ALTER TABLE monitor
|
||||
DROP COLUMN kafka_producer_ssl_old;
|
||||
|
||||
COMMIT;
|
10
db/patch-notification-config.sql
Normal file
10
db/patch-notification-config.sql
Normal file
@@ -0,0 +1,10 @@
|
||||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
-- SQLite: Change the data type of the column "config" from VARCHAR to TEXT
|
||||
ALTER TABLE notification RENAME COLUMN config TO config_old;
|
||||
ALTER TABLE notification ADD COLUMN config TEXT;
|
||||
UPDATE notification SET config = config_old;
|
||||
ALTER TABLE notification DROP COLUMN config_old;
|
||||
|
||||
COMMIT;
|
7
db/patch-timeout.sql
Normal file
7
db/patch-timeout.sql
Normal file
@@ -0,0 +1,7 @@
|
||||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
UPDATE monitor SET timeout = (interval * 0.8)
|
||||
WHERE timeout IS NULL OR timeout <= 0;
|
||||
|
||||
COMMIT;
|
@@ -1,6 +1,6 @@
|
||||
# DON'T UPDATE TO node:14-bullseye-slim, see #372.
|
||||
# If the image changed, the second stage image should be changed too
|
||||
FROM node:16-buster-slim
|
||||
# DON'T UPDATE TO bullseye-slim, see #372.
|
||||
# There is no 20-buster-slim for armv7 unfortunately, 18-buster-slim is the last one for Uptime Kuma v1.
|
||||
FROM node:18-buster-slim
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
WORKDIR /app
|
||||
|
@@ -189,13 +189,15 @@ if (type == "local") {
|
||||
bash("check=$(git --version)");
|
||||
if (check == "") {
|
||||
error = 1;
|
||||
println("Error: git is missing");
|
||||
println("Error: git is not found!");
|
||||
println("help: an installation guide is available at https://git-scm.com/book/en/v2/Getting-Started-Installing-Git");
|
||||
}
|
||||
|
||||
bash("check=$(node -v)");
|
||||
if (check == "") {
|
||||
error = 1;
|
||||
println("Error: node is missing");
|
||||
println("Error: node is not found");
|
||||
println("help: an installation guide is available at https://nodejs.org/en/download");
|
||||
}
|
||||
|
||||
if (error > 0) {
|
||||
@@ -216,6 +218,7 @@ if (type == "local") {
|
||||
bash("check=$(pm2 --version)");
|
||||
if (check == "") {
|
||||
println("Error: pm2 is not found!");
|
||||
println("help: an installation guide is available at https://pm2.keymetrics.io/docs/usage/quick-start/");
|
||||
bash("exit 1");
|
||||
}
|
||||
|
||||
@@ -232,6 +235,7 @@ if (type == "local") {
|
||||
bash("check=$(docker -v)");
|
||||
if (check == "") {
|
||||
println("Error: docker is not found!");
|
||||
println("help: an installation guide is available at https://docs.docker.com/desktop/");
|
||||
bash("exit 1");
|
||||
}
|
||||
|
||||
@@ -239,6 +243,7 @@ if (type == "local") {
|
||||
|
||||
bash("if [[ \"$check\" == *\"Is the docker daemon running\"* ]]; then
|
||||
\"echo\" \"Error: docker is not running\"
|
||||
\"echo\" \"help: a troubleshooting guide is available at https://docs.docker.com/config/daemon/troubleshoot/\"
|
||||
\"exit\" \"1\"
|
||||
fi");
|
||||
|
||||
|
@@ -156,12 +156,14 @@ fi
|
||||
check=$(git --version)
|
||||
if [ "$check" == "" ]; then
|
||||
error=$((1))
|
||||
"echo" "-e" "Error: git is missing"
|
||||
"echo" "-e" "Error: git is not found!"
|
||||
"echo" "-e" "help: an installation guide is available at https://git-scm.com/book/en/v2/Getting-Started-Installing-Git"
|
||||
fi
|
||||
check=$(node -v)
|
||||
if [ "$check" == "" ]; then
|
||||
error=$((1))
|
||||
"echo" "-e" "Error: node is missing"
|
||||
"echo" "-e" "Error: node is not found"
|
||||
"echo" "-e" "help: an installation guide is available at https://nodejs.org/en/download"
|
||||
fi
|
||||
if [ $(($error > 0)) == 1 ]; then
|
||||
"echo" "-e" "Please install above missing software"
|
||||
@@ -180,6 +182,7 @@ fi
|
||||
check=$(pm2 --version)
|
||||
if [ "$check" == "" ]; then
|
||||
"echo" "-e" "Error: pm2 is not found!"
|
||||
"echo" "-e" "help: an installation guide is available at https://pm2.keymetrics.io/docs/usage/quick-start/"
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p $installPath
|
||||
@@ -192,11 +195,13 @@ else
|
||||
check=$(docker -v)
|
||||
if [ "$check" == "" ]; then
|
||||
"echo" "-e" "Error: docker is not found!"
|
||||
"echo" "-e" "help: an installation guide is available at https://docs.docker.com/desktop/"
|
||||
exit 1
|
||||
fi
|
||||
check=$(docker info)
|
||||
if [[ "$check" == *"Is the docker daemon running"* ]]; then
|
||||
"echo" "Error: docker is not running"
|
||||
"echo" "help: a troubleshooting guide is available at https://docs.docker.com/config/daemon/troubleshoot/"
|
||||
"exit" "1"
|
||||
fi
|
||||
if [ "$3" != "" ]; then
|
||||
|
3995
package-lock.json
generated
3995
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
19
package.json
19
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "uptime-kuma",
|
||||
"version": "1.23.0",
|
||||
"version": "1.23.5-beta.0",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -40,7 +40,7 @@
|
||||
"build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain",
|
||||
"build-docker-pr-test": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64 -t louislam/uptime-kuma:pr-test --target pr-test . --push",
|
||||
"upload-artifacts": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg VERSION --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain",
|
||||
"setup": "git checkout 1.23.0 && npm ci --production && npm run download-dist",
|
||||
"setup": "git checkout 1.23.4 && npm ci --production && npm run download-dist",
|
||||
"download-dist": "node extra/download-dist.js",
|
||||
"mark-as-nightly": "node extra/mark-as-nightly.js",
|
||||
"reset-password": "node extra/reset-password.js",
|
||||
@@ -56,6 +56,9 @@
|
||||
"test-install-script-ubuntu1604": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/ubuntu1604.dockerfile .",
|
||||
"simple-dns-server": "node extra/simple-dns-server.js",
|
||||
"simple-mqtt-server": "node extra/simple-mqtt-server.js",
|
||||
"simple-mongo": "docker run --rm -p 27017:27017 mongo",
|
||||
"simple-postgres": "docker run --rm -p 5432:5432 -e POSTGRES_PASSWORD=postgres postgres",
|
||||
"simple-mariadb": "docker run --rm -p 3306:3306 -e MYSQL_ROOT_PASSWORD=mariadb# mariadb",
|
||||
"update-language-files": "cd extra/update-language-files && node index.js && cross-env-shell eslint ../../src/languages/$npm_config_language.js --fix",
|
||||
"ncu-patch": "npm-check-updates -u -t patch",
|
||||
"release-final": "node ./extra/test-docker.js && node extra/update-version.js && npm run build-docker && node ./extra/press-any-key.js && npm run upload-artifacts && node ./extra/update-wiki-version.js",
|
||||
@@ -95,7 +98,7 @@
|
||||
"express-basic-auth": "~1.2.1",
|
||||
"express-static-gzip": "~2.1.7",
|
||||
"form-data": "~4.0.0",
|
||||
"gamedig": "~4.0.5",
|
||||
"gamedig": "~4.1.0",
|
||||
"http-graceful-shutdown": "~3.1.7",
|
||||
"http-proxy-agent": "~5.0.0",
|
||||
"https-proxy-agent": "~5.0.1",
|
||||
@@ -108,10 +111,10 @@
|
||||
"kafkajs": "^2.2.4",
|
||||
"limiter": "~2.1.0",
|
||||
"liquidjs": "^10.7.0",
|
||||
"mongodb": "~4.14.0",
|
||||
"mongodb": "~4.17.1",
|
||||
"mqtt": "~4.3.7",
|
||||
"mssql": "~8.1.4",
|
||||
"mysql2": "~2.3.3",
|
||||
"mysql2": "~3.6.2",
|
||||
"nanoid": "~3.3.4",
|
||||
"node-cloudflared-tunnel": "~1.0.9",
|
||||
"node-radius-client": "~1.0.0",
|
||||
@@ -120,8 +123,8 @@
|
||||
"notp": "~2.0.3",
|
||||
"openid-client": "^5.4.2",
|
||||
"password-hash": "~1.2.2",
|
||||
"pg": "~8.8.0",
|
||||
"pg-connection-string": "~2.5.0",
|
||||
"pg": "~8.11.3",
|
||||
"pg-connection-string": "~2.6.2",
|
||||
"playwright-core": "~1.35.1",
|
||||
"prom-client": "~13.2.0",
|
||||
"prometheus-api-metrics": "~3.2.1",
|
||||
@@ -160,7 +163,7 @@
|
||||
"core-js": "~3.26.1",
|
||||
"cronstrue": "~2.24.0",
|
||||
"cross-env": "~7.0.3",
|
||||
"cypress": "^12.17.0",
|
||||
"cypress": "^13.2.0",
|
||||
"delay": "^5.0.0",
|
||||
"dns2": "~2.0.1",
|
||||
"dompurify": "~2.4.3",
|
||||
|
@@ -147,15 +147,18 @@ async function sendAPIKeyList(socket) {
|
||||
async function sendInfo(socket, hideVersion = false) {
|
||||
let version;
|
||||
let latestVersion;
|
||||
let isContainer;
|
||||
|
||||
if (!hideVersion) {
|
||||
version = checkVersion.version;
|
||||
latestVersion = checkVersion.latestVersion;
|
||||
isContainer = (process.env.UPTIME_KUMA_IS_CONTAINER === "1");
|
||||
}
|
||||
|
||||
socket.emit("info", {
|
||||
version,
|
||||
latestVersion,
|
||||
isContainer,
|
||||
primaryBaseURL: await setting("primaryBaseURL"),
|
||||
serverTimezone: await server.getTimezone(),
|
||||
serverTimezoneOffset: server.getTimezoneOffset(),
|
||||
|
@@ -81,6 +81,9 @@ class Database {
|
||||
"patch-monitor-oauth-cc.sql": true,
|
||||
"patch-add-timeout-monitor.sql": true,
|
||||
"patch-add-gamedig-given-port.sql": true,
|
||||
"patch-notification-config.sql": true,
|
||||
"patch-fix-kafka-producer-booleans.sql": true,
|
||||
"patch-timeout.sql": true,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@@ -80,8 +80,8 @@ class DockerHost {
|
||||
options.socketPath = dockerHost.dockerDaemon;
|
||||
} else if (dockerHost.dockerType === "tcp") {
|
||||
options.baseURL = DockerHost.patchDockerURL(dockerHost.dockerDaemon);
|
||||
options.httpsAgent = new https.Agent(DockerHost.getHttpsAgentOptions(dockerHost.dockerType, options.baseURL));
|
||||
}
|
||||
options.httpsAgent = new https.Agent(DockerHost.getHttpsAgentOptions(dockerHost.dockerType, options.baseURL));
|
||||
|
||||
let res = await axios.request(options);
|
||||
|
||||
|
@@ -3,10 +3,10 @@ const dayjs = require("dayjs");
|
||||
const axios = require("axios");
|
||||
const { Prometheus } = require("../prometheus");
|
||||
const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, TimeLogger, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND,
|
||||
SQL_DATETIME_FORMAT
|
||||
SQL_DATETIME_FORMAT, isDev, sleep, getRandomInt
|
||||
} = require("../../src/util");
|
||||
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, mqttAsync, setSetting, httpNtlm, radius, grpcQuery,
|
||||
redisPingAsync, mongodbPing, kafkaProducerAsync, getOidcTokenClientCredentials,
|
||||
redisPingAsync, mongodbPing, kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal
|
||||
} = require("../util-server");
|
||||
const { R } = require("redbean-node");
|
||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||
@@ -23,6 +23,8 @@ const Gamedig = require("gamedig");
|
||||
const jsonata = require("jsonata");
|
||||
const jwt = require("jsonwebtoken");
|
||||
|
||||
const rootCertificates = rootCertificatesFingerprints();
|
||||
|
||||
/**
|
||||
* status:
|
||||
* 0 = DOWN
|
||||
@@ -53,7 +55,7 @@ class Monitor extends BeanModel {
|
||||
obj.tags = await this.getTags();
|
||||
}
|
||||
|
||||
if (certExpiry && this.type === "http") {
|
||||
if (certExpiry && (this.type === "http" || this.type === "keyword" || this.type === "json-query") && this.getURLProtocol() === "https:") {
|
||||
const { certExpiryDaysRemaining, validCert } = await this.getCertExpiry(this.id);
|
||||
obj.certExpiryDaysRemaining = certExpiryDaysRemaining;
|
||||
obj.validCert = validCert;
|
||||
@@ -141,8 +143,8 @@ class Monitor extends BeanModel {
|
||||
expectedValue: this.expectedValue,
|
||||
kafkaProducerTopic: this.kafkaProducerTopic,
|
||||
kafkaProducerBrokers: JSON.parse(this.kafkaProducerBrokers),
|
||||
kafkaProducerSsl: this.kafkaProducerSsl === "1" && true || false,
|
||||
kafkaProducerAllowAutoTopicCreation: this.kafkaProducerAllowAutoTopicCreation === "1" && true || false,
|
||||
kafkaProducerSsl: this.getKafkaProducerSsl(),
|
||||
kafkaProducerAllowAutoTopicCreation: this.getKafkaProducerAllowAutoTopicCreation(),
|
||||
kafkaProducerMessage: this.kafkaProducerMessage,
|
||||
screenshot,
|
||||
};
|
||||
@@ -285,6 +287,22 @@ class Monitor extends BeanModel {
|
||||
return Boolean(this.gamedigGivenPortOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse to boolean
|
||||
* @returns {boolean} Kafka Producer Ssl enabled?
|
||||
*/
|
||||
getKafkaProducerSsl() {
|
||||
return Boolean(this.kafkaProducerSsl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse to boolean
|
||||
* @returns {boolean} Kafka Producer Allow Auto Topic Creation Enabled?
|
||||
*/
|
||||
getKafkaProducerAllowAutoTopicCreation() {
|
||||
return Boolean(this.kafkaProducerAllowAutoTopicCreation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start monitor
|
||||
* @param {Server} io Socket server instance
|
||||
@@ -310,6 +328,16 @@ class Monitor extends BeanModel {
|
||||
}
|
||||
}
|
||||
|
||||
// Evil
|
||||
if (isDev) {
|
||||
if (process.env.EVIL_RANDOM_MONITOR_SLEEP === "SURE") {
|
||||
if (getRandomInt(0, 100) === 0) {
|
||||
log.debug("evil", `[${this.name}] Evil mode: Random sleep: ` + beatInterval * 10000);
|
||||
await sleep(beatInterval * 10000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Expose here for prometheus update
|
||||
// undefined if not https
|
||||
let tlsInfo = undefined;
|
||||
@@ -339,6 +367,12 @@ class Monitor extends BeanModel {
|
||||
bean.duration = 0;
|
||||
}
|
||||
|
||||
// Runtime patch timeout if it is 0
|
||||
// See https://github.com/louislam/uptime-kuma/pull/3961#issuecomment-1804149144
|
||||
if (!this.timeout || this.timeout <= 0) {
|
||||
this.timeout = this.interval * 1000 * 0.8;
|
||||
}
|
||||
|
||||
try {
|
||||
if (await Monitor.isUnderMaintenance(this.id)) {
|
||||
bean.msg = "Monitor under maintenance";
|
||||
@@ -447,6 +481,7 @@ class Monitor extends BeanModel {
|
||||
validateStatus: (status) => {
|
||||
return checkStatusCode(status, this.getAcceptedStatuscodes());
|
||||
},
|
||||
signal: axiosAbortSignal(this.timeout * 1000),
|
||||
};
|
||||
|
||||
if (bodyValue) {
|
||||
@@ -581,13 +616,13 @@ class Monitor extends BeanModel {
|
||||
let dnsRes = await dnsResolve(this.hostname, this.dns_resolve_server, this.port, this.dns_resolve_type);
|
||||
bean.ping = dayjs().valueOf() - startTime;
|
||||
|
||||
if (this.dns_resolve_type === "A" || this.dns_resolve_type === "AAAA" || this.dns_resolve_type === "TXT") {
|
||||
if (this.dns_resolve_type === "A" || this.dns_resolve_type === "AAAA" || this.dns_resolve_type === "TXT" || this.dns_resolve_type === "PTR") {
|
||||
dnsMessage += "Records: ";
|
||||
dnsMessage += dnsRes.join(" | ");
|
||||
} else if (this.dns_resolve_type === "CNAME" || this.dns_resolve_type === "PTR") {
|
||||
dnsMessage = dnsRes[0];
|
||||
} else if (this.dns_resolve_type === "CNAME") {
|
||||
dnsMessage += dnsRes[0];
|
||||
} else if (this.dns_resolve_type === "CAA") {
|
||||
dnsMessage = dnsRes[0].issue;
|
||||
dnsMessage += dnsRes[0].issue;
|
||||
} else if (this.dns_resolve_type === "MX") {
|
||||
dnsRes.forEach(record => {
|
||||
dnsMessage += `Hostname: ${record.exchange} - Priority: ${record.priority} | `;
|
||||
@@ -605,7 +640,7 @@ class Monitor extends BeanModel {
|
||||
dnsMessage = dnsMessage.slice(0, -2);
|
||||
}
|
||||
|
||||
if (this.dnsLastResult !== dnsMessage) {
|
||||
if (this.dnsLastResult !== dnsMessage && dnsMessage !== undefined) {
|
||||
R.exec("UPDATE `monitor` SET dns_last_result = ? WHERE id = ? ", [
|
||||
dnsMessage,
|
||||
this.id
|
||||
@@ -704,8 +739,6 @@ class Monitor extends BeanModel {
|
||||
} else if (this.type === "docker") {
|
||||
log.debug("monitor", `[${this.name}] Prepare Options for Axios`);
|
||||
|
||||
const dockerHost = await R.load("docker_host", this.docker_host);
|
||||
|
||||
const options = {
|
||||
url: `/containers/${this.docker_container}/json`,
|
||||
timeout: this.interval * 1000 * 0.8,
|
||||
@@ -722,6 +755,12 @@ class Monitor extends BeanModel {
|
||||
}),
|
||||
};
|
||||
|
||||
const dockerHost = await R.load("docker_host", this.docker_host);
|
||||
|
||||
if (!dockerHost) {
|
||||
throw new Error("Failed to load docker host config");
|
||||
}
|
||||
|
||||
if (dockerHost._dockerType === "socket") {
|
||||
options.socketPath = dockerHost._dockerDaemon;
|
||||
} else if (dockerHost._dockerType === "tcp") {
|
||||
@@ -756,7 +795,7 @@ class Monitor extends BeanModel {
|
||||
} else if (this.type === "sqlserver") {
|
||||
let startTime = dayjs().valueOf();
|
||||
|
||||
await mssqlQuery(this.databaseConnectionString, this.databaseQuery);
|
||||
await mssqlQuery(this.databaseConnectionString, this.databaseQuery || "SELECT 1");
|
||||
|
||||
bean.msg = "";
|
||||
bean.status = UP;
|
||||
@@ -795,7 +834,7 @@ class Monitor extends BeanModel {
|
||||
} else if (this.type === "postgres") {
|
||||
let startTime = dayjs().valueOf();
|
||||
|
||||
await postgresQuery(this.databaseConnectionString, this.databaseQuery);
|
||||
await postgresQuery(this.databaseConnectionString, this.databaseQuery || "SELECT 1");
|
||||
|
||||
bean.msg = "";
|
||||
bean.status = UP;
|
||||
@@ -803,7 +842,11 @@ class Monitor extends BeanModel {
|
||||
} else if (this.type === "mysql") {
|
||||
let startTime = dayjs().valueOf();
|
||||
|
||||
bean.msg = await mysqlQuery(this.databaseConnectionString, this.databaseQuery);
|
||||
// Use `radius_password` as `password` field, since there are too many unnecessary fields
|
||||
// TODO: rename `radius_password` to `password` later for general use
|
||||
let mysqlPassword = this.radiusPassword;
|
||||
|
||||
bean.msg = await mysqlQuery(this.databaseConnectionString, this.databaseQuery || "SELECT 1", mysqlPassword);
|
||||
bean.status = UP;
|
||||
bean.ping = dayjs().valueOf() - startTime;
|
||||
} else if (this.type === "mongodb") {
|
||||
@@ -973,6 +1016,7 @@ class Monitor extends BeanModel {
|
||||
if (! this.isStop) {
|
||||
log.debug("monitor", `[${this.name}] SetTimeout for next check.`);
|
||||
this.heartbeatInterval = setTimeout(safeBeat, beatInterval * 1000);
|
||||
this.lastScheduleBeatTime = dayjs();
|
||||
} else {
|
||||
log.info("monitor", `[${this.name}] isStop = true, no next check.`);
|
||||
}
|
||||
@@ -982,7 +1026,9 @@ class Monitor extends BeanModel {
|
||||
/** Get a heartbeat and handle errors */
|
||||
const safeBeat = async () => {
|
||||
try {
|
||||
this.lastStartBeatTime = dayjs();
|
||||
await beat();
|
||||
this.lastEndBeatTime = dayjs();
|
||||
} catch (e) {
|
||||
console.trace(e);
|
||||
UptimeKumaServer.errorLog(e, false);
|
||||
@@ -991,6 +1037,9 @@ class Monitor extends BeanModel {
|
||||
if (! this.isStop) {
|
||||
log.info("monitor", "Try to restart the monitor");
|
||||
this.heartbeatInterval = setTimeout(safeBeat, this.interval * 1000);
|
||||
this.lastScheduleBeatTime = dayjs();
|
||||
} else {
|
||||
log.info("monitor", "isStop = true, no next check.");
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1075,6 +1124,19 @@ class Monitor extends BeanModel {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: http: or https:
|
||||
* @returns {(null|string)}
|
||||
*/
|
||||
getURLProtocol() {
|
||||
const url = this.getUrl();
|
||||
if (url) {
|
||||
return this.getUrl().protocol;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store TLS info to database
|
||||
* @param checkCertificateResult
|
||||
@@ -1411,7 +1473,10 @@ class Monitor extends BeanModel {
|
||||
let certInfo = tlsInfoObject.certInfo;
|
||||
while (certInfo) {
|
||||
let subjectCN = certInfo.subject["CN"];
|
||||
if (certInfo.daysRemaining > targetDays) {
|
||||
if (rootCertificates.has(certInfo.fingerprint256)) {
|
||||
log.debug("monitor", `Known root cert: ${certInfo.certType} certificate "${subjectCN}" (${certInfo.daysRemaining} days valid) on ${targetDays} deadline.`);
|
||||
break;
|
||||
} else if (certInfo.daysRemaining > targetDays) {
|
||||
log.debug("monitor", `No need to send cert notification for ${certInfo.certType} certificate "${subjectCN}" (${certInfo.daysRemaining} days valid) on ${targetDays} deadline.`);
|
||||
} else {
|
||||
log.debug("monitor", `call sendCertNotificationByTargetDays for ${targetDays} deadline on certificate ${subjectCN}.`);
|
||||
|
@@ -1,6 +1,8 @@
|
||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||
const passwordHash = require("../password-hash");
|
||||
const { R } = require("redbean-node");
|
||||
const jwt = require("jsonwebtoken");
|
||||
const { shake256, SHAKE256_LENGTH } = require("../util-server");
|
||||
|
||||
class User extends BeanModel {
|
||||
/**
|
||||
@@ -27,6 +29,19 @@ class User extends BeanModel {
|
||||
this.password = newPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new JWT for a user
|
||||
* @param {User} user
|
||||
* @param {string} jwtSecret
|
||||
* @return {string}
|
||||
*/
|
||||
static createJWT(user, jwtSecret) {
|
||||
return jwt.sign({
|
||||
username: user.username,
|
||||
h: shake256(user.password, SHAKE256_LENGTH),
|
||||
}, jwtSecret);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = User;
|
||||
|
@@ -30,6 +30,7 @@ class Discord extends NotificationProvider {
|
||||
break;
|
||||
case "port":
|
||||
case "dns":
|
||||
case "gamedig":
|
||||
case "steam":
|
||||
address = monitorJSON["hostname"];
|
||||
if (monitorJSON["port"]) {
|
||||
|
@@ -20,10 +20,10 @@ class Opsgenie extends NotificationProvider {
|
||||
|
||||
try {
|
||||
switch (notification.opsgenieRegion) {
|
||||
case "US":
|
||||
case "us":
|
||||
opsgenieAlertsUrl = opsgenieAlertsUrlUS;
|
||||
break;
|
||||
case "EU":
|
||||
case "eu":
|
||||
opsgenieAlertsUrl = opsgenieAlertsUrlEU;
|
||||
break;
|
||||
default:
|
||||
|
@@ -1,5 +1,12 @@
|
||||
let express = require("express");
|
||||
const { allowDevAllOrigin, allowAllOrigin, percentageToColor, filterAndJoin, sendHttpError } = require("../util-server");
|
||||
const {
|
||||
setting,
|
||||
allowDevAllOrigin,
|
||||
allowAllOrigin,
|
||||
percentageToColor,
|
||||
filterAndJoin,
|
||||
sendHttpError,
|
||||
} = require("../util-server");
|
||||
const { R } = require("redbean-node");
|
||||
const apicache = require("../modules/apicache");
|
||||
const Monitor = require("../model/monitor");
|
||||
@@ -22,10 +29,14 @@ router.get("/api/entry-page", async (request, response) => {
|
||||
allowDevAllOrigin(response);
|
||||
|
||||
let result = { };
|
||||
let hostname = request.hostname;
|
||||
if ((await setting("trustProxy")) && request.headers["x-forwarded-host"]) {
|
||||
hostname = request.headers["x-forwarded-host"];
|
||||
}
|
||||
|
||||
if (request.hostname in StatusPage.domainMappingList) {
|
||||
if (hostname in StatusPage.domainMappingList) {
|
||||
result.type = "statusPageMatchedDomain";
|
||||
result.statusPageSlug = StatusPage.domainMappingList[request.hostname];
|
||||
result.statusPageSlug = StatusPage.domainMappingList[hostname];
|
||||
} else {
|
||||
result.type = "entryPage";
|
||||
result.entryPage = server.entryPage;
|
||||
@@ -38,7 +49,7 @@ router.get("/api/push/:pushToken", async (request, response) => {
|
||||
|
||||
let pushToken = request.params.pushToken;
|
||||
let msg = request.query.msg || "OK";
|
||||
let ping = parseInt(request.query.ping) || null;
|
||||
let ping = parseFloat(request.query.ping) || null;
|
||||
let statusString = request.query.status || "up";
|
||||
let status = (statusString === "up") ? UP : DOWN;
|
||||
|
||||
|
@@ -83,8 +83,11 @@ const app = server.app;
|
||||
log.info("server", "Importing this project modules");
|
||||
log.debug("server", "Importing Monitor");
|
||||
const Monitor = require("./model/monitor");
|
||||
const User = require("./model/user");
|
||||
|
||||
log.debug("server", "Importing Settings");
|
||||
const { getSettings, setSettings, setting, initJWTSecret, checkLogin, startUnitTest, FBSD, doubleCheckPassword, startE2eTests } = require("./util-server");
|
||||
const { getSettings, setSettings, setting, initJWTSecret, checkLogin, startUnitTest, FBSD, doubleCheckPassword, startE2eTests, shake256, SHAKE256_LENGTH
|
||||
} = require("./util-server");
|
||||
|
||||
log.debug("server", "Importing Notification");
|
||||
const { Notification } = require("./notification");
|
||||
@@ -297,6 +300,11 @@ let needSetup = false;
|
||||
]);
|
||||
|
||||
if (user) {
|
||||
// Check if the password changed
|
||||
if (decoded.h !== shake256(user.password, SHAKE256_LENGTH)) {
|
||||
throw new Error("The token is invalid due to password change or old token");
|
||||
}
|
||||
|
||||
log.debug("auth", "afterLogin");
|
||||
afterLogin(socket, user);
|
||||
log.debug("auth", "afterLogin ok");
|
||||
@@ -316,9 +324,10 @@ let needSetup = false;
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
log.error("auth", `Invalid token. IP=${clientIP}`);
|
||||
|
||||
if (error.message) {
|
||||
log.error("auth", error.message, `IP=${clientIP}`);
|
||||
}
|
||||
callback({
|
||||
ok: false,
|
||||
msg: "Invalid token.",
|
||||
@@ -357,9 +366,7 @@ let needSetup = false;
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
token: jwt.sign({
|
||||
username: data.username,
|
||||
}, server.jwtSecret),
|
||||
token: User.createJWT(user, server.jwtSecret),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -387,9 +394,7 @@ let needSetup = false;
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
token: jwt.sign({
|
||||
username: data.username,
|
||||
}, server.jwtSecret),
|
||||
token: User.createJWT(user, server.jwtSecret),
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -726,11 +731,11 @@ let needSetup = false;
|
||||
bean.basic_auth_user = monitor.basic_auth_user;
|
||||
bean.basic_auth_pass = monitor.basic_auth_pass;
|
||||
bean.timeout = monitor.timeout;
|
||||
bean.oauth_client_id = monitor.oauth_client_id,
|
||||
bean.oauth_client_secret = monitor.oauth_client_secret,
|
||||
bean.oauth_auth_method = this.oauth_auth_method,
|
||||
bean.oauth_token_url = monitor.oauth_token_url,
|
||||
bean.oauth_scopes = monitor.oauth_scopes,
|
||||
bean.oauth_client_id = monitor.oauth_client_id;
|
||||
bean.oauth_client_secret = monitor.oauth_client_secret;
|
||||
bean.oauth_auth_method = monitor.oauth_auth_method;
|
||||
bean.oauth_token_url = monitor.oauth_token_url;
|
||||
bean.oauth_scopes = monitor.oauth_scopes;
|
||||
bean.tlsCa = monitor.tlsCa;
|
||||
bean.tlsCert = monitor.tlsCert;
|
||||
bean.tlsKey = monitor.tlsKey;
|
||||
@@ -784,6 +789,9 @@ let needSetup = false;
|
||||
bean.kafkaProducerAllowAutoTopicCreation = monitor.kafkaProducerAllowAutoTopicCreation;
|
||||
bean.kafkaProducerSaslOptions = JSON.stringify(monitor.kafkaProducerSaslOptions);
|
||||
bean.kafkaProducerMessage = monitor.kafkaProducerMessage;
|
||||
bean.kafkaProducerSsl = monitor.kafkaProducerSsl;
|
||||
bean.kafkaProducerAllowAutoTopicCreation =
|
||||
monitor.kafkaProducerAllowAutoTopicCreation;
|
||||
bean.gamedigGivenPortOnly = monitor.gamedigGivenPortOnly;
|
||||
|
||||
bean.validate();
|
||||
@@ -796,7 +804,7 @@ let needSetup = false;
|
||||
|
||||
await updateMonitorNotification(bean.id, monitor.notificationIDList);
|
||||
|
||||
if (bean.isActive()) {
|
||||
if (await bean.isActive()) {
|
||||
await restartMonitor(socket.userID, bean.id);
|
||||
}
|
||||
|
||||
@@ -1116,9 +1124,6 @@ let needSetup = false;
|
||||
value,
|
||||
]);
|
||||
|
||||
// Cleanup unused Tags
|
||||
await R.exec("delete from tag where ( select count(*) from monitor_tag mt where tag.id = mt.tag_id ) = 0");
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Deleted Successfully.",
|
||||
@@ -1197,6 +1202,7 @@ let needSetup = false;
|
||||
}
|
||||
|
||||
const previousChromeExecutable = await Settings.get("chromeExecutable");
|
||||
const previousNSCDStatus = await Settings.get("nscd");
|
||||
|
||||
await setSettings("general", data);
|
||||
server.entryPage = data.entryPage;
|
||||
@@ -1214,6 +1220,15 @@ let needSetup = false;
|
||||
await resetChrome();
|
||||
}
|
||||
|
||||
// Update nscd status
|
||||
if (previousNSCDStatus !== data.nscd) {
|
||||
if (data.nscd) {
|
||||
server.startNSCDServices();
|
||||
} else {
|
||||
server.stopNSCDServices();
|
||||
}
|
||||
}
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Saved"
|
||||
@@ -1875,8 +1890,10 @@ gracefulShutdown(server.httpServer, {
|
||||
});
|
||||
|
||||
// Catch unexpected errors here
|
||||
process.addListener("unhandledRejection", (error, promise) => {
|
||||
let unexpectedErrorHandler = (error, promise) => {
|
||||
console.trace(error);
|
||||
UptimeKumaServer.errorLog(error, false);
|
||||
console.error("If you keep encountering errors, please report to https://github.com/louislam/uptime-kuma/issues");
|
||||
});
|
||||
};
|
||||
process.addListener("unhandledRejection", unexpectedErrorHandler);
|
||||
process.addListener("uncaughtException", unexpectedErrorHandler);
|
||||
|
@@ -12,6 +12,7 @@ const { Settings } = require("./settings");
|
||||
const dayjs = require("dayjs");
|
||||
const childProcess = require("child_process");
|
||||
const path = require("path");
|
||||
const axios = require("axios");
|
||||
// DO NOT IMPORT HERE IF THE MODULES USED `UptimeKumaServer.getInstance()`, put at the bottom of this file instead.
|
||||
|
||||
/**
|
||||
@@ -62,6 +63,8 @@ class UptimeKumaServer {
|
||||
*/
|
||||
jwtSecret = null;
|
||||
|
||||
checkMonitorsInterval = null;
|
||||
|
||||
static getInstance(args) {
|
||||
if (UptimeKumaServer.instance == null) {
|
||||
UptimeKumaServer.instance = new UptimeKumaServer(args);
|
||||
@@ -75,6 +78,9 @@ class UptimeKumaServer {
|
||||
const sslCert = args["ssl-cert"] || process.env.UPTIME_KUMA_SSL_CERT || process.env.SSL_CERT || undefined;
|
||||
const sslKeyPassphrase = args["ssl-key-passphrase"] || process.env.UPTIME_KUMA_SSL_KEY_PASSPHRASE || process.env.SSL_KEY_PASSPHRASE || undefined;
|
||||
|
||||
// Set default axios timeout to 5 minutes instead of infinity
|
||||
axios.defaults.timeout = 300 * 1000;
|
||||
|
||||
log.info("server", "Creating express and socket.io instance");
|
||||
this.app = express();
|
||||
if (sslKey && sslCert) {
|
||||
@@ -341,7 +347,15 @@ class UptimeKumaServer {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async start() {
|
||||
this.startServices();
|
||||
let enable = await Settings.get("nscd");
|
||||
|
||||
if (enable || enable === null) {
|
||||
this.startNSCDServices();
|
||||
}
|
||||
|
||||
this.checkMonitorsInterval = setInterval(() => {
|
||||
this.checkMonitors();
|
||||
}, 60 * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -349,14 +363,20 @@ class UptimeKumaServer {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async stop() {
|
||||
this.stopServices();
|
||||
let enable = await Settings.get("nscd");
|
||||
|
||||
if (enable || enable === null) {
|
||||
this.stopNSCDServices();
|
||||
}
|
||||
|
||||
clearInterval(this.checkMonitorsInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start all system services (e.g. nscd)
|
||||
* For now, only used in Docker
|
||||
*/
|
||||
startServices() {
|
||||
startNSCDServices() {
|
||||
if (process.env.UPTIME_KUMA_IS_CONTAINER) {
|
||||
try {
|
||||
log.info("services", "Starting nscd");
|
||||
@@ -370,7 +390,7 @@ class UptimeKumaServer {
|
||||
/**
|
||||
* Stop all system services
|
||||
*/
|
||||
stopServices() {
|
||||
stopNSCDServices() {
|
||||
if (process.env.UPTIME_KUMA_IS_CONTAINER) {
|
||||
try {
|
||||
log.info("services", "Stopping nscd");
|
||||
@@ -380,6 +400,83 @@ class UptimeKumaServer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the specified monitor
|
||||
* @param {number} monitorID ID of monitor to start
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async startMonitor(monitorID) {
|
||||
log.info("manage", `Resume Monitor: ${monitorID} by server`);
|
||||
|
||||
await R.exec("UPDATE monitor SET active = 1 WHERE id = ?", [
|
||||
monitorID,
|
||||
]);
|
||||
|
||||
let monitor = await R.findOne("monitor", " id = ? ", [
|
||||
monitorID,
|
||||
]);
|
||||
|
||||
if (monitor.id in this.monitorList) {
|
||||
this.monitorList[monitor.id].stop();
|
||||
}
|
||||
|
||||
this.monitorList[monitor.id] = monitor;
|
||||
monitor.start(this.io);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restart a given monitor
|
||||
* @param {number} monitorID ID of monitor to start
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async restartMonitor(monitorID) {
|
||||
return await this.startMonitor(monitorID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if monitors are running properly
|
||||
*/
|
||||
async checkMonitors() {
|
||||
log.debug("monitor_checker", "Checking monitors");
|
||||
|
||||
for (let monitorID in this.monitorList) {
|
||||
let monitor = this.monitorList[monitorID];
|
||||
|
||||
// Not for push monitor
|
||||
if (monitor.type === "push") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!monitor.active) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check the lastStartBeatTime, if it is too long, then restart
|
||||
if (monitor.lastScheduleBeatTime ) {
|
||||
let diff = dayjs().diff(monitor.lastStartBeatTime, "second");
|
||||
|
||||
if (diff > monitor.interval * 1.5) {
|
||||
log.error("monitor_checker", `Monitor Interval: ${monitor.interval} Monitor ` + monitorID + " lastStartBeatTime diff: " + diff);
|
||||
log.error("monitor_checker", "Unexpected error: Monitor " + monitorID + " is struck for unknown reason");
|
||||
log.error("monitor_checker", "Last start beat time: " + R.isoDateTime(monitor.lastStartBeatTime));
|
||||
log.error("monitor_checker", "Last end beat time: " + R.isoDateTime(monitor.lastEndBeatTime));
|
||||
log.error("monitor_checker", "Last ScheduleBeatTime: " + R.isoDateTime(monitor.lastScheduleBeatTime));
|
||||
|
||||
// Restart
|
||||
log.error("monitor_checker", `Restarting monitor ${monitorID} automatically now`);
|
||||
this.restartMonitor(monitorID);
|
||||
} else {
|
||||
//log.debug("monitor_checker", "Monitor " + monitorID + " is running normally");
|
||||
}
|
||||
} else {
|
||||
//log.debug("monitor_checker", "Monitor " + monitorID + " is not started yet, skipp");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
log.debug("monitor_checker", "Checking monitors end");
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
@@ -22,6 +22,7 @@ const protojs = require("protobufjs");
|
||||
const radiusClient = require("node-radius-client");
|
||||
const redis = require("redis");
|
||||
const oidc = require("openid-client");
|
||||
const tls = require("tls");
|
||||
|
||||
const {
|
||||
dictionaries: {
|
||||
@@ -33,6 +34,7 @@ const dayjs = require("dayjs");
|
||||
// SASLOptions used in JSDoc
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { Kafka, SASLOptions } = require("kafkajs");
|
||||
const crypto = require("crypto");
|
||||
|
||||
const isWindows = process.platform === /^win/.test(process.platform);
|
||||
/**
|
||||
@@ -286,22 +288,22 @@ exports.kafkaProducerAsync = function (brokers, topic, message, options = {}, sa
|
||||
|
||||
producer.connect().then(
|
||||
() => {
|
||||
try {
|
||||
producer.send({
|
||||
topic: topic,
|
||||
messages: [{
|
||||
value: message,
|
||||
}],
|
||||
});
|
||||
connectedToKafka = true;
|
||||
clearTimeout(timeoutID);
|
||||
producer.send({
|
||||
topic: topic,
|
||||
messages: [{
|
||||
value: message,
|
||||
}],
|
||||
}).then((_) => {
|
||||
resolve("Message sent successfully");
|
||||
} catch (e) {
|
||||
}).catch((e) => {
|
||||
connectedToKafka = true;
|
||||
producer.disconnect();
|
||||
clearTimeout(timeoutID);
|
||||
reject(new Error("Error sending message: " + e.message));
|
||||
}
|
||||
}).finally(() => {
|
||||
connectedToKafka = true;
|
||||
clearTimeout(timeoutID);
|
||||
});
|
||||
}
|
||||
).catch(
|
||||
(e) => {
|
||||
@@ -313,8 +315,10 @@ exports.kafkaProducerAsync = function (brokers, topic, message, options = {}, sa
|
||||
);
|
||||
|
||||
producer.on("producer.network.request_timeout", (_) => {
|
||||
clearTimeout(timeoutID);
|
||||
reject(new Error("producer.network.request_timeout"));
|
||||
if (!connectedToKafka) {
|
||||
clearTimeout(timeoutID);
|
||||
reject(new Error("producer.network.request_timeout"));
|
||||
}
|
||||
});
|
||||
|
||||
producer.on("producer.disconnect", (_) => {
|
||||
@@ -392,6 +396,9 @@ exports.mssqlQuery = async function (connectionString, query) {
|
||||
try {
|
||||
pool = new mssql.ConnectionPool(connectionString);
|
||||
await pool.connect();
|
||||
if (!query) {
|
||||
query = "SELECT 1";
|
||||
}
|
||||
await pool.request().query(query);
|
||||
pool.close();
|
||||
} catch (e) {
|
||||
@@ -412,12 +419,22 @@ exports.postgresQuery = function (connectionString, query) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const config = postgresConParse(connectionString);
|
||||
|
||||
if (config.password === "") {
|
||||
// See https://github.com/brianc/node-postgres/issues/1927
|
||||
return reject(new Error("Password is undefined."));
|
||||
// Fix #3868, which true/false is not parsed to boolean
|
||||
if (typeof config.ssl === "string") {
|
||||
config.ssl = config.ssl === "true";
|
||||
}
|
||||
|
||||
const client = new Client({ connectionString });
|
||||
if (config.password === "") {
|
||||
// See https://github.com/brianc/node-postgres/issues/1927
|
||||
reject(new Error("Password is undefined."));
|
||||
return;
|
||||
}
|
||||
const client = new Client(config);
|
||||
|
||||
client.on("error", (error) => {
|
||||
log.debug("postgres", "Error caught in the error event handler.");
|
||||
reject(error);
|
||||
});
|
||||
|
||||
client.connect((err) => {
|
||||
if (err) {
|
||||
@@ -452,11 +469,15 @@ exports.postgresQuery = function (connectionString, query) {
|
||||
* Run a query on MySQL/MariaDB
|
||||
* @param {string} connectionString The database connection string
|
||||
* @param {string} query The query to validate the database with
|
||||
* @param {?string} password The password to use
|
||||
* @returns {Promise<(string)>}
|
||||
*/
|
||||
exports.mysqlQuery = function (connectionString, query) {
|
||||
exports.mysqlQuery = function (connectionString, query, password = undefined) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const connection = mysql.createConnection(connectionString);
|
||||
const connection = mysql.createConnection({
|
||||
uri: connectionString,
|
||||
password
|
||||
});
|
||||
|
||||
connection.on("error", (err) => {
|
||||
reject(err);
|
||||
@@ -1053,6 +1074,47 @@ module.exports.grpcQuery = async (options) => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an array of SHA256 fingerprints for all known root certificates.
|
||||
* @returns {Set} A set of SHA256 fingerprints.
|
||||
*/
|
||||
module.exports.rootCertificatesFingerprints = () => {
|
||||
let fingerprints = tls.rootCertificates.map(cert => {
|
||||
let certLines = cert.split("\n");
|
||||
certLines.shift();
|
||||
certLines.pop();
|
||||
let certBody = certLines.join("");
|
||||
let buf = Buffer.from(certBody, "base64");
|
||||
|
||||
const shasum = crypto.createHash("sha256");
|
||||
shasum.update(buf);
|
||||
|
||||
return shasum.digest("hex").toUpperCase().replace(/(.{2})(?!$)/g, "$1:");
|
||||
});
|
||||
|
||||
fingerprints.push("6D:99:FB:26:5E:B1:C5:B3:74:47:65:FC:BC:64:8F:3C:D8:E1:BF:FA:FD:C4:C2:F9:9B:9D:47:CF:7F:F1:C2:4F"); // ISRG X1 cross-signed with DST X3
|
||||
fingerprints.push("8B:05:B6:8C:C6:59:E5:ED:0F:CB:38:F2:C9:42:FB:FD:20:0E:6F:2F:F9:F8:5D:63:C6:99:4E:F5:E0:B0:27:01"); // ISRG X2 cross-signed with ISRG X1
|
||||
|
||||
return new Set(fingerprints);
|
||||
};
|
||||
|
||||
module.exports.SHAKE256_LENGTH = 16;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} data
|
||||
* @param {number} len
|
||||
* @return {string}
|
||||
*/
|
||||
module.exports.shake256 = (data, len) => {
|
||||
if (!data) {
|
||||
return "";
|
||||
}
|
||||
return crypto.createHash("shake256", { outputLength: len })
|
||||
.update(data)
|
||||
.digest("hex");
|
||||
};
|
||||
|
||||
// For unit test, export functions
|
||||
if (process.env.TEST_BACKEND) {
|
||||
module.exports.__test = {
|
||||
@@ -1062,3 +1124,30 @@ if (process.env.TEST_BACKEND) {
|
||||
return module.exports.__test[functionName];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an abort signal with the specified timeout.
|
||||
* @param {number} timeoutMs - The timeout in milliseconds.
|
||||
* @returns {AbortSignal | null} - The generated abort signal, or null if not supported.
|
||||
*/
|
||||
module.exports.axiosAbortSignal = (timeoutMs) => {
|
||||
try {
|
||||
// Just in case, as 0 timeout here will cause the request to be aborted immediately
|
||||
if (!timeoutMs || timeoutMs <= 0) {
|
||||
timeoutMs = 5000;
|
||||
}
|
||||
return AbortSignal.timeout(timeoutMs);
|
||||
} catch (_) {
|
||||
// v16-: AbortSignal.timeout is not supported
|
||||
try {
|
||||
const abortController = new AbortController();
|
||||
|
||||
setTimeout(() => abortController.abort(), timeoutMs);
|
||||
|
||||
return abortController.signal;
|
||||
} catch (_) {
|
||||
// v15-: AbortController is not supported
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="input-group mb-3">
|
||||
<select ref="select" v-model="model" class="form-select" :disabled="disabled">
|
||||
<option v-for="option in options" :key="option" :value="option.value">{{ option.label }}</option>
|
||||
<select ref="select" v-model="model" class="form-select" :disabled="disabled" :required="required">
|
||||
<option v-for="option in options" :key="option" :value="option.value" :disabled="option.disabled">{{ option.label }}</option>
|
||||
</select>
|
||||
<a class="btn btn-outline-primary" @click="action()">
|
||||
<a class="btn btn-outline-primary" :class="{ disabled: actionDisabled }" @click="action()">
|
||||
<font-awesome-icon :icon="icon" />
|
||||
</a>
|
||||
</div>
|
||||
@@ -50,6 +50,22 @@ export default {
|
||||
action: {
|
||||
type: Function,
|
||||
default: () => {},
|
||||
},
|
||||
/**
|
||||
* Whether the action button is disabled.
|
||||
* @example true
|
||||
*/
|
||||
actionDisabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* Whether the select field is required.
|
||||
* @example true
|
||||
*/
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
}
|
||||
},
|
||||
emits: [ "update:modelValue" ],
|
||||
|
@@ -70,7 +70,7 @@ export default {
|
||||
Confirm,
|
||||
},
|
||||
props: {},
|
||||
emits: [ "added" ],
|
||||
emits: [ "added", "deleted" ],
|
||||
data() {
|
||||
return {
|
||||
modal: null,
|
||||
@@ -167,6 +167,7 @@ export default {
|
||||
this.processing = false;
|
||||
|
||||
if (res.ok) {
|
||||
this.$emit("deleted", this.id);
|
||||
this.modal.hide();
|
||||
}
|
||||
});
|
||||
|
@@ -11,7 +11,7 @@
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="size !== 'small' && beatList.length > 4 && $root.styleElapsedTime !== 'none'"
|
||||
v-if="!$root.isMobile && size !== 'small' && beatList.length > 4 && $root.styleElapsedTime !== 'none'"
|
||||
class="d-flex justify-content-between align-items-center word" :style="timeStyle"
|
||||
>
|
||||
<div>{{ timeSinceFirstBeat }} ago</div>
|
||||
|
@@ -5,18 +5,18 @@
|
||||
<h1 class="h3 mb-3 fw-normal" />
|
||||
|
||||
<div v-if="!tokenRequired" class="form-floating">
|
||||
<input id="floatingInput" v-model="username" type="text" class="form-control" placeholder="Username">
|
||||
<input id="floatingInput" v-model="username" type="text" class="form-control" placeholder="Username" autocomplete="username" required>
|
||||
<label for="floatingInput">{{ $t("Username") }}</label>
|
||||
</div>
|
||||
|
||||
<div v-if="!tokenRequired" class="form-floating mt-3">
|
||||
<input id="floatingPassword" v-model="password" type="password" class="form-control" placeholder="Password">
|
||||
<input id="floatingPassword" v-model="password" type="password" class="form-control" placeholder="Password" autocomplete="current-password" required>
|
||||
<label for="floatingPassword">{{ $t("Password") }}</label>
|
||||
</div>
|
||||
|
||||
<div v-if="tokenRequired">
|
||||
<div class="form-floating mt-3">
|
||||
<input id="otp" v-model="token" type="text" maxlength="6" class="form-control" placeholder="123456">
|
||||
<input id="otp" v-model="token" type="text" maxlength="6" class="form-control" placeholder="123456" autocomplete="one-time-code" required>
|
||||
<label for="otp">{{ $t("Token") }}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -258,6 +258,10 @@ export default {
|
||||
<style lang="scss" scoped>
|
||||
@import "../assets/vars.scss";
|
||||
|
||||
.dropdown-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.clear-filters-btn {
|
||||
font-size: 0.8em;
|
||||
margin-right: 5px;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="dropdown" @focusin="open = true" @focusout="handleFocusOut">
|
||||
<div tabindex="-1" class="dropdown" @focusin="open = true" @focusout="handleFocusOut">
|
||||
<button type="button" class="filter-dropdown-status" :class="{ 'active': filterActive }" tabindex="0">
|
||||
<div class="px-1 d-flex align-items-center">
|
||||
<slot name="status"></slot>
|
||||
@@ -107,6 +107,19 @@ export default {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 5px;
|
||||
color: $link-color;
|
||||
|
||||
.dark & {
|
||||
color: $dark-font-color;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background-color: $highlight-white;
|
||||
|
||||
.dark & {
|
||||
background-color: $dark-font-color2;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
border: 1px solid $highlight;
|
||||
|
@@ -62,7 +62,7 @@
|
||||
</span>
|
||||
</div>
|
||||
<div class="extra-info">
|
||||
<div v-if="showCertificateExpiry && monitor.element.type === 'http'">
|
||||
<div v-if="showCertificateExpiry && monitor.element.certExpiryDaysRemaining">
|
||||
<Tag :item="{name: $t('Cert Exp.'), value: formattedCertExpiryMessage(monitor), color: certExpiryColor(monitor)}" :size="'sm'" />
|
||||
</div>
|
||||
<div v-if="showTags">
|
||||
|
@@ -28,6 +28,7 @@
|
||||
v-model="currentPassword"
|
||||
type="password"
|
||||
class="form-control"
|
||||
autocomplete="current-password"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
@@ -43,7 +44,7 @@
|
||||
<div v-if="uri && twoFAStatus == false" class="mt-3">
|
||||
<label for="basic-url" class="form-label">{{ $t("twoFAVerifyLabel") }}</label>
|
||||
<div class="input-group">
|
||||
<input v-model="token" type="text" maxlength="6" class="form-control">
|
||||
<input v-model="token" type="text" maxlength="6" class="form-control" autocomplete="one-time-code" required>
|
||||
<button class="btn btn-outline-primary" type="button" @click="verifyToken()">{{ $t("Verify Token") }}</button>
|
||||
</div>
|
||||
<p v-show="tokenValid" class="mt-2" style="color: green;">{{ $t("tokenValidSettingsMsg") }}</p>
|
||||
|
@@ -150,6 +150,43 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DNS Cache (nscd) -->
|
||||
<div v-if="$root.info.isContainer" class="mb-4">
|
||||
<label class="form-label">
|
||||
{{ $t("enableNSCD") }}
|
||||
</label>
|
||||
|
||||
<div class="form-check">
|
||||
<input
|
||||
id="nscdEnable"
|
||||
v-model="settings.nscd"
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
name="nscd"
|
||||
:value="true"
|
||||
required
|
||||
/>
|
||||
<label class="form-check-label" for="nscdEnable">
|
||||
{{ $t("Enable") }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<input
|
||||
id="nscdDisable"
|
||||
v-model="settings.nscd"
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
name="nscd"
|
||||
:value="false"
|
||||
required
|
||||
/>
|
||||
<label class="form-check-label" for="nscdDisable">
|
||||
{{ $t("Disable") }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DNS Cache -->
|
||||
<div class="mb-4">
|
||||
<label class="form-label">
|
||||
|
@@ -19,6 +19,7 @@
|
||||
v-model="password.currentPassword"
|
||||
type="password"
|
||||
class="form-control"
|
||||
autocomplete="current-password"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
@@ -32,6 +33,7 @@
|
||||
v-model="password.newPassword"
|
||||
type="password"
|
||||
class="form-control"
|
||||
autocomplete="new-password"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
@@ -46,6 +48,7 @@
|
||||
type="password"
|
||||
class="form-control"
|
||||
:class="{ 'is-invalid': invalidPassword }"
|
||||
autocomplete="new-password"
|
||||
required
|
||||
/>
|
||||
<div class="invalid-feedback">
|
||||
|
@@ -683,5 +683,6 @@
|
||||
"languageName": "العربية",
|
||||
"Game": "الألعاب",
|
||||
"List": "القائمة",
|
||||
"statusMaintenance": "الصيانة"
|
||||
"statusMaintenance": "الصيانة",
|
||||
"Home": "الرئيسة"
|
||||
}
|
||||
|
@@ -369,6 +369,8 @@
|
||||
"Setup Docker Host": "Setup Docker Host",
|
||||
"Connection Type": "Connection Type",
|
||||
"Docker Daemon": "Docker Daemon",
|
||||
"noDockerHostMsg": "Not Available. Setup a Docker Host First.",
|
||||
"DockerHostRequired": "Please set the Docker Host for this monitor.",
|
||||
"deleteDockerHostMsg": "Are you sure want to delete this docker host for all monitors?",
|
||||
"socket": "Socket",
|
||||
"tcp": "TCP / HTTP",
|
||||
@@ -449,9 +451,10 @@
|
||||
"Server Timezone": "Server Timezone",
|
||||
"statusPageMaintenanceEndDate": "End",
|
||||
"IconUrl": "Icon URL",
|
||||
"Enable DNS Cache": "Enable DNS Cache for HTTP(s) monitors",
|
||||
"Enable DNS Cache": "(Deprecated) Enable DNS Cache for HTTP(s) monitors",
|
||||
"Enable": "Enable",
|
||||
"Disable": "Disable",
|
||||
"enableNSCD": "Enable NSCD (Name Service Cache Daemon) for caching all DNS requests",
|
||||
"chromeExecutable": "Chrome/Chromium Executable",
|
||||
"chromeExecutableAutoDetect": "Auto Detect",
|
||||
"chromeExecutableDescription": "For Docker users, if Chromium is not yet installed, it may take a few minutes to install and display the test result. It takes 1GB of disk space.",
|
||||
|
@@ -569,5 +569,18 @@
|
||||
"dayOfMonth": "Hilabeteko eguna",
|
||||
"lastDay": "Azken eguna",
|
||||
"lastDay1": "Hilabeteko azken eguna",
|
||||
"Resend Notification if Down X times consecutively": "Bidali jakinarazpena X aldiz jarraian erortzen bada"
|
||||
"Resend Notification if Down X times consecutively": "Bidali jakinarazpena X aldiz jarraian erortzen bada",
|
||||
"Add New Tag": "Gehitu etiketa berria",
|
||||
"Schedule maintenance": "Programatu mantenua",
|
||||
"Start of maintenance": "Mantenuaren hasiera",
|
||||
"All Status Pages": "Egoera orrialde guztiak",
|
||||
"Custom": "Pertsonalizatua",
|
||||
"showCertificateExpiry": "Erakutsi ziurtagiriaren iraungitzea",
|
||||
"Close": "Itxi",
|
||||
"Request Body": "Eskaera gorputza",
|
||||
"Mechanism": "Mekanismoa",
|
||||
"Home": "Hasiera",
|
||||
"filterActive": "Aktibo",
|
||||
"filterActivePaused": "Geldituta",
|
||||
"Expected Value": "Esperotako balioa"
|
||||
}
|
||||
|
@@ -739,5 +739,6 @@
|
||||
"pagertreeHigh": "גבוהה",
|
||||
"pagertreeCritical": "קריטי",
|
||||
"pagertreeResolve": "הגדרה אוטומטית",
|
||||
"ntfyUsernameAndPassword": "שם משתמש וסיסמא"
|
||||
"ntfyUsernameAndPassword": "שם משתמש וסיסמא",
|
||||
"Cannot connect to the socket server": "לא ניתן להתחבר לשקע השרת"
|
||||
}
|
||||
|
@@ -177,7 +177,7 @@
|
||||
"Entry Page": "Pagina Principale",
|
||||
"statusPageNothing": "Non c'è nulla qui, aggiungi un gruppo oppure un monitor.",
|
||||
"No Services": "Nessun servizio",
|
||||
"All Systems Operational": "Tutti i sistemi sono funzionali",
|
||||
"All Systems Operational": "Tutti i sistemi sono funzionanti",
|
||||
"Partially Degraded Service": "Servizio parzialmente degradato",
|
||||
"Degraded Service": "Servizio degradato",
|
||||
"Add Group": "Aggiungi gruppo",
|
||||
@@ -608,5 +608,7 @@
|
||||
"Cannot connect to the socket server": "Impossibile connettersi al server socket",
|
||||
"Reconnecting...": "Riconnessione...",
|
||||
"Expected Value": "Valore atteso",
|
||||
"Json Query": "Query Json"
|
||||
"Json Query": "Query Json",
|
||||
"deleteMaintenanceMsg": "Sei sicuro di voler cancellare questa attività di manutenzione?",
|
||||
"dnsPortDescription": "Porta server DNS. Default 53. Puoi cambiare questa porta in ogni momento."
|
||||
}
|
||||
|
@@ -215,5 +215,6 @@
|
||||
"Cannot connect to the socket server": "Nemožno sa pripojiť k socket serveru",
|
||||
"Invert Keyword": "Prevrátiť kľúčové slovo",
|
||||
"Expected Value": "Očakávaná hodnota",
|
||||
"Json Query": "Json Query"
|
||||
"Json Query": "Json Query",
|
||||
"Method": "Metoda"
|
||||
}
|
||||
|
191
src/lang/te.json
Normal file
191
src/lang/te.json
Normal file
@@ -0,0 +1,191 @@
|
||||
{
|
||||
"languageName": "తెలుగు",
|
||||
"Settings": "సెట్టింగ్లు",
|
||||
"Dashboard": "డాష్బోర్డ్",
|
||||
"Help": "సహాయం",
|
||||
"Language": "భాష",
|
||||
"Appearance": "ప్రదర్శన",
|
||||
"Theme": "నేపథ్యం",
|
||||
"General": "సాధారణ",
|
||||
"Game": "ఆట",
|
||||
"Version": "సంస్కరణ",
|
||||
"Check Update On GitHub": "GitHubలో నవీకరణను తనిఖీ చేయండి",
|
||||
"List": "జాబితా",
|
||||
"Home": "స్వస్థల o",
|
||||
"Add": "జోడించు",
|
||||
"Quick Stats": "త్వరిత గణాంకాలు",
|
||||
"Up": "పనిచేస్తుంది",
|
||||
"Down": "ఆగిపోయింది",
|
||||
"Pending": "అనిశ్చిత",
|
||||
"Maintenance": "నిర్వహణ",
|
||||
"Unknown": "తెలియని స్థితి",
|
||||
"Reconnecting...": "మళ్లీ కనెక్ట్ అవుతోంది...",
|
||||
"General Monitor Type": "సాధారణ మానిటర్ రకం",
|
||||
"Passive Monitor Type": "నిష్క్రియాత్మక మానిటర్ రకం",
|
||||
"markdownSupported": "మార్క్డౌన్ సింటాక్స్కు మద్దతు ఉంది",
|
||||
"Pause": "విరామం",
|
||||
"Name": "పేరు",
|
||||
"Status": "స్థితి",
|
||||
"DateTime": "తేదీ సమయం",
|
||||
"Message": "సందేశం",
|
||||
"No important events": "ముఖ్యమైన సంఘటనలు లేవు",
|
||||
"Resume": "పునఃప్రారంభం",
|
||||
"Edit": "సవరించు",
|
||||
"Current": "ప్రస్తుత",
|
||||
"Uptime": "సమయ వ్యవధి",
|
||||
"Monitor": "మానిటర్ | మానిటర్లు",
|
||||
"day": "రోజు | రోజులు",
|
||||
"-day": "-రోజు",
|
||||
"hour": "గంట",
|
||||
"-hour": "-గంట",
|
||||
"Response": "ప్రతిస్పందన",
|
||||
"Ping": "పింగ్",
|
||||
"Keyword": "కీవర్డ్",
|
||||
"Invert Keyword": "విలోమ కీవర్డ్",
|
||||
"Expected Value": "అంచనా విలువ",
|
||||
"Json Query": "Json ప్రశ్న",
|
||||
"URL": "URL",
|
||||
"Hostname": "హోస్ట్ పేరు",
|
||||
"Port": "పోర్ట్",
|
||||
"Heartbeat Interval": "హృదయ స్పందన విరామం",
|
||||
"Request Timeout": "అభ్యర్థన ముగిసె గడువు",
|
||||
"timeoutAfter": "{0} సెకన్ల తర్వాత గడువు ముగిసింది",
|
||||
"Heartbeat Retry Interval": "హృదయ స్పందన పునఃప్రయత్న విరామం",
|
||||
"Advanced": "ఆధునిక",
|
||||
"checkEverySecond": "ప్రతి {0} సెకన్లకు తనిఖీ చేయండి",
|
||||
"retryCheckEverySecond": "ప్రతి {0} సెకన్లకు మళ్లీ ప్రయత్నించండి",
|
||||
"resendDisabled": "మల్లిపంపడము అచేతనము చేయబడ్డది",
|
||||
"ignoreTLSError": "HTTPS వెబ్సైట్ల కోసం TLS/SSL లోపాన్ని విస్మరించండి",
|
||||
"maxRedirectDescription": "అనుసరించాల్సిన దారి మళ్లింపుల గరిష్ట సంఖ్య. దారి మళ్లింపులను నిలిపివేయడానికి 0కి సెట్ చేయండి.",
|
||||
"Upside Down Mode": "అప్సైడ్ డౌన్ మోడ్",
|
||||
"Max. Redirects": "గరిష్టంగా దారి మళ్లింపులు",
|
||||
"Push URL": "పుష్ URL",
|
||||
"needPushEvery": "మీరు ప్రతి {0} సెకన్లకు ఈ URLకి కాల్ చేయాలి.",
|
||||
"Save": "సేవ్ చేయండి",
|
||||
"Notifications": "నోటిఫికేషన్లు",
|
||||
"Setup Notification": "నోటిఫికేషన్ సెటప్ చేయండి",
|
||||
"Light": "కాంతి",
|
||||
"Dark": "వెలుతురు లేని",
|
||||
"Auto": "ఆటో",
|
||||
"Theme - Heartbeat Bar": "థీమ్ - హార్ట్బీట్ బార్",
|
||||
"styleElapsedTimeShowNoLine": "చూపించు (పంక్తి లేదు)",
|
||||
"styleElapsedTimeShowWithLine": "చూపించు (పంక్తితో)",
|
||||
"Normal": "సాధారణ",
|
||||
"Bottom": "దిగువన",
|
||||
"None": "ఏదీ లేదు",
|
||||
"Timezone": "సమయమండలం",
|
||||
"Allow indexing": "ఇండెక్సింగ్ని అనుమతించండి",
|
||||
"Change Password": "పాస్వర్డ్ మార్చండి",
|
||||
"Current Password": "ప్రస్తుత పాస్వర్డ్",
|
||||
"New Password": "కొత్త పాస్వర్డ్",
|
||||
"Repeat New Password": "కొత్త పాస్వర్డ్ని మళ్లీ నమోదు చేయండి",
|
||||
"Disable Auth": "ప్రామాణీకరణను నిలిపివేయండి",
|
||||
"Enable Auth": "ప్రామాణీకరణను ప్రారంభించండి",
|
||||
"Please use this option carefully!": "దయచేసి ఈ ఎంపికను జాగ్రత్తగా ఉపయోగించండి!",
|
||||
"Logout": "లాగ్అవుట్",
|
||||
"Leave": "వదిలేయండి",
|
||||
"Confirm": "నిర్ధారించండి",
|
||||
"Yes": "అవును",
|
||||
"No": "లేదు",
|
||||
"Username": "వినియోగదారు పేరు",
|
||||
"Password": "పాస్వర్డ్",
|
||||
"Remember me": "నన్ను గుర్తు పెట్టుకో",
|
||||
"Login": "లాగిన్",
|
||||
"add one": "ఒకటి జోడించండి",
|
||||
"Notification Type": "నోటిఫికేషన్ రకం",
|
||||
"Email": "ఇమెయిల్",
|
||||
"Test": "పరీక్షించండి",
|
||||
"Resolver Server": "రిసోల్వర్ సర్వర్",
|
||||
"Resource Record Type": "రిసోర్స్ రికార్డ్ రకం",
|
||||
"Last Result": "చివరి ఫలితం",
|
||||
"Repeat Password": "పాస్వర్డ్ని మళ్లీ నమోదు చేయండి",
|
||||
"Import Backup": "బ్యాకప్ని దిగుమతి చేయండి",
|
||||
"Export": "ఎగుమతి",
|
||||
"Import": "దిగుమతి",
|
||||
"respTime": "ప్రతిస్పందన. సమయం (మిసె)",
|
||||
"Default enabled": "డిఫాల్ట్ ప్రారంభించబడింది",
|
||||
"Create": "సృష్టించు",
|
||||
"Clear Data": "డేటాను క్లియర్ చేయండి",
|
||||
"Events": "ఈవెంట్స్",
|
||||
"Heartbeats": "హృదయ స్పందన",
|
||||
"Auto Get": "స్వయంచాలక పొందండి",
|
||||
"Affected Monitors": "ప్రభావిత మానిటర్లు",
|
||||
"Pick Affected Monitors...": "ప్రభావిత మానిటర్లను ఎంచుకోండి…",
|
||||
"All Status Pages": "అన్ని స్థితి పేజీలు",
|
||||
"Select status pages...": "స్థితి పేజీలను ఎంచుకోండి…",
|
||||
"alertWrongFileType": "దయచేసి JSON ఫైల్ని ఎంచుకోండి.",
|
||||
"Clear all statistics": "అన్ని గణాంకాలను క్లియర్ చేయండి",
|
||||
"Skip existing": "ఉనికిని దాటవేయి",
|
||||
"Options": "ఎంపికలు",
|
||||
"Keep both": "రెండు ఉంచండి",
|
||||
"Verify Token": "టోకెన్ని ధృవీకరించండి",
|
||||
"Setup 2FA": "సెటప్ 2FA",
|
||||
"Disable 2FA": "2FAని నిలిపివేయండి",
|
||||
"2FA Settings": "2FA సెట్టింగ్లు",
|
||||
"filterActive": "చురుకుగా",
|
||||
"filterActivePaused": "ఆగిపోయింది",
|
||||
"Active": "చురుకుగా",
|
||||
"Inactive": "నిష్క్రియ",
|
||||
"Token": "టోకెన్",
|
||||
"Tags": "టాగ్లు",
|
||||
"Add New Tag": "కొత్త ట్యాగ్ని జోడించండి",
|
||||
"Tag with this name already exist.": "ఈ పేరుతో ట్యాగ్ ఇప్పటికే ఉంది.",
|
||||
"color": "రంగు",
|
||||
"value (optional)": "విలువ (ఐచ్ఛికం)",
|
||||
"Gray": "బూడిద రంగు",
|
||||
"Red": "ఎరుపు",
|
||||
"Orange": "నారింజ రంగు",
|
||||
"Green": "ఆకుపచ్చ",
|
||||
"Blue": "నీలం",
|
||||
"Indigo": "నీలిమందు రంగు",
|
||||
"Purple": "ఊదా రంగు",
|
||||
"Pink": "పింక్ కలర్",
|
||||
"Search...": "వెతకండి…",
|
||||
"Avg. Ping": "సగటు పింగ్",
|
||||
"Avg. Response": "సగటు ప్రతిస్పందన",
|
||||
"statusPageRefreshIn": "సెకన్లలో రిఫ్రెష్ చేయండి: {0}",
|
||||
"New Update": "కొత్త నవీకరణ",
|
||||
"Primary Base URL": "ప్రాథమిక URL",
|
||||
"Add New Monitor": "కొత్త మానిటర్ని జోడించండి",
|
||||
"statusMaintenance": "స్థితి నిర్వహణ",
|
||||
"Cannot connect to the socket server": "సాకెట్ సర్వర్కి కనెక్ట్ చేయడం సాధ్యపడదు",
|
||||
"Specific Monitor Type": "నిర్దిష్ట మానిటర్ రకం",
|
||||
"pauseDashboardHome": "డాష్బోర్డ్ హోమ్నకు విరామం",
|
||||
"Delete": "తొలగించు",
|
||||
"Cert Exp.": "సర్టిఫికేట్ గడువు.",
|
||||
"Monitor Type": "మానిటర్ రకం",
|
||||
"Friendly Name": "స్నేహపూర్వక పేరు",
|
||||
"Retries": "పునఃప్రయత్నాలు",
|
||||
"Resend Notification if Down X times consecutively": "వరుసగా X సార్లు డౌన్ అయితే నోటిఫికేషన్ని మళ్లీ పంపండి",
|
||||
"resendEveryXTimes": "ప్రతి {0} సార్లు మళ్లీ పంపండి",
|
||||
"retriesDescription": "సేవ డౌన్గా గుర్తించబడి నోటిఫికేషన్ పంపబడటానికి ముందు గరిష్ట సంఖ్యలో పునఃప్రయత్నాలు",
|
||||
"upsideDownModeDescription": "స్థితిని తలక్రిందులుగా తిప్పండి. సేవ చేరుకోగలిగితే, అది పని చేయనట్లు పరిగణించబడుతుంది.",
|
||||
"Accepted Status Codes": "ఆమోదించబడిన HTTP స్థితి కోడ్లు",
|
||||
"pushOptionalParams": "ఐచ్ఛిక పారామితులు: {0}",
|
||||
"Not available, please setup.": "అందుబాటులో లేదు, దయచేసి సెటప్ చేయండి.",
|
||||
"styleElapsedTime": "హృదయ స్పందన పట్టీ కింద గడిచిన సమయం",
|
||||
"Search Engine Visibility": "శోధన ఇంజిన్ దృశ్యమానత",
|
||||
"Discourage search engines from indexing site": "ఇండెక్సింగ్ సైట్ నుండి శోధన ఇంజిన్లను నిరుత్సాహపరచండి",
|
||||
"Update Password": "పాస్వర్డ్ని నవీకరించండి",
|
||||
"disableauth.message1": "మీరు ఖచ్చితంగా <strong>ప్రామాణీకరణను నిలిపివేయాలనుకుంటున్నారా</strong>?",
|
||||
"disableauth.message2": "ఇది Cloudflare యాక్సెస్, Authelia లేదా ఇతర ప్రమాణీకరణ మెకానిజమ్ల వంటి Uptime Kuma ముందు <strong>థర్డ్-పార్టీ ప్రామాణీకరణను అమలు చేయాలనుకుంటున్న</strong> దృశ్యాల కోసం రూపొందించబడింది.",
|
||||
"I understand, please disable": "నాకు అర్థమైంది, దయచేసి నిలిపివేయండి",
|
||||
"No Monitors, please": "దయచేసి మానిటర్లు వద్దు",
|
||||
"Certificate Info": "సర్టిఫికేట్ సమాచారం",
|
||||
"Create your admin account": "మీ నిర్వాహక ఖాతాను సృష్టించండి",
|
||||
"Export Backup": "బ్యాకప్ ఎగుమతి",
|
||||
"notAvailableShort": "లేదు/అందుబాటులో లేదు",
|
||||
"Apply on all existing monitors": "ఇప్పటికే ఉన్న అన్ని మానిటర్లపై వర్తించండి",
|
||||
"Schedule maintenance": "షెడ్యూల్ నిర్వహణ",
|
||||
"Start of maintenance": "నిర్వహణ ప్రారంభం",
|
||||
"alertNoFile": "దయచేసి దిగుమతి చేయడానికి ఫైల్ను ఎంచుకోండి.",
|
||||
"Overwrite": "ఓవర్రైట్",
|
||||
"Enable 2FA": "2FAని ప్రారంభించండి",
|
||||
"Two Factor Authentication": "రెండు కారకాల ప్రమాణీకరణ",
|
||||
"Show URI": "URIని చూపు",
|
||||
"Add New below or Select...": "దిగువన కొత్తది జోడించండి లేదా ఎంచుకోండి…",
|
||||
"Tag with this value already exist.": "ఈ విలువతో ట్యాగ్ ఇప్పటికే ఉంది.",
|
||||
"Custom": "కస్టమ్",
|
||||
"Entry Page": "ఎంట్రీ పేజీ",
|
||||
"statusPageNothing": "ఇక్కడ ఏమీ లేదు, దయచేసి సమూహాన్ని లేదా మానిటర్ని జోడించండి."
|
||||
}
|
@@ -672,5 +672,8 @@
|
||||
"recurringIntervalMessage": "ดำเนินการทุกวัน | ดำเนินการทุก {0} วัน",
|
||||
"chromeExecutableAutoDetect": "ตรวจจับอัตโนมัติ",
|
||||
"chromeExecutableDescription": "สำหรับผู้ใช้งาน Docker, ถ้ายังไม่ได้ติดตั่ง Chromium, อาจจะเสียเวลาในการติดตั่งและแสดงผลการทดสอบเพิ่มเติม, ใช้พื้นที่ประมาณ 1GB",
|
||||
"notificationRegional": "ภูมิภาค"
|
||||
"notificationRegional": "ภูมิภาค",
|
||||
"timeoutAfter": "หมดเวลาหลังจาก {0} วินาที",
|
||||
"Select": "เลือก",
|
||||
"Expected Value": "ค่าที่คาดหวัง"
|
||||
}
|
||||
|
@@ -64,7 +64,7 @@
|
||||
"Accepted Status Codes": "Припустимі коди статусу",
|
||||
"Save": "Зберегти",
|
||||
"Notifications": "Сповіщення",
|
||||
"Not available, please setup.": "Доступних сповіщень немає, необхідно створити.",
|
||||
"Not available, please setup.": "Недоступно, будь ласка, налаштуйте.",
|
||||
"Setup Notification": "Створити сповіщення",
|
||||
"Light": "Світла",
|
||||
"Dark": "Темна",
|
||||
@@ -445,7 +445,7 @@
|
||||
"Don't know how to get the token? Please read the guide:": "Не знаєте, як отримати токен? Прочитайте посібник:",
|
||||
"The current connection may be lost if you are currently connecting via Cloudflare Tunnel. Are you sure want to stop it? Type your current password to confirm it.": "Поточне з’єднання може бути втрачено, якщо ви зараз під’єднуєтеся через Cloudflare Tunnel. Ви дійсно хочете зробити це? Для підтвердження введіть поточний пароль.",
|
||||
"Other Software": "Інше програмне забезпечення",
|
||||
"For example: nginx, Apache and Traefik.": "Наприклад: nginx, Apache and Traefik.",
|
||||
"For example: nginx, Apache and Traefik.": "Наприклад: nginx, Apache і Traefik.",
|
||||
"Please read": "Будь ласка, прочитайте",
|
||||
"Subject:": "Тема:",
|
||||
"Valid To:": "Дійсний до:",
|
||||
|
@@ -204,7 +204,7 @@
|
||||
"webhookJsonDesc": "{0} کسی بھی جدید HTTP سرورز جیسے Express.js کے لیے اچھا ہے",
|
||||
"webhookFormDataDesc": "{multipart} پی ایچ پی کے لیے اچھا ہے۔ JSON کو {decodeFunction} کے ساتھ پارس کرنے کی ضرورت ہوگی",
|
||||
"webhookAdditionalHeadersTitle": "اضافی ہیڈرز",
|
||||
"webhookAdditionalHeadersDesc": "ویب ہک کے ساتھ بھیجے گئے اضافی ہیڈر سیٹ کرتا ہے۔",
|
||||
"webhookAdditionalHeadersDesc": "ویب ہک کے ساتھ بھیجے گئے اضافی ہیڈر سیٹ کرتا ہے۔ ہر ہیڈر کو JSON کلید/قدر کے طور پر بیان کیا جانا چاہیے۔",
|
||||
"Webhook URL": "ابھوک دیہی",
|
||||
"Application Token": "ایپلیکیشن ٹوکن",
|
||||
"Server URL": "سرور URL",
|
||||
@@ -429,7 +429,7 @@
|
||||
"No Maintenance": "کوئی دیکھ بھال نہیں",
|
||||
"weekdayShortTue": "منگل",
|
||||
"Add New Tag": "نیا ٹیگ شامل کریں",
|
||||
"Enable DNS Cache": "ڈی این ایس کیشے کو فعال کریں",
|
||||
"Enable DNS Cache": "HTTP(s) مانیٹرز کے لیے DNS کیش کو فعال کریں",
|
||||
"Effective Date Range": "مؤثر تاریخ کی حد (اختیاری)",
|
||||
"Schedule Maintenance": "شیڈول کی بحالی",
|
||||
"Date and Time": "تاریخ اور وقت",
|
||||
@@ -461,5 +461,23 @@
|
||||
"installing": "تنصیب",
|
||||
"chromeExecutableAutoDetect": "آٹو کھوج",
|
||||
"Edit Maintenance": "دیکھ بھال میں ترمیم کریں",
|
||||
"Reconnecting...": "دوبارہ رابطہ قائم کرنا..."
|
||||
"Reconnecting...": "دوبارہ رابطہ قائم کرنا...",
|
||||
"Request Timeout": "ٹائم آؤٹ کی درخواست کریں",
|
||||
"timeoutAfter": "{0} سیکنڈ کے بعد ٹائم آؤٹ",
|
||||
"styleElapsedTime": "دل کی دھڑکن بار کے نیچے گزرا ہوا وقت",
|
||||
"webhookCustomBodyDesc": "درخواست کے لیے حسب ضرورت HTTP باڈی کی وضاحت کریں۔ ٹیمپلیٹ متغیرات {msg}، {heartbeat}، {monitor} قبول کیے گئے ہیں۔",
|
||||
"filterActive": "فعال",
|
||||
"Check/Uncheck": "چیک/ان چیک کریں",
|
||||
"styleElapsedTimeShowNoLine": "دکھائیں (کوئی لائن نہیں)",
|
||||
"styleElapsedTimeShowWithLine": "دکھائیں (لائن کے ساتھ)",
|
||||
"filterActivePaused": "روک دیا گیا",
|
||||
"webhookBodyPresetOption": "پیش سیٹ - {0}",
|
||||
"webhookBodyCustomOption": "حسب ضرورت باڈی",
|
||||
"Select": "منتخب کریں",
|
||||
"selectedMonitorCount": "منتخب کردہ: {0}",
|
||||
"tailscalePingWarning": "Tailscale Ping مانیٹر استعمال کرنے کے لیے، آپ کو Docker کے بغیر Uptime Kuma انسٹال کرنا ہوگا اور اپنے سرور پر Tailscale کلائنٹ بھی انسٹال کرنا ہوگا۔",
|
||||
"uninstall": "ان انسٹال کریں",
|
||||
"Invert Keyword": "مطلوبہ الفاظ کو الٹ دیں",
|
||||
"Expected Value": "متوقع قدر",
|
||||
"Json Query": "Json استفسار"
|
||||
}
|
||||
|
@@ -82,7 +82,7 @@
|
||||
<option value="redis">
|
||||
Redis
|
||||
</option>
|
||||
<option value="tailscale-ping">
|
||||
<option v-if="!$root.info.isContainer" value="tailscale-ping">
|
||||
Tailscale Ping
|
||||
</option>
|
||||
</optgroup>
|
||||
@@ -99,18 +99,6 @@
|
||||
<input id="name" v-model="monitor.name" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<!-- Parent Monitor -->
|
||||
<div class="my-3">
|
||||
<label for="parent" class="form-label">{{ $t("Monitor Group") }}</label>
|
||||
<ActionSelect
|
||||
v-model="monitor.parent"
|
||||
:options="parentMonitorOptionsList"
|
||||
:disabled="sortedGroupMonitorList.length === 0 && draftGroupName == null"
|
||||
:icon="'plus'"
|
||||
:action="() => $refs.createGroupDialog.show()"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- URL -->
|
||||
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query' || monitor.type === 'real-browser' " class="my-3">
|
||||
<label for="url" class="form-label">{{ $t("URL") }}</label>
|
||||
@@ -297,22 +285,17 @@
|
||||
<!-- Docker Host -->
|
||||
<!-- For Docker Type -->
|
||||
<div v-if="monitor.type === 'docker'" class="my-3">
|
||||
<h2 class="mb-2">{{ $t("Docker Host") }}</h2>
|
||||
<p v-if="$root.dockerHostList.length === 0">
|
||||
{{ $t("Not available, please setup.") }}
|
||||
</p>
|
||||
|
||||
<div v-else class="mb-3">
|
||||
<div class="mb-3">
|
||||
<label for="docker-host" class="form-label">{{ $t("Docker Host") }}</label>
|
||||
<select id="docket-host" v-model="monitor.docker_host" class="form-select">
|
||||
<option v-for="host in $root.dockerHostList" :key="host.id" :value="host.id">{{ host.name }}</option>
|
||||
</select>
|
||||
<a href="#" @click="$refs.dockerHostDialog.show(monitor.docker_host)">{{ $t("Edit") }}</a>
|
||||
<ActionSelect
|
||||
v-model="monitor.docker_host"
|
||||
:options="dockerHostOptionsList"
|
||||
:disabled="$root.dockerHostList == null || $root.dockerHostList.length === 0"
|
||||
:icon="'plus'"
|
||||
:action="() => $refs.dockerHostDialog.show()"
|
||||
:required="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary me-2" type="button" @click="$refs.dockerHostDialog.show()">
|
||||
{{ $t("Setup Docker Host") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- MQTT -->
|
||||
@@ -382,11 +365,20 @@
|
||||
<input id="connectionString" v-model="monitor.databaseConnectionString" type="text" class="form-control" required>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="monitor.type === 'mysql'">
|
||||
<div class="my-3">
|
||||
<label for="mysql-password" class="form-label">{{ $t("Password") }}</label>
|
||||
<!-- TODO: Rename monitor.radiusPassword to monitor.password for general use -->
|
||||
<HiddenInput id="mysql-password" v-model="monitor.radiusPassword" autocomplete="false"></HiddenInput>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- SQL Server / PostgreSQL / MySQL -->
|
||||
<template v-if="monitor.type === 'sqlserver' || monitor.type === 'postgres' || monitor.type === 'mysql'">
|
||||
<div class="my-3">
|
||||
<label for="sqlQuery" class="form-label">{{ $t("Query") }}</label>
|
||||
<textarea id="sqlQuery" v-model="monitor.databaseQuery" class="form-control" :placeholder="$t('Example:', [ 'select getdate()' ])" required></textarea>
|
||||
<textarea id="sqlQuery" v-model="monitor.databaseQuery" class="form-control" :placeholder="$t('Example:', [ 'SELECT 1' ])"></textarea>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -413,7 +405,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Timeout: HTTP / Keyword only -->
|
||||
<div v-if="monitor.type === 'http' || monitor.type === 'keyword'" class="my-3">
|
||||
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query'" class="my-3">
|
||||
<label for="timeout" class="form-label">{{ $t("Request Timeout") }} ({{ $t("timeoutAfter", [ monitor.timeout || clampTimeout(monitor.interval) ]) }})</label>
|
||||
<input id="timeout" v-model="monitor.timeout" type="number" class="form-control" required min="0" step="0.1">
|
||||
</div>
|
||||
@@ -472,7 +464,7 @@
|
||||
</div>
|
||||
|
||||
<!-- HTTP / Keyword only -->
|
||||
<template v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'grpc-keyword' ">
|
||||
<template v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query' || monitor.type === 'grpc-keyword' ">
|
||||
<div class="my-3">
|
||||
<label for="maxRedirects" class="form-label">{{ $t("Max. Redirects") }}</label>
|
||||
<input id="maxRedirects" v-model="monitor.maxredirects" type="number" class="form-control" required min="0" step="1">
|
||||
@@ -504,6 +496,18 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Parent Monitor -->
|
||||
<div class="my-3">
|
||||
<label for="parent" class="form-label">{{ $t("Monitor Group") }}</label>
|
||||
<ActionSelect
|
||||
v-model="monitor.parent"
|
||||
:options="parentMonitorOptionsList"
|
||||
:disabled="sortedGroupMonitorList.length === 0 && draftGroupName == null"
|
||||
:icon="'plus'"
|
||||
:action="() => $refs.createGroupDialog.show()"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<div class="my-3">
|
||||
<label for="description" class="form-label">{{ $t("Description") }}</label>
|
||||
@@ -843,6 +847,7 @@ import TagsManager from "../components/TagsManager.vue";
|
||||
import { genSecret, isDev, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND } from "../util.ts";
|
||||
import { hostNameRegexPattern } from "../util-frontend";
|
||||
import { sleep } from "../util";
|
||||
import HiddenInput from "../components/HiddenInput.vue";
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
@@ -880,11 +885,14 @@ const monitorDefaults = {
|
||||
kafkaProducerSaslOptions: {
|
||||
mechanism: "None",
|
||||
},
|
||||
kafkaProducerSsl: false,
|
||||
kafkaProducerAllowAutoTopicCreation: false,
|
||||
gamedigGivenPortOnly: true,
|
||||
};
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HiddenInput,
|
||||
ActionSelect,
|
||||
ProxyDialog,
|
||||
CopyableInput,
|
||||
@@ -1106,6 +1114,21 @@ message HealthCheckResponse {
|
||||
return list;
|
||||
},
|
||||
|
||||
dockerHostOptionsList() {
|
||||
if (this.$root.dockerHostList && this.$root.dockerHostList.length > 0) {
|
||||
return this.$root.dockerHostList.map((host) => {
|
||||
return {
|
||||
label: host.name,
|
||||
value: host.id
|
||||
};
|
||||
});
|
||||
} else {
|
||||
return [{
|
||||
label: this.$t("noDockerHostMsg"),
|
||||
value: null,
|
||||
}];
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
"$root.proxyList"() {
|
||||
@@ -1338,6 +1361,12 @@ message HealthCheckResponse {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (this.monitor.type === "docker") {
|
||||
if (this.monitor.docker_host == null) {
|
||||
toast.error(this.$t("DockerHostRequired"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
|
@@ -163,6 +163,10 @@ export default {
|
||||
this.settings.entryPage = "dashboard";
|
||||
}
|
||||
|
||||
if (this.settings.nscd === undefined) {
|
||||
this.settings.nscd = true;
|
||||
}
|
||||
|
||||
if (this.settings.dnsCache === undefined) {
|
||||
this.settings.dnsCache = false;
|
||||
}
|
||||
|
@@ -102,7 +102,7 @@
|
||||
|
||||
<!-- Sidebar Footer -->
|
||||
<div class="sidebar-footer">
|
||||
<button class="btn btn-success me-2" @click="save">
|
||||
<button class="btn btn-success me-2" :disabled="loading" @click="save">
|
||||
<font-awesome-icon icon="save" />
|
||||
{{ $t("Save") }}
|
||||
</button>
|
||||
@@ -438,6 +438,7 @@ export default {
|
||||
lastUpdateTime: dayjs(),
|
||||
updateCountdown: null,
|
||||
updateCountdownText: null,
|
||||
loading: true,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -697,6 +698,8 @@ export default {
|
||||
this.incident = res.data.incident;
|
||||
this.maintenanceList = res.data.maintenanceList;
|
||||
this.$root.publicGroupList = res.data.publicGroupList;
|
||||
|
||||
this.loading = false;
|
||||
}).catch( function (error) {
|
||||
if (error.response.status === 404) {
|
||||
location.href = "/page-not-found";
|
||||
@@ -806,6 +809,7 @@ export default {
|
||||
|
||||
/** Save the status page */
|
||||
save() {
|
||||
this.loading = true;
|
||||
let startTime = new Date();
|
||||
this.config.slug = this.config.slug.trim().toLowerCase();
|
||||
|
||||
@@ -823,10 +827,12 @@ export default {
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.loading = false;
|
||||
location.href = "/status/" + this.config.slug;
|
||||
}, time);
|
||||
|
||||
} else {
|
||||
this.loading = false;
|
||||
toast.error(res.msg);
|
||||
}
|
||||
});
|
||||
|
Reference in New Issue
Block a user