mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-09-11 13:36:55 +08:00
Compare commits
145 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
6d4a45f18c | ||
|
f0975cd929 | ||
|
40d6a21453 | ||
|
b383392e8f | ||
|
9964b6c4d8 | ||
|
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 | ||
|
9564550d5f | ||
|
a78e7a423e | ||
|
9dddd0b657 | ||
|
d04d86d74e | ||
|
eb11c18203 | ||
|
3da2d78ad9 | ||
|
2b4ec765ff | ||
|
72dcefff76 | ||
|
3a894958eb | ||
|
fe431d6385 | ||
|
ce0289855d | ||
|
c0174dc1c4 | ||
|
e745bd69da | ||
|
72741ebb10 | ||
|
ff88018b0c | ||
|
921c8f8100 | ||
|
de83863627 | ||
|
d7e0ff4b8c | ||
|
112cc3d7b8 | ||
|
b44f6e4af2 | ||
|
18a2a8eb1e | ||
|
e9585ccbf4 | ||
|
5e39b0daf6 | ||
|
71fca3f0c3 | ||
|
2921f33c24 | ||
|
4e0bb394db | ||
|
ced576feba | ||
|
ceb5708bfd | ||
|
23fdd32de0 | ||
|
36777c5eff | ||
|
439b6517d1 | ||
|
c6e68fac97 | ||
|
bce4835362 | ||
|
a032e11a2e | ||
|
d231a05526 | ||
|
f4967615c0 | ||
|
67b1974718 | ||
|
062e9db2a7 | ||
|
90e1b4cf56 | ||
|
43698b23c6 | ||
|
f19b27c1c7 | ||
|
42b5d30a33 | ||
|
cda77c1a32 | ||
|
157cf47d38 | ||
|
09c6798a30 | ||
|
587d9e4781 | ||
|
ae2867e305 | ||
|
65ffd77d35 | ||
|
9dd652733e | ||
|
0a59fef7d8 | ||
|
27ce47277b | ||
|
7f68e4a987 | ||
|
149d08ecbb | ||
|
eb6167aaf1 | ||
|
db66195f7d | ||
|
59245e624d | ||
|
c0eb0cb42c | ||
|
573f158f7f | ||
|
6413d4cbdf | ||
|
cf5a04bc5e | ||
|
a0203372ce | ||
|
5ccf2d23fc | ||
|
bf68e0a7bc | ||
|
6984596568 | ||
|
f3562864ee | ||
|
9058a829a1 | ||
|
0684313ec9 | ||
|
d7e12dc92d | ||
|
1d9a28e9ab | ||
|
ddbf367011 | ||
|
50d4091ded | ||
|
f6c1b92fc6 | ||
|
2e75142fe5 | ||
|
ad3ffacf45 | ||
|
6f4af30701 | ||
|
b1f266ceb1 | ||
|
4ea5771f97 |
@@ -18,6 +18,7 @@ README.md
|
||||
.vscode
|
||||
.eslint*
|
||||
.stylelint*
|
||||
/.devcontainer
|
||||
/.github
|
||||
yarn.lock
|
||||
app.json
|
||||
@@ -33,7 +34,13 @@ 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)
|
||||
|
||||
|
12
.github/workflows/auto-test.yml
vendored
12
.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 ]
|
||||
branches: [ master, 1.23.X ]
|
||||
paths-ignore:
|
||||
- '*.md'
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest, ARM64]
|
||||
node: [ 14, 18 ]
|
||||
node: [ 14, 20 ]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
@@ -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
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ARMv7 ]
|
||||
node: [ 14.21.3, 18.16.1 ]
|
||||
node: [ 14, 20 ]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
@@ -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:
|
||||
|
3
.github/workflows/json-yaml-validate.yml
vendored
3
.github/workflows/json-yaml-validate.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: json-yaml-validate
|
||||
name: json-yaml-validate
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@@ -6,6 +6,7 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- 2.0.X
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
|
@@ -10,6 +10,7 @@
|
||||
"color-function-notation": "legacy",
|
||||
"shorthand-property-no-redundant-values": null,
|
||||
"color-hex-length": null,
|
||||
"declaration-block-no-redundant-longhand-properties": null
|
||||
"declaration-block-no-redundant-longhand-properties": null,
|
||||
"at-rule-no-unknown": null
|
||||
}
|
||||
}
|
||||
|
@@ -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,24 +30,24 @@ 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:
|
||||
|
||||
✅ Usually Accept:
|
||||
### ✅ Usually accepted:
|
||||
- Bug fix
|
||||
- Security fix
|
||||
- Adding notification providers
|
||||
- Adding new language files (You should go to https://weblate.kuma.pet for existing languages)
|
||||
- Adding new language files (see [these instructions](https://github.com/louislam/uptime-kuma/blob/master/src/lang/README.md))
|
||||
- Adding new language keys: `$t("...")`
|
||||
|
||||
⚠️ Discussion First
|
||||
### ⚠️ Discussion required:
|
||||
- Large pull requests
|
||||
- New features
|
||||
|
||||
❌ Won't Merge
|
||||
- A dedicated pr for translating existing languages (You can now translate on https://weblate.kuma.pet)
|
||||
- Do not pass the auto test
|
||||
### ❌ 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
|
||||
- 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
|
||||
|
||||
@@ -106,11 +106,11 @@ I personally do not like something that requires so many configurations before y
|
||||
|
||||
## Tools
|
||||
|
||||
- Node.js >= 14
|
||||
- NPM >= 8.5
|
||||
- Git
|
||||
- IDE that supports ESLint and EditorConfig (I am using IntelliJ IDEA)
|
||||
- A SQLite GUI tool (SQLite Expert Personal is suggested)
|
||||
- [`Node.js`](https://nodejs.org/) >= 14
|
||||
- [`npm`](https://www.npmjs.com/) >= 8.5
|
||||
- [`git`](https://git-scm.com/)
|
||||
- IDE that supports [`ESLint`](https://eslint.org/) and EditorConfig (I am using [`IntelliJ IDEA`](https://www.jetbrains.com/idea/))
|
||||
- A SQLite GUI tool (f.ex. [`SQLite Expert Personal`](https://www.sqliteexpert.com/download.html) or [`DBeaver Community`](https://dbeaver.io/download/))
|
||||
|
||||
## Install Dependencies for Development
|
||||
|
||||
@@ -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,15 +210,25 @@ 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/))
|
||||
|
||||
If for maybe security reasons, a library must be updated. Then you must need to check if there are any breaking changes.
|
||||
If for security / bug / other reasons, a library must be updated, breaking changes need to be checked by the person proposing the change.
|
||||
|
||||
## Translations
|
||||
|
||||
Please read: https://github.com/louislam/uptime-kuma/tree/master/src/languages
|
||||
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 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.
|
||||
|
||||
## Wiki
|
||||
|
||||
|
25
README.md
25
README.md
@@ -1,16 +1,16 @@
|
||||
<div align="center" width="100%">
|
||||
<img src="./public/icon.svg" width="128" alt="" />
|
||||
</div>
|
||||
|
||||
# Uptime Kuma
|
||||
|
||||
Uptime Kuma is an easy-to-use self-hosted monitoring tool.
|
||||
|
||||
<a target="_blank" href="https://github.com/louislam/uptime-kuma"><img src="https://img.shields.io/github/stars/louislam/uptime-kuma" /></a> <a target="_blank" href="https://hub.docker.com/r/louislam/uptime-kuma"><img src="https://img.shields.io/docker/pulls/louislam/uptime-kuma" /></a> <a target="_blank" href="https://hub.docker.com/r/louislam/uptime-kuma"><img src="https://img.shields.io/docker/v/louislam/uptime-kuma/latest?label=docker%20image%20ver." /></a> <a target="_blank" href="https://github.com/louislam/uptime-kuma"><img src="https://img.shields.io/github/last-commit/louislam/uptime-kuma" /></a> <a target="_blank" href="https://opencollective.com/uptime-kuma"><img src="https://opencollective.com/uptime-kuma/total/badge.svg?label=Open%20Collective%20Backers&color=brightgreen" /></a>
|
||||
[](https://github.com/sponsors/louislam) <a href="https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/">
|
||||
<img src="https://weblate.kuma.pet/widgets/uptime-kuma/-/svg-badge.svg" alt="Translation status" />
|
||||
</a>
|
||||
|
||||
<div align="center" width="100%">
|
||||
<img src="./public/icon.svg" width="128" alt="" />
|
||||
</div>
|
||||
|
||||
Uptime Kuma is an easy-to-use self-hosted monitoring tool.
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/1336778/212262296-e6205815-ad62-488c-83ec-a5b0d0689f7c.jpg" width="700" alt="" />
|
||||
|
||||
## 🥔 Live Demo
|
||||
@@ -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
|
||||
|
||||
@@ -184,7 +184,10 @@ If you want to report a bug or request a new feature, feel free to open a [new i
|
||||
### Translations
|
||||
If you want to translate Uptime Kuma into your language, please visit [Weblate Readme](https://github.com/louislam/uptime-kuma/blob/master/src/lang/README.md).
|
||||
|
||||
Feel free to correct my grammar in this README, source code, or wiki, as my mother language is not English and my grammar is not that great.
|
||||
## 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.
|
||||
|
||||
### Create Pull Requests
|
||||
If you want to modify Uptime Kuma, please read this guide and follow the rules here: https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -4,8 +4,4 @@ if (process.env.TEST_FRONTEND) {
|
||||
config.presets = [ "@babel/preset-env" ];
|
||||
}
|
||||
|
||||
if (process.env.TEST_BACKEND) {
|
||||
config.plugins = [ "babel-plugin-rewire" ];
|
||||
}
|
||||
|
||||
module.exports = config;
|
||||
|
7
db/patch-add-certificate-expiry-status-page.sql
Normal file
7
db/patch-add-certificate-expiry-status-page.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;
|
||||
|
||||
ALTER TABLE status_page
|
||||
ADD show_certificate_expiry BOOLEAN default 0 NOT NULL;
|
||||
|
||||
COMMIT;
|
7
db/patch-add-gamedig-given-port.sql
Normal file
7
db/patch-add-gamedig-given-port.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;
|
||||
|
||||
ALTER TABLE monitor
|
||||
ADD gamedig_given_port_only BOOLEAN default 1 not null;
|
||||
|
||||
COMMIT;
|
6
db/patch-add-timeout-monitor.sql
Normal file
6
db/patch-add-timeout-monitor.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
ALTER TABLE monitor
|
||||
ADD timeout DOUBLE default 0 not null;
|
||||
COMMIT;
|
34
db/patch-fix-kafka-producer-booleans.sql
Normal file
34
db/patch-fix-kafka-producer-booleans.sql
Normal file
@@ -0,0 +1,34 @@
|
||||
-- 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;
|
||||
|
||||
-- These SQL is still not fully safe. See https://github.com/louislam/uptime-kuma/issues/4039.
|
||||
|
||||
-- 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;
|
19
db/patch-monitor-oauth-cc.sql
Normal file
19
db/patch-monitor-oauth-cc.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
ALTER TABLE monitor
|
||||
ADD oauth_client_id TEXT default null;
|
||||
|
||||
ALTER TABLE monitor
|
||||
ADD oauth_client_secret TEXT default null;
|
||||
|
||||
ALTER TABLE monitor
|
||||
ADD oauth_token_url TEXT default null;
|
||||
|
||||
ALTER TABLE monitor
|
||||
ADD oauth_scopes TEXT default null;
|
||||
|
||||
ALTER TABLE monitor
|
||||
ADD oauth_auth_method TEXT default null;
|
||||
|
||||
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,17 +1,33 @@
|
||||
# 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
|
||||
|
||||
# Install Curl
|
||||
# Install Apprise, add sqlite3 cli for debugging in the future, iputils-ping for ping, util-linux for setpriv
|
||||
# Stupid python3 and python3-pip actually install a lot of useless things into Debian, specify --no-install-recommends to skip them, make the base even smaller than alpine!
|
||||
# Specify --no-install-recommends to skip unused dependencies, make the base much smaller!
|
||||
# python3* = apprise's dependencies
|
||||
# sqlite3 = for debugging
|
||||
# iputils-ping = for ping
|
||||
# util-linux = for setpriv (Should be dropped in 2.0.0?)
|
||||
# dumb-init = avoid zombie processes (#480)
|
||||
# curl = for debugging
|
||||
# ca-certificates = keep the cert up-to-date
|
||||
# sudo = for start service nscd with non-root user
|
||||
# nscd = for better DNS caching
|
||||
# (pip) apprise = for notifications
|
||||
RUN apt-get update && \
|
||||
apt-get --yes --no-install-recommends install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \
|
||||
sqlite3 iputils-ping util-linux dumb-init git curl ca-certificates && \
|
||||
pip3 --no-cache-dir install apprise==1.4.0 && \
|
||||
apt-get --yes --no-install-recommends install \
|
||||
python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \
|
||||
sqlite3 \
|
||||
iputils-ping \
|
||||
util-linux \
|
||||
dumb-init \
|
||||
curl \
|
||||
ca-certificates \
|
||||
sudo \
|
||||
nscd && \
|
||||
pip3 --no-cache-dir install apprise==1.4.5 && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
apt --yes autoremove
|
||||
|
||||
@@ -26,3 +42,7 @@ RUN set -eux && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
apt --yes autoremove
|
||||
|
||||
# For nscd
|
||||
COPY ./docker/etc/nscd.conf /etc/nscd.conf
|
||||
COPY ./docker/etc/sudoers /etc/sudoers
|
||||
|
||||
|
90
docker/etc/nscd.conf
Normal file
90
docker/etc/nscd.conf
Normal file
@@ -0,0 +1,90 @@
|
||||
#
|
||||
# /etc/nscd.conf
|
||||
#
|
||||
# An example Name Service Cache config file. This file is needed by nscd.
|
||||
#
|
||||
# Legal entries are:
|
||||
#
|
||||
# logfile <file>
|
||||
# debug-level <level>
|
||||
# threads <initial #threads to use>
|
||||
# max-threads <maximum #threads to use>
|
||||
# server-user <user to run server as instead of root>
|
||||
# server-user is ignored if nscd is started with -S parameters
|
||||
# stat-user <user who is allowed to request statistics>
|
||||
# reload-count unlimited|<number>
|
||||
# paranoia <yes|no>
|
||||
# restart-interval <time in seconds>
|
||||
#
|
||||
# enable-cache <service> <yes|no>
|
||||
# positive-time-to-live <service> <time in seconds>
|
||||
# negative-time-to-live <service> <time in seconds>
|
||||
# suggested-size <service> <prime number>
|
||||
# check-files <service> <yes|no>
|
||||
# persistent <service> <yes|no>
|
||||
# shared <service> <yes|no>
|
||||
# max-db-size <service> <number bytes>
|
||||
# auto-propagate <service> <yes|no>
|
||||
#
|
||||
# Currently supported cache names (services): passwd, group, hosts, services
|
||||
#
|
||||
|
||||
|
||||
# logfile /var/log/nscd.log
|
||||
# threads 4
|
||||
# max-threads 32
|
||||
# server-user node
|
||||
# stat-user somebody
|
||||
debug-level 0
|
||||
# reload-count 5
|
||||
paranoia no
|
||||
# restart-interval 3600
|
||||
|
||||
enable-cache passwd no
|
||||
positive-time-to-live passwd 600
|
||||
negative-time-to-live passwd 20
|
||||
suggested-size passwd 211
|
||||
check-files passwd yes
|
||||
persistent passwd yes
|
||||
shared passwd yes
|
||||
max-db-size passwd 33554432
|
||||
auto-propagate passwd yes
|
||||
|
||||
enable-cache group no
|
||||
positive-time-to-live group 3600
|
||||
negative-time-to-live group 60
|
||||
suggested-size group 211
|
||||
check-files group yes
|
||||
persistent group yes
|
||||
shared group yes
|
||||
max-db-size group 33554432
|
||||
auto-propagate group yes
|
||||
|
||||
enable-cache hosts yes
|
||||
positive-time-to-live hosts 3600
|
||||
negative-time-to-live hosts 20
|
||||
suggested-size hosts 211
|
||||
check-files hosts yes
|
||||
persistent hosts yes
|
||||
# Set shared to "no" to display stats in `nscd -g`
|
||||
# Read more: https://stackoverflow.com/questions/40429245/nscdcentos7curl-0-dns-cache-hit-rate
|
||||
shared hosts no
|
||||
max-db-size hosts 33554432
|
||||
|
||||
enable-cache services no
|
||||
positive-time-to-live services 28800
|
||||
negative-time-to-live services 20
|
||||
suggested-size services 211
|
||||
check-files services yes
|
||||
persistent services yes
|
||||
shared services yes
|
||||
max-db-size services 33554432
|
||||
|
||||
enable-cache netgroup no
|
||||
positive-time-to-live netgroup 28800
|
||||
negative-time-to-live netgroup 20
|
||||
suggested-size netgroup 211
|
||||
check-files netgroup yes
|
||||
persistent netgroup yes
|
||||
shared netgroup yes
|
||||
max-db-size netgroup 33554432
|
31
docker/etc/sudoers
Normal file
31
docker/etc/sudoers
Normal file
@@ -0,0 +1,31 @@
|
||||
#
|
||||
# This file MUST be edited with the 'visudo' command as root.
|
||||
#
|
||||
# Please consider adding local content in /etc/sudoers.d/ instead of
|
||||
# directly modifying this file.
|
||||
#
|
||||
# See the man page for details on how to write a sudoers file.
|
||||
#
|
||||
Defaults env_reset
|
||||
Defaults mail_badpass
|
||||
Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||
|
||||
# Host alias specification
|
||||
|
||||
# User alias specification
|
||||
|
||||
# Cmnd alias specification
|
||||
|
||||
# User privilege specification
|
||||
root ALL=(ALL:ALL) ALL
|
||||
|
||||
# Allow members of group sudo to execute any command
|
||||
%sudo ALL=(ALL:ALL) ALL
|
||||
|
||||
# See sudoers(5) for more information on "#include" directives:
|
||||
|
||||
#includedir /etc/sudoers.d
|
||||
|
||||
# Allow `node` to control service (mainly for nscd)
|
||||
node ALL=(root) NOPASSWD: /usr/sbin/nscdservice
|
||||
node ALL=(root) NOPASSWD: /usr/sbin/service
|
@@ -5,15 +5,15 @@
|
||||
|
||||
// curl -o kuma_install.sh https://raw.githubusercontent.com/louislam/uptime-kuma/master/install.sh && sudo bash kuma_install.sh
|
||||
println("=====================");
|
||||
println("Uptime Kuma Installer");
|
||||
println("Uptime Kuma Install Script");
|
||||
println("=====================");
|
||||
println("Supported OS: CentOS 7/8, Ubuntu >= 16.04 and Debian");
|
||||
println("Supported OS: Ubuntu >= 16.04, Debian and CentOS/RHEL 7/8");
|
||||
println("---------------------------------------");
|
||||
println("This script is designed for Linux and basic usage.");
|
||||
println("For advanced usage, please go to https://github.com/louislam/uptime-kuma/wiki/Installation");
|
||||
println("---------------------------------------");
|
||||
println("");
|
||||
println("Local - Install Uptime Kuma in your current machine with git, Node.js 14 and pm2");
|
||||
println("Local - Install Uptime Kuma on your current machine with git, Node.js and pm2");
|
||||
println("Docker - Install Uptime Kuma Docker container");
|
||||
println("");
|
||||
|
||||
@@ -29,14 +29,10 @@ function checkNode() {
|
||||
bash("nodeVersion=$(node -e 'console.log(process.versions.node.split(`.`)[0])')");
|
||||
println("Node Version: " ++ nodeVersion);
|
||||
|
||||
if (nodeVersion < "12") {
|
||||
if (nodeVersion <= "12") {
|
||||
println("Error: Required Node.js 14");
|
||||
call("exit", "1");
|
||||
}
|
||||
|
||||
if (nodeVersion == "12") {
|
||||
println("Warning: NodeJS " ++ nodeVersion ++ " is not tested.");
|
||||
}
|
||||
}
|
||||
|
||||
function deb() {
|
||||
@@ -60,8 +56,8 @@ function deb() {
|
||||
bash("apt --yes install curl");
|
||||
}
|
||||
|
||||
println("Installing Node.js 14");
|
||||
bash("curl -sL https://deb.nodesource.com/setup_14.x | bash - > log.txt");
|
||||
println("Installing Node.js 16");
|
||||
bash("curl -sL https://deb.nodesource.com/setup_16.x | bash - > log.txt");
|
||||
bash("apt --yes install nodejs");
|
||||
bash("node -v");
|
||||
|
||||
@@ -91,6 +87,10 @@ if (type == "local") {
|
||||
bash("os=$(head -n1 /etc/issue | cut -f 1 -d ' ')");
|
||||
if (os == "Ubuntu") {
|
||||
distribution = "ubuntu";
|
||||
|
||||
// Get ubuntu version
|
||||
bash(". /etc/lsb-release");
|
||||
version = DISTRIB_RELEASE;
|
||||
}
|
||||
if (os == "Debian") {
|
||||
distribution = "debian";
|
||||
@@ -101,6 +101,7 @@ if (type == "local") {
|
||||
|
||||
println("Your OS: " ++ os);
|
||||
println("Distribution: " ++ distribution);
|
||||
println("Version: " ++ version);
|
||||
println("Arch: " ++ arch);
|
||||
|
||||
if ("$3" != "") {
|
||||
@@ -131,15 +132,32 @@ if (type == "local") {
|
||||
checkNode();
|
||||
} else {
|
||||
|
||||
bash("curlCheck=$(curl --version)");
|
||||
if (curlCheck == "") {
|
||||
println("Installing Curl");
|
||||
bash("yum -y -q install curl");
|
||||
bash("dnfCheck=$(dnf --version)");
|
||||
|
||||
// Use yum
|
||||
if (dnfCheck == "") {
|
||||
bash("curlCheck=$(curl --version)");
|
||||
if (curlCheck == "") {
|
||||
println("Installing Curl");
|
||||
bash("yum -y -q install curl");
|
||||
}
|
||||
|
||||
println("Installing Node.js 16");
|
||||
bash("curl -sL https://rpm.nodesource.com/setup_16.x | bash - > log.txt");
|
||||
bash("yum install -y -q nodejs");
|
||||
} else {
|
||||
bash("curlCheck=$(curl --version)");
|
||||
if (curlCheck == "") {
|
||||
println("Installing Curl");
|
||||
bash("dnf -y install curl");
|
||||
}
|
||||
|
||||
println("Installing Node.js 16");
|
||||
bash("curl -sL https://rpm.nodesource.com/setup_16.x | bash - > log.txt");
|
||||
bash("dnf install -y nodejs");
|
||||
}
|
||||
|
||||
println("Installing Node.js 14");
|
||||
bash("curl -sL https://rpm.nodesource.com/setup_14.x | bash - > log.txt");
|
||||
bash("yum install -y -q nodejs");
|
||||
|
||||
bash("node -v");
|
||||
|
||||
bash("nodeCheckAgain=$(node -v)");
|
||||
@@ -171,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) {
|
||||
@@ -193,6 +213,15 @@ if (type == "local") {
|
||||
bash("pm2 startup");
|
||||
}
|
||||
|
||||
|
||||
// Check again
|
||||
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");
|
||||
}
|
||||
|
||||
bash("mkdir -p $installPath");
|
||||
bash("cd $installPath");
|
||||
bash("git clone https://github.com/louislam/uptime-kuma.git .");
|
||||
@@ -206,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");
|
||||
}
|
||||
|
||||
@@ -213,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");
|
||||
|
||||
|
16
index.html
16
index.html
@@ -9,8 +9,24 @@
|
||||
<meta name="theme-color" id="theme-color" content="" />
|
||||
<meta name="description" content="Uptime Kuma monitoring tool" />
|
||||
<title>Uptime Kuma</title>
|
||||
<style>
|
||||
.noscript-message {
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<div class="noscript-message">
|
||||
Sorry, you don't seem to have JavaScript enabled or your browser
|
||||
doesn't support it.<br />This website requires JavaScript to function.
|
||||
Please enable JavaScript in your browser settings to continue.
|
||||
</div>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
|
63
install.sh
63
install.sh
@@ -3,15 +3,15 @@
|
||||
# The command is working on Windows PowerShell and Docker for Windows only.
|
||||
# curl -o kuma_install.sh https://raw.githubusercontent.com/louislam/uptime-kuma/master/install.sh && sudo bash kuma_install.sh
|
||||
"echo" "-e" "====================="
|
||||
"echo" "-e" "Uptime Kuma Installer"
|
||||
"echo" "-e" "Uptime Kuma Install Script"
|
||||
"echo" "-e" "====================="
|
||||
"echo" "-e" "Supported OS: CentOS 7/8, Ubuntu >= 16.04 and Debian"
|
||||
"echo" "-e" "Supported OS: Ubuntu >= 16.04, Debian and CentOS/RHEL 7/8"
|
||||
"echo" "-e" "---------------------------------------"
|
||||
"echo" "-e" "This script is designed for Linux and basic usage."
|
||||
"echo" "-e" "For advanced usage, please go to https://github.com/louislam/uptime-kuma/wiki/Installation"
|
||||
"echo" "-e" "---------------------------------------"
|
||||
"echo" "-e" ""
|
||||
"echo" "-e" "Local - Install Uptime Kuma in your current machine with git, Node.js 14 and pm2"
|
||||
"echo" "-e" "Local - Install Uptime Kuma on your current machine with git, Node.js and pm2"
|
||||
"echo" "-e" "Docker - Install Uptime Kuma Docker container"
|
||||
"echo" "-e" ""
|
||||
if [ "$1" != "" ]; then
|
||||
@@ -25,12 +25,9 @@ function checkNode {
|
||||
nodeVersion=$(node -e 'console.log(process.versions.node.split(`.`)[0])')
|
||||
"echo" "-e" "Node Version: ""$nodeVersion"
|
||||
_0="12"
|
||||
if [ $(($nodeVersion < $_0)) == 1 ]; then
|
||||
if [ $(($nodeVersion <= $_0)) == 1 ]; then
|
||||
"echo" "-e" "Error: Required Node.js 14"
|
||||
"exit" "1"
|
||||
fi
|
||||
if [ "$nodeVersion" == "12" ]; then
|
||||
"echo" "-e" "Warning: NodeJS ""$nodeVersion"" is not tested."
|
||||
fi
|
||||
}
|
||||
function deb {
|
||||
@@ -50,8 +47,8 @@ fi
|
||||
"echo" "-e" "Installing Curl"
|
||||
apt --yes install curl
|
||||
fi
|
||||
"echo" "-e" "Installing Node.js 14"
|
||||
curl -sL https://deb.nodesource.com/setup_14.x | bash - > log.txt
|
||||
"echo" "-e" "Installing Node.js 16"
|
||||
curl -sL https://deb.nodesource.com/setup_16.x | bash - > log.txt
|
||||
apt --yes install nodejs
|
||||
node -v
|
||||
nodeCheckAgain=$(node -v)
|
||||
@@ -75,7 +72,10 @@ if [ "$type" == "local" ]; then
|
||||
if [ -e "/etc/issue" ]; then
|
||||
os=$(head -n1 /etc/issue | cut -f 1 -d ' ')
|
||||
if [ "$os" == "Ubuntu" ]; then
|
||||
distribution="ubuntu"
|
||||
distribution="ubuntu"
|
||||
# Get ubuntu version
|
||||
. /etc/lsb-release
|
||||
version="$DISTRIB_RELEASE"
|
||||
fi
|
||||
if [ "$os" == "Debian" ]; then
|
||||
distribution="debian"
|
||||
@@ -85,6 +85,7 @@ fi
|
||||
arch=$(uname -i)
|
||||
"echo" "-e" "Your OS: ""$os"
|
||||
"echo" "-e" "Distribution: ""$distribution"
|
||||
"echo" "-e" "Version: ""$version"
|
||||
"echo" "-e" "Arch: ""$arch"
|
||||
if [ "$3" != "" ]; then
|
||||
port="$3"
|
||||
@@ -108,14 +109,27 @@ fi
|
||||
if [ "$nodeCheck" != "" ]; then
|
||||
"checkNode"
|
||||
else
|
||||
curlCheck=$(curl --version)
|
||||
if [ "$curlCheck" == "" ]; then
|
||||
"echo" "-e" "Installing Curl"
|
||||
yum -y -q install curl
|
||||
dnfCheck=$(dnf --version)
|
||||
# Use yum
|
||||
if [ "$dnfCheck" == "" ]; then
|
||||
curlCheck=$(curl --version)
|
||||
if [ "$curlCheck" == "" ]; then
|
||||
"echo" "-e" "Installing Curl"
|
||||
yum -y -q install curl
|
||||
fi
|
||||
"echo" "-e" "Installing Node.js 14"
|
||||
curl -sL https://rpm.nodesource.com/setup_14.x | bash - > log.txt
|
||||
yum install -y -q nodejs
|
||||
"echo" "-e" "Installing Node.js 16"
|
||||
curl -sL https://rpm.nodesource.com/setup_16.x | bash - > log.txt
|
||||
yum install -y -q nodejs
|
||||
else
|
||||
curlCheck=$(curl --version)
|
||||
if [ "$curlCheck" == "" ]; then
|
||||
"echo" "-e" "Installing Curl"
|
||||
dnf -y install curl
|
||||
fi
|
||||
"echo" "-e" "Installing Node.js 16"
|
||||
curl -sL https://rpm.nodesource.com/setup_16.x | bash - > log.txt
|
||||
dnf install -y nodejs
|
||||
fi
|
||||
node -v
|
||||
nodeCheckAgain=$(node -v)
|
||||
if [ "$nodeCheckAgain" == "" ]; then
|
||||
@@ -142,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"
|
||||
@@ -161,6 +177,13 @@ fi
|
||||
"echo" "-e" "Installing PM2"
|
||||
npm install pm2 -g && pm2 install pm2-logrotate
|
||||
pm2 startup
|
||||
fi
|
||||
# Check again
|
||||
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
|
||||
cd $installPath
|
||||
@@ -172,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
|
||||
|
5025
package-lock.json
generated
5025
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
32
package.json
32
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "uptime-kuma",
|
||||
"version": "1.22.1",
|
||||
"version": "1.23.5",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -40,19 +40,25 @@
|
||||
"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.22.1 && npm ci --production && npm run download-dist",
|
||||
"setup": "git checkout 1.23.5 && 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",
|
||||
"remove-2fa": "node extra/remove-2fa.js",
|
||||
"compile-install-script": "@powershell -NoProfile -ExecutionPolicy Unrestricted -Command ./extra/compile-install-script.ps1",
|
||||
"test-install-script-rockylinux": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/rockylinux.dockerfile .",
|
||||
"test-install-script-centos7": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/centos7.dockerfile .",
|
||||
"test-install-script-alpine3": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/alpine3.dockerfile .",
|
||||
"test-install-script-debian": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/debian.dockerfile .",
|
||||
"test-install-script-debian-buster": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/debian-buster.dockerfile .",
|
||||
"test-install-script-ubuntu": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/ubuntu.dockerfile .",
|
||||
"test-install-script-ubuntu1804": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/ubuntu1804.dockerfile .",
|
||||
"test-install-script-ubuntu1604": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/ubuntu1604.dockerfile .",
|
||||
"test-nodejs16": "docker build --progress plain -f test/ubuntu-nodejs16.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",
|
||||
@@ -92,11 +98,12 @@
|
||||
"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",
|
||||
"iconv-lite": "~0.6.3",
|
||||
"isomorphic-ws": "^5.0.0",
|
||||
"jsesc": "~3.0.2",
|
||||
"jsonata": "^2.0.3",
|
||||
"jsonwebtoken": "~9.0.0",
|
||||
@@ -104,24 +111,25 @@
|
||||
"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",
|
||||
"nodemailer": "~6.6.5",
|
||||
"nostr-tools": "^1.13.1",
|
||||
"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",
|
||||
"protobufjs": "~7.2.4",
|
||||
"qs": "~6.10.4",
|
||||
"queue": "~7.0.0",
|
||||
"redbean-node": "~0.3.0",
|
||||
"redis": "~4.5.1",
|
||||
"semver": "~7.5.4",
|
||||
@@ -130,7 +138,8 @@
|
||||
"socks-proxy-agent": "6.1.1",
|
||||
"tar": "~6.1.11",
|
||||
"tcp-ping": "~0.1.1",
|
||||
"thirty-two": "~1.0.2"
|
||||
"thirty-two": "~1.0.2",
|
||||
"ws": "^8.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/github": "~5.0.1",
|
||||
@@ -147,7 +156,6 @@
|
||||
"@vue/compiler-sfc": "~3.3.4",
|
||||
"@vuepic/vue-datepicker": "~3.4.8",
|
||||
"aedes": "^0.46.3",
|
||||
"babel-plugin-rewire": "~1.2.0",
|
||||
"bootstrap": "5.1.3",
|
||||
"chart.js": "~4.2.1",
|
||||
"chartjs-adapter-dayjs-4": "~1.0.4",
|
||||
@@ -155,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(),
|
||||
|
@@ -3,6 +3,7 @@ const { R } = require("redbean-node");
|
||||
const { setSetting, setting } = require("./util-server");
|
||||
const { log, sleep } = require("../src/util");
|
||||
const knex = require("knex");
|
||||
const path = require("path");
|
||||
|
||||
/**
|
||||
* Database & App Data Folder
|
||||
@@ -25,6 +26,8 @@ class Database {
|
||||
|
||||
static path;
|
||||
|
||||
static dockerTLSDir;
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
@@ -74,6 +77,13 @@ class Database {
|
||||
"patch-add-invert-keyword.sql": true,
|
||||
"patch-added-json-query.sql": true,
|
||||
"patch-added-kafka-producer.sql": true,
|
||||
"patch-add-certificate-expiry-status-page.sql": true,
|
||||
"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,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -92,23 +102,28 @@ class Database {
|
||||
// Data Directory (must be end with "/")
|
||||
Database.dataDir = process.env.DATA_DIR || args["data-dir"] || "./data/";
|
||||
|
||||
Database.path = Database.dataDir + "kuma.db";
|
||||
Database.path = path.join(Database.dataDir, "kuma.db");
|
||||
if (! fs.existsSync(Database.dataDir)) {
|
||||
fs.mkdirSync(Database.dataDir, { recursive: true });
|
||||
}
|
||||
|
||||
Database.uploadDir = Database.dataDir + "upload/";
|
||||
Database.uploadDir = path.join(Database.dataDir, "upload/");
|
||||
|
||||
if (! fs.existsSync(Database.uploadDir)) {
|
||||
fs.mkdirSync(Database.uploadDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Create screenshot dir
|
||||
Database.screenshotDir = Database.dataDir + "screenshots/";
|
||||
Database.screenshotDir = path.join(Database.dataDir, "screenshots/");
|
||||
if (! fs.existsSync(Database.screenshotDir)) {
|
||||
fs.mkdirSync(Database.screenshotDir, { recursive: true });
|
||||
}
|
||||
|
||||
Database.dockerTLSDir = path.join(Database.dataDir, "docker-tls/");
|
||||
if (! fs.existsSync(Database.dockerTLSDir)) {
|
||||
fs.mkdirSync(Database.dockerTLSDir, { recursive: true });
|
||||
}
|
||||
|
||||
log.info("db", `Data Dir: ${Database.dataDir}`);
|
||||
}
|
||||
|
||||
|
@@ -2,8 +2,16 @@ const axios = require("axios");
|
||||
const { R } = require("redbean-node");
|
||||
const version = require("../package.json").version;
|
||||
const https = require("https");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const Database = require("./database");
|
||||
|
||||
class DockerHost {
|
||||
|
||||
static CertificateFileNameCA = "ca.pem";
|
||||
static CertificateFileNameCert = "cert.pem";
|
||||
static CertificateFileNameKey = "key.pem";
|
||||
|
||||
/**
|
||||
* Save a docker host
|
||||
* @param {Object} dockerHost Docker host to save
|
||||
@@ -66,16 +74,13 @@ class DockerHost {
|
||||
"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: false,
|
||||
}),
|
||||
};
|
||||
|
||||
if (dockerHost.dockerType === "socket") {
|
||||
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));
|
||||
}
|
||||
|
||||
let res = await axios.request(options);
|
||||
@@ -111,6 +116,53 @@ class DockerHost {
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns HTTPS agent options with client side TLS parameters if certificate files
|
||||
* for the given host are available under a predefined directory path.
|
||||
*
|
||||
* The base path where certificates are looked for can be set with the
|
||||
* 'DOCKER_TLS_DIR_PATH' environmental variable or defaults to 'data/docker-tls/'.
|
||||
*
|
||||
* If a directory in this path exists with a name matching the FQDN of the docker host
|
||||
* (e.g. the FQDN of 'https://example.com:2376' is 'example.com' so the directory
|
||||
* 'data/docker-tls/example.com/' would be searched for certificate files),
|
||||
* then 'ca.pem', 'key.pem' and 'cert.pem' files are included in the agent options.
|
||||
* File names can also be overridden via 'DOCKER_TLS_FILE_NAME_(CA|KEY|CERT)'.
|
||||
*
|
||||
* @param {String} dockerType i.e. "tcp" or "socket"
|
||||
* @param {String} url The docker host URL rewritten to https://
|
||||
* @return {Object}
|
||||
* */
|
||||
static getHttpsAgentOptions(dockerType, url) {
|
||||
let baseOptions = {
|
||||
maxCachedSessions: 0,
|
||||
rejectUnauthorized: true
|
||||
};
|
||||
let certOptions = {};
|
||||
|
||||
let dirName = (new URL(url)).hostname;
|
||||
|
||||
let caPath = path.join(Database.dockerTLSDir, dirName, DockerHost.CertificateFileNameCA);
|
||||
let certPath = path.join(Database.dockerTLSDir, dirName, DockerHost.CertificateFileNameCert);
|
||||
let keyPath = path.join(Database.dockerTLSDir, dirName, DockerHost.CertificateFileNameKey);
|
||||
|
||||
if (dockerType === "tcp" && fs.existsSync(caPath) && fs.existsSync(certPath) && fs.existsSync(keyPath)) {
|
||||
let ca = fs.readFileSync(caPath);
|
||||
let key = fs.readFileSync(keyPath);
|
||||
let cert = fs.readFileSync(certPath);
|
||||
certOptions = {
|
||||
ca,
|
||||
key,
|
||||
cert
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...baseOptions,
|
||||
...certOptions
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
@@ -9,12 +9,12 @@ class Group extends BeanModel {
|
||||
* @param {boolean} [showTags=false] Should the JSON include monitor tags
|
||||
* @returns {Object}
|
||||
*/
|
||||
async toPublicJSON(showTags = false) {
|
||||
async toPublicJSON(showTags = false, certExpiry = false) {
|
||||
let monitorBeanList = await this.getMonitorList();
|
||||
let monitorList = [];
|
||||
|
||||
for (let bean of monitorBeanList) {
|
||||
monitorList.push(await bean.toPublicJSON(showTags));
|
||||
monitorList.push(await bean.toPublicJSON(showTags, certExpiry));
|
||||
}
|
||||
|
||||
return {
|
||||
|
@@ -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
|
||||
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
|
||||
@@ -37,11 +39,12 @@ class Monitor extends BeanModel {
|
||||
* Only show necessary data to public
|
||||
* @returns {Object}
|
||||
*/
|
||||
async toPublicJSON(showTags = false) {
|
||||
async toPublicJSON(showTags = false, certExpiry = false) {
|
||||
let obj = {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
sendUrl: this.sendUrl,
|
||||
type: this.type,
|
||||
};
|
||||
|
||||
if (this.sendUrl) {
|
||||
@@ -51,6 +54,13 @@ class Monitor extends BeanModel {
|
||||
if (showTags) {
|
||||
obj.tags = await this.getTags();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -94,6 +104,7 @@ class Monitor extends BeanModel {
|
||||
active: await this.isActive(),
|
||||
forceInactive: !await Monitor.isParentActive(this.id),
|
||||
type: this.type,
|
||||
timeout: this.timeout,
|
||||
interval: this.interval,
|
||||
retryInterval: this.retryInterval,
|
||||
resendInterval: this.resendInterval,
|
||||
@@ -126,13 +137,14 @@ class Monitor extends BeanModel {
|
||||
radiusCalledStationId: this.radiusCalledStationId,
|
||||
radiusCallingStationId: this.radiusCallingStationId,
|
||||
game: this.game,
|
||||
gamedigGivenPortOnly: this.getGameDigGivenPortOnly(),
|
||||
httpBodyEncoding: this.httpBodyEncoding,
|
||||
jsonPath: this.jsonPath,
|
||||
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,
|
||||
};
|
||||
@@ -146,6 +158,11 @@ class Monitor extends BeanModel {
|
||||
grpcMetadata: this.grpcMetadata,
|
||||
basic_auth_user: this.basic_auth_user,
|
||||
basic_auth_pass: this.basic_auth_pass,
|
||||
oauth_client_id: this.oauth_client_id,
|
||||
oauth_client_secret: this.oauth_client_secret,
|
||||
oauth_token_url: this.oauth_token_url,
|
||||
oauth_scopes: this.oauth_scopes,
|
||||
oauth_auth_method: this.oauth_auth_method,
|
||||
pushToken: this.pushToken,
|
||||
databaseConnectionString: this.databaseConnectionString,
|
||||
radiusUsername: this.radiusUsername,
|
||||
@@ -184,6 +201,31 @@ class Monitor extends BeanModel {
|
||||
return await R.getAll("SELECT mt.*, tag.name, tag.color FROM monitor_tag mt JOIN tag ON mt.tag_id = tag.id WHERE mt.monitor_id = ? ORDER BY tag.name", [ this.id ]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets certificate expiry for this monitor
|
||||
* @param {number} monitorID ID of monitor to send
|
||||
* @returns {Promise<LooseObject<any>>}
|
||||
*/
|
||||
async getCertExpiry(monitorID) {
|
||||
let tlsInfoBean = await R.findOne("monitor_tls_info", "monitor_id = ?", [
|
||||
monitorID,
|
||||
]);
|
||||
let tlsInfo;
|
||||
if (tlsInfoBean) {
|
||||
tlsInfo = JSON.parse(tlsInfoBean?.info_json);
|
||||
if (tlsInfo?.valid && tlsInfo?.certInfo?.daysRemaining) {
|
||||
return {
|
||||
certExpiryDaysRemaining: tlsInfo.certInfo.daysRemaining,
|
||||
validCert: true
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
certExpiryDaysRemaining: "",
|
||||
validCert: false
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode user and password to Base64 encoding
|
||||
* for HTTP "basic" auth, as per RFC-7617
|
||||
@@ -241,6 +283,26 @@ class Monitor extends BeanModel {
|
||||
return JSON.parse(this.accepted_statuscodes_json);
|
||||
}
|
||||
|
||||
getGameDigGivenPortOnly() {
|
||||
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
|
||||
@@ -266,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;
|
||||
@@ -295,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";
|
||||
@@ -313,7 +391,10 @@ class Monitor extends BeanModel {
|
||||
const lastBeat = await Monitor.getPreviousHeartbeat(child.id);
|
||||
|
||||
// Only change state if the monitor is in worse conditions then the ones before
|
||||
if (bean.status === UP && (lastBeat.status === PENDING || lastBeat.status === DOWN)) {
|
||||
// lastBeat.status could be null
|
||||
if (!lastBeat) {
|
||||
bean.status = PENDING;
|
||||
} else if (bean.status === UP && (lastBeat.status === PENDING || lastBeat.status === DOWN)) {
|
||||
bean.status = lastBeat.status;
|
||||
} else if (bean.status === PENDING && lastBeat.status === DOWN) {
|
||||
bean.status = lastBeat.status;
|
||||
@@ -341,9 +422,28 @@ class Monitor extends BeanModel {
|
||||
};
|
||||
}
|
||||
|
||||
// OIDC: Basic client credential flow.
|
||||
// Additional grants might be implemented in the future
|
||||
let oauth2AuthHeader = {};
|
||||
if (this.auth_method === "oauth2-cc") {
|
||||
try {
|
||||
if (this.oauthAccessToken === undefined || new Date(this.oauthAccessToken.expires_at * 1000) <= new Date()) {
|
||||
log.debug("monitor", `[${this.name}] The oauth access-token undefined or expired. Requesting a new one`);
|
||||
this.oauthAccessToken = await getOidcTokenClientCredentials(this.oauth_token_url, this.oauth_client_id, this.oauth_client_secret, this.oauth_scopes, this.oauth_auth_method);
|
||||
log.debug("monitor", `[${this.name}] Obtained oauth access-token. Expires at ${new Date(this.oauthAccessToken.expires_at * 1000)}`);
|
||||
}
|
||||
oauth2AuthHeader = {
|
||||
"Authorization": this.oauthAccessToken.token_type + " " + this.oauthAccessToken.access_token,
|
||||
};
|
||||
} catch (e) {
|
||||
throw new Error("The oauth config is invalid. " + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
const httpsAgentOptions = {
|
||||
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
|
||||
rejectUnauthorized: !this.getIgnoreTls(),
|
||||
secureOptions: crypto.constants.SSL_OP_LEGACY_SERVER_CONNECT,
|
||||
};
|
||||
|
||||
log.debug("monitor", `[${this.name}] Prepare Options for axios`);
|
||||
@@ -369,18 +469,20 @@ class Monitor extends BeanModel {
|
||||
const options = {
|
||||
url: this.url,
|
||||
method: (this.method || "get").toLowerCase(),
|
||||
timeout: this.interval * 1000 * 0.8,
|
||||
timeout: this.timeout * 1000,
|
||||
headers: {
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
|
||||
"User-Agent": "Uptime-Kuma/" + version,
|
||||
...(contentType ? { "Content-Type": contentType } : {}),
|
||||
...(basicAuthHeader),
|
||||
...(oauth2AuthHeader),
|
||||
...(this.headers ? JSON.parse(this.headers) : {})
|
||||
},
|
||||
maxRedirects: this.maxredirects,
|
||||
validateStatus: (status) => {
|
||||
return checkStatusCode(status, this.getAcceptedStatuscodes());
|
||||
},
|
||||
signal: axiosAbortSignal(this.timeout * 1000),
|
||||
};
|
||||
|
||||
if (bodyValue) {
|
||||
@@ -515,13 +617,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} | `;
|
||||
@@ -539,7 +641,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
|
||||
@@ -588,7 +690,7 @@ class Monitor extends BeanModel {
|
||||
}
|
||||
|
||||
let res = await axios.get(steamApiUrl, {
|
||||
timeout: this.interval * 1000 * 0.8,
|
||||
timeout: this.timeout * 1000,
|
||||
headers: {
|
||||
"Accept": "*/*",
|
||||
"User-Agent": "Uptime-Kuma/" + version,
|
||||
@@ -596,6 +698,7 @@ class Monitor extends BeanModel {
|
||||
httpsAgent: CacheableDnsHttpAgent.getHttpsAgent({
|
||||
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
|
||||
rejectUnauthorized: !this.getIgnoreTls(),
|
||||
secureOptions: crypto.constants.SSL_OP_LEGACY_SERVER_CONNECT,
|
||||
}),
|
||||
httpAgent: CacheableDnsHttpAgent.getHttpAgent({
|
||||
maxCachedSessions: 0,
|
||||
@@ -626,7 +729,7 @@ class Monitor extends BeanModel {
|
||||
type: this.game,
|
||||
host: this.hostname,
|
||||
port: this.port,
|
||||
givenPortOnly: true,
|
||||
givenPortOnly: this.getGameDigGivenPortOnly(),
|
||||
});
|
||||
|
||||
bean.msg = state.name;
|
||||
@@ -638,8 +741,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,
|
||||
@@ -650,16 +751,26 @@ class Monitor extends BeanModel {
|
||||
httpsAgent: CacheableDnsHttpAgent.getHttpsAgent({
|
||||
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
|
||||
rejectUnauthorized: !this.getIgnoreTls(),
|
||||
secureOptions: crypto.constants.SSL_OP_LEGACY_SERVER_CONNECT,
|
||||
}),
|
||||
httpAgent: CacheableDnsHttpAgent.getHttpAgent({
|
||||
maxCachedSessions: 0,
|
||||
}),
|
||||
};
|
||||
|
||||
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") {
|
||||
options.baseURL = DockerHost.patchDockerURL(dockerHost._dockerDaemon);
|
||||
options.httpsAgent = CacheableDnsHttpAgent.getHttpsAgent(
|
||||
DockerHost.getHttpsAgentOptions(dockerHost._dockerType, options.baseURL)
|
||||
);
|
||||
}
|
||||
|
||||
log.debug("monitor", `[${this.name}] Axios Request`);
|
||||
@@ -687,7 +798,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;
|
||||
@@ -726,7 +837,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;
|
||||
@@ -734,7 +845,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") {
|
||||
@@ -759,29 +874,19 @@ class Monitor extends BeanModel {
|
||||
port = this.port;
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await radius(
|
||||
this.hostname,
|
||||
this.radiusUsername,
|
||||
this.radiusPassword,
|
||||
this.radiusCalledStationId,
|
||||
this.radiusCallingStationId,
|
||||
this.radiusSecret,
|
||||
port,
|
||||
this.interval * 1000 * 0.8,
|
||||
);
|
||||
if (resp.code) {
|
||||
bean.msg = resp.code;
|
||||
}
|
||||
bean.status = UP;
|
||||
} catch (error) {
|
||||
bean.status = DOWN;
|
||||
if (error.response?.code) {
|
||||
bean.msg = error.response.code;
|
||||
} else {
|
||||
bean.msg = error.message;
|
||||
}
|
||||
}
|
||||
const resp = await radius(
|
||||
this.hostname,
|
||||
this.radiusUsername,
|
||||
this.radiusPassword,
|
||||
this.radiusCalledStationId,
|
||||
this.radiusCallingStationId,
|
||||
this.radiusSecret,
|
||||
port,
|
||||
this.interval * 1000 * 0.4,
|
||||
);
|
||||
|
||||
bean.msg = resp.code;
|
||||
bean.status = UP;
|
||||
bean.ping = dayjs().valueOf() - startTime;
|
||||
} else if (this.type === "redis") {
|
||||
let startTime = dayjs().valueOf();
|
||||
@@ -914,6 +1019,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.`);
|
||||
}
|
||||
@@ -923,7 +1029,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);
|
||||
@@ -932,6 +1040,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.");
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1016,6 +1127,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
|
||||
@@ -1352,7 +1476,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}.`);
|
||||
|
@@ -90,6 +90,8 @@ class StatusPage extends BeanModel {
|
||||
* @param {StatusPage} statusPage
|
||||
*/
|
||||
static async getStatusPageData(statusPage) {
|
||||
const config = await statusPage.toPublicJSON();
|
||||
|
||||
// Incident
|
||||
let incident = await R.findOne("incident", " pin = 1 AND active = 1 AND status_page_id = ? ", [
|
||||
statusPage.id,
|
||||
@@ -110,13 +112,13 @@ class StatusPage extends BeanModel {
|
||||
]);
|
||||
|
||||
for (let groupBean of list) {
|
||||
let monitorGroup = await groupBean.toPublicJSON(showTags);
|
||||
let monitorGroup = await groupBean.toPublicJSON(showTags, config?.showCertificateExpiry);
|
||||
publicGroupList.push(monitorGroup);
|
||||
}
|
||||
|
||||
// Response
|
||||
return {
|
||||
config: await statusPage.toPublicJSON(),
|
||||
config,
|
||||
incident,
|
||||
publicGroupList,
|
||||
maintenanceList,
|
||||
@@ -234,6 +236,7 @@ class StatusPage extends BeanModel {
|
||||
footerText: this.footer_text,
|
||||
showPoweredBy: !!this.show_powered_by,
|
||||
googleAnalyticsId: this.google_analytics_tag_id,
|
||||
showCertificateExpiry: !!this.show_certificate_expiry,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -255,6 +258,7 @@ class StatusPage extends BeanModel {
|
||||
footerText: this.footer_text,
|
||||
showPoweredBy: !!this.show_powered_by,
|
||||
googleAnalyticsId: this.google_analytics_tag_id,
|
||||
showCertificateExpiry: !!this.show_certificate_expiry,
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
|
95
server/monitor-types/tailscale-ping.js
Normal file
95
server/monitor-types/tailscale-ping.js
Normal file
@@ -0,0 +1,95 @@
|
||||
const { MonitorType } = require("./monitor-type");
|
||||
const { UP, log } = require("../../src/util");
|
||||
const exec = require("child_process").exec;
|
||||
|
||||
/**
|
||||
* A TailscalePing class extends the MonitorType.
|
||||
* It runs Tailscale ping to monitor the status of a specific node.
|
||||
*/
|
||||
class TailscalePing extends MonitorType {
|
||||
|
||||
name = "tailscale-ping";
|
||||
|
||||
/**
|
||||
* Checks the ping status of the URL associated with the monitor.
|
||||
* It then parses the Tailscale ping command output to update the heatrbeat.
|
||||
*
|
||||
* @param {Object} monitor - The monitor object associated with the check.
|
||||
* @param {Object} heartbeat - The heartbeat object to update.
|
||||
* @throws Will throw an error if checking Tailscale ping encounters any error
|
||||
*/
|
||||
async check(monitor, heartbeat) {
|
||||
try {
|
||||
let tailscaleOutput = await this.runTailscalePing(monitor.hostname, monitor.interval);
|
||||
this.parseTailscaleOutput(tailscaleOutput, heartbeat);
|
||||
} catch (err) {
|
||||
log.debug("Tailscale", err);
|
||||
// trigger log function somewhere to display a notification or alert to the user (but how?)
|
||||
throw new Error(`Error checking Tailscale ping: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the Tailscale ping command to the given URL.
|
||||
*
|
||||
* @param {string} hostname - The hostname to ping.
|
||||
* @returns {Promise<string>} - A Promise that resolves to the output of the Tailscale ping command
|
||||
* @throws Will throw an error if the command execution encounters any error.
|
||||
*/
|
||||
async runTailscalePing(hostname, interval) {
|
||||
let cmd = `tailscale ping ${hostname}`;
|
||||
|
||||
log.debug("Tailscale", cmd);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let timeout = interval * 1000 * 0.8;
|
||||
exec(cmd, { timeout: timeout }, (error, stdout, stderr) => {
|
||||
// we may need to handle more cases if tailscale reports an error that isn't necessarily an error (such as not-logged in or DERP health-related issues)
|
||||
if (error) {
|
||||
reject(`Execution error: ${error.message}`);
|
||||
return;
|
||||
}
|
||||
if (stderr) {
|
||||
reject(`Error in output: ${stderr}`);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(stdout);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the output of the Tailscale ping command to update the heartbeat.
|
||||
*
|
||||
* @param {string} tailscaleOutput - The output of the Tailscale ping command.
|
||||
* @param {Object} heartbeat - The heartbeat object to update.
|
||||
* @throws Will throw an eror if the output contains any unexpected string.
|
||||
*/
|
||||
parseTailscaleOutput(tailscaleOutput, heartbeat) {
|
||||
let lines = tailscaleOutput.split("\n");
|
||||
|
||||
for (let line of lines) {
|
||||
if (line.includes("pong from")) {
|
||||
heartbeat.status = UP;
|
||||
let time = line.split(" in ")[1].split(" ")[0];
|
||||
heartbeat.ping = parseInt(time);
|
||||
heartbeat.msg = line;
|
||||
break;
|
||||
} else if (line.includes("timed out")) {
|
||||
throw new Error(`Ping timed out: "${line}"`);
|
||||
// Immediately throws upon "timed out" message, the server is expected to re-call the check function
|
||||
} else if (line.includes("no matching peer")) {
|
||||
throw new Error(`Nonexistant or inaccessible due to ACLs: "${line}"`);
|
||||
} else if (line.includes("is local Tailscale IP")) {
|
||||
throw new Error(`Tailscale only works if used on other machines: "${line}"`);
|
||||
} else if (line !== "") {
|
||||
throw new Error(`Unexpected output: "${line}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
TailscalePing,
|
||||
};
|
@@ -18,7 +18,7 @@ class AliyunSMS extends NotificationProvider {
|
||||
status: this.statusToString(heartbeatJSON["status"]),
|
||||
msg: heartbeatJSON["msg"],
|
||||
});
|
||||
if (this.sendSms(notification, msgBody)) {
|
||||
if (await this.sendSms(notification, msgBody)) {
|
||||
return okMsg;
|
||||
}
|
||||
} else {
|
||||
@@ -28,7 +28,7 @@ class AliyunSMS extends NotificationProvider {
|
||||
status: "",
|
||||
msg: msg,
|
||||
});
|
||||
if (this.sendSms(notification, msgBody)) {
|
||||
if (await this.sendSms(notification, msgBody)) {
|
||||
return okMsg;
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,8 @@ class AliyunSMS extends NotificationProvider {
|
||||
if (result.data.Message === "OK") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
throw new Error(result.data.Message);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -30,6 +30,7 @@ class Discord extends NotificationProvider {
|
||||
break;
|
||||
case "port":
|
||||
case "dns":
|
||||
case "gamedig":
|
||||
case "steam":
|
||||
address = monitorJSON["hostname"];
|
||||
if (monitorJSON["port"]) {
|
||||
|
98
server/notification-providers/flashduty.js
Normal file
98
server/notification-providers/flashduty.js
Normal file
@@ -0,0 +1,98 @@
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
|
||||
const { setting } = require("../util-server");
|
||||
const successMessage = "Sent Successfully.";
|
||||
|
||||
class FlashDuty extends NotificationProvider {
|
||||
name = "FlashDuty";
|
||||
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
try {
|
||||
if (heartbeatJSON == null) {
|
||||
const title = "Uptime Kuma Alert";
|
||||
const monitor = {
|
||||
type: "ping",
|
||||
url: msg,
|
||||
name: "https://flashcat.cloud"
|
||||
};
|
||||
return this.postNotification(notification, title, msg, monitor);
|
||||
}
|
||||
|
||||
if (heartbeatJSON.status === UP) {
|
||||
const title = "Uptime Kuma Monitor ✅ Up";
|
||||
|
||||
return this.postNotification(notification, title, heartbeatJSON.msg, monitorJSON, "Ok");
|
||||
}
|
||||
|
||||
if (heartbeatJSON.status === DOWN) {
|
||||
const title = "Uptime Kuma Monitor 🔴 Down";
|
||||
return this.postNotification(notification, title, heartbeatJSON.msg, monitorJSON, notification.flashdutySeverity);
|
||||
}
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Generate a monitor url from the monitors infomation
|
||||
* @param {Object} monitorInfo Monitor details
|
||||
* @returns {string|undefined}
|
||||
*/
|
||||
|
||||
genMonitorUrl(monitorInfo) {
|
||||
if (monitorInfo.type === "port" && monitorInfo.port) {
|
||||
return monitorInfo.hostname + ":" + monitorInfo.port;
|
||||
}
|
||||
if (monitorInfo.hostname != null) {
|
||||
return monitorInfo.hostname;
|
||||
}
|
||||
return monitorInfo.url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the message
|
||||
* @param {BeanModel} notification Message title
|
||||
* @param {string} title Message
|
||||
* @param {string} body Message
|
||||
* @param {Object} monitorInfo Monitor details
|
||||
* @param {string} eventStatus Monitor status (Info, Warning, Critical, Ok)
|
||||
* @returns {string}
|
||||
*/
|
||||
async postNotification(notification, title, body, monitorInfo, eventStatus) {
|
||||
const options = {
|
||||
method: "POST",
|
||||
url: "https://api.flashcat.cloud/event/push/alert/standard?integration_key=" + notification.flashdutyIntegrationKey,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data: {
|
||||
description: `[${title}] [${monitorInfo.name}] ${body}`,
|
||||
title,
|
||||
event_status: eventStatus || "Info",
|
||||
alert_key: String(monitorInfo.id) || Math.random().toString(36).substring(7),
|
||||
labels: monitorInfo?.tags?.reduce((acc, item) => ({ ...acc,
|
||||
[item.name]: item.value
|
||||
}), { resource: this.genMonitorUrl(monitorInfo) }),
|
||||
}
|
||||
};
|
||||
|
||||
const baseURL = await setting("primaryBaseURL");
|
||||
if (baseURL && monitorInfo) {
|
||||
options.client = "Uptime Kuma";
|
||||
options.client_url = baseURL + getMonitorRelativeURL(monitorInfo.id);
|
||||
}
|
||||
|
||||
let result = await axios.request(options);
|
||||
if (result.status == null) {
|
||||
throw new Error("FlashDuty notification failed with invalid response!");
|
||||
}
|
||||
if (result.status < 200 || result.status >= 300) {
|
||||
throw new Error("FlashDuty notification failed with status code " + result.status);
|
||||
}
|
||||
if (result.statusText != null) {
|
||||
return "FlashDuty notification succeed: " + result.statusText;
|
||||
}
|
||||
|
||||
return successMessage;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FlashDuty;
|
119
server/notification-providers/nostr.js
Normal file
119
server/notification-providers/nostr.js
Normal file
@@ -0,0 +1,119 @@
|
||||
const { log } = require("../../src/util");
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const {
|
||||
relayInit,
|
||||
getPublicKey,
|
||||
getEventHash,
|
||||
getSignature,
|
||||
nip04,
|
||||
nip19
|
||||
} = require("nostr-tools");
|
||||
|
||||
// polyfills for node versions
|
||||
const semver = require("semver");
|
||||
const nodeVersion = process.version;
|
||||
if (semver.lt(nodeVersion, "16.0.0")) {
|
||||
log.warn("monitor", "Node <= 16 is unsupported for nostr, sorry :(");
|
||||
} else if (semver.lt(nodeVersion, "18.0.0")) {
|
||||
// polyfills for node 16
|
||||
global.crypto = require("crypto");
|
||||
global.WebSocket = require("isomorphic-ws");
|
||||
if (typeof crypto !== "undefined" && !crypto.subtle && crypto.webcrypto) {
|
||||
crypto.subtle = crypto.webcrypto.subtle;
|
||||
}
|
||||
} else if (semver.lt(nodeVersion, "20.0.0")) {
|
||||
// polyfills for node 18
|
||||
global.crypto = require("crypto");
|
||||
global.WebSocket = require("isomorphic-ws");
|
||||
} else {
|
||||
// polyfills for node 20
|
||||
global.WebSocket = require("isomorphic-ws");
|
||||
}
|
||||
|
||||
class Nostr extends NotificationProvider {
|
||||
name = "nostr";
|
||||
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
// All DMs should have same timestamp
|
||||
const createdAt = Math.floor(Date.now() / 1000);
|
||||
|
||||
const senderPrivateKey = await this.getPrivateKey(notification.sender);
|
||||
const senderPublicKey = getPublicKey(senderPrivateKey);
|
||||
const recipientsPublicKeys = await this.getPublicKeys(notification.recipients);
|
||||
|
||||
// Create NIP-04 encrypted direct message event for each recipient
|
||||
const events = [];
|
||||
for (const recipientPublicKey of recipientsPublicKeys) {
|
||||
const ciphertext = await nip04.encrypt(senderPrivateKey, recipientPublicKey, msg);
|
||||
let event = {
|
||||
kind: 4,
|
||||
pubkey: senderPublicKey,
|
||||
created_at: createdAt,
|
||||
tags: [[ "p", recipientPublicKey ]],
|
||||
content: ciphertext,
|
||||
};
|
||||
event.id = getEventHash(event);
|
||||
event.sig = getSignature(event, senderPrivateKey);
|
||||
events.push(event);
|
||||
}
|
||||
|
||||
// Publish events to each relay
|
||||
const relays = notification.relays.split("\n");
|
||||
let successfulRelays = 0;
|
||||
|
||||
// Connect to each relay
|
||||
for (const relayUrl of relays) {
|
||||
const relay = relayInit(relayUrl);
|
||||
try {
|
||||
await relay.connect();
|
||||
successfulRelays++;
|
||||
|
||||
// Publish events
|
||||
for (const event of events) {
|
||||
relay.publish(event);
|
||||
}
|
||||
} catch (error) {
|
||||
continue;
|
||||
} finally {
|
||||
relay.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Report success or failure
|
||||
if (successfulRelays === 0) {
|
||||
throw Error("Failed to connect to any relays.");
|
||||
}
|
||||
return `${successfulRelays}/${relays.length} relays connected.`;
|
||||
}
|
||||
|
||||
async getPrivateKey(sender) {
|
||||
try {
|
||||
const senderDecodeResult = await nip19.decode(sender);
|
||||
const { data } = senderDecodeResult;
|
||||
return data;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to get private key: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async getPublicKeys(recipients) {
|
||||
const recipientsList = recipients.split("\n");
|
||||
const publicKeys = [];
|
||||
for (const recipient of recipientsList) {
|
||||
try {
|
||||
const recipientDecodeResult = await nip19.decode(recipient);
|
||||
const { type, data } = recipientDecodeResult;
|
||||
if (type === "npub") {
|
||||
publicKeys.push(data);
|
||||
} else {
|
||||
throw new Error("not an npub");
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(`Error decoding recipient: ${error}`);
|
||||
}
|
||||
}
|
||||
return publicKeys;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Nostr;
|
@@ -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:
|
||||
|
@@ -8,7 +8,9 @@ class PushDeer extends NotificationProvider {
|
||||
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
let okMsg = "Sent Successfully.";
|
||||
let pushdeerlink = "https://api2.pushdeer.com/message/push";
|
||||
let endpoint = "/message/push";
|
||||
let serverUrl = notification.pushdeerServer || "https://api2.pushdeer.com";
|
||||
let pushdeerlink = `${serverUrl.trim().replace(/\/*$/, "")}${endpoint}`;
|
||||
|
||||
let valid = msg != null && monitorJSON != null && heartbeatJSON != null;
|
||||
|
||||
|
@@ -13,7 +13,7 @@ class SMTP extends NotificationProvider {
|
||||
port: notification.smtpPort,
|
||||
secure: notification.smtpSecure,
|
||||
tls: {
|
||||
rejectUnauthorized: notification.smtpIgnoreTLSError || false,
|
||||
rejectUnauthorized: !notification.smtpIgnoreTLSError || false,
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -21,11 +21,13 @@ const LineNotify = require("./notification-providers/linenotify");
|
||||
const LunaSea = require("./notification-providers/lunasea");
|
||||
const Matrix = require("./notification-providers/matrix");
|
||||
const Mattermost = require("./notification-providers/mattermost");
|
||||
const Nostr = require("./notification-providers/nostr");
|
||||
const Ntfy = require("./notification-providers/ntfy");
|
||||
const Octopush = require("./notification-providers/octopush");
|
||||
const OneBot = require("./notification-providers/onebot");
|
||||
const Opsgenie = require("./notification-providers/opsgenie");
|
||||
const PagerDuty = require("./notification-providers/pagerduty");
|
||||
const FlashDuty = require("./notification-providers/flashduty");
|
||||
const PagerTree = require("./notification-providers/pagertree");
|
||||
const PromoSMS = require("./notification-providers/promosms");
|
||||
const Pushbullet = require("./notification-providers/pushbullet");
|
||||
@@ -84,11 +86,13 @@ class Notification {
|
||||
new LunaSea(),
|
||||
new Matrix(),
|
||||
new Mattermost(),
|
||||
new Nostr(),
|
||||
new Ntfy(),
|
||||
new Octopush(),
|
||||
new OneBot(),
|
||||
new Opsgenie(),
|
||||
new PagerDuty(),
|
||||
new FlashDuty(),
|
||||
new PagerTree(),
|
||||
new PromoSMS(),
|
||||
new Pushbullet(),
|
||||
@@ -115,7 +119,6 @@ class Notification {
|
||||
new GoAlert(),
|
||||
new ZohoCliq()
|
||||
];
|
||||
|
||||
for (let item of list) {
|
||||
if (! item.name) {
|
||||
throw new Error("Notification provider without name");
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -49,6 +49,7 @@ if (! process.env.NODE_ENV) {
|
||||
}
|
||||
|
||||
log.info("server", "Node Env: " + process.env.NODE_ENV);
|
||||
log.info("server", "Inside Container: " + (process.env.UPTIME_KUMA_IS_CONTAINER === "1"));
|
||||
|
||||
log.info("server", "Importing Node libraries");
|
||||
const fs = require("fs");
|
||||
@@ -82,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");
|
||||
@@ -296,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");
|
||||
@@ -315,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.",
|
||||
@@ -356,9 +366,7 @@ let needSetup = false;
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
token: jwt.sign({
|
||||
username: data.username,
|
||||
}, server.jwtSecret),
|
||||
token: User.createJWT(user, server.jwtSecret),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -386,9 +394,7 @@ let needSetup = false;
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
token: jwt.sign({
|
||||
username: data.username,
|
||||
}, server.jwtSecret),
|
||||
token: User.createJWT(user, server.jwtSecret),
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -640,6 +646,10 @@ let needSetup = false;
|
||||
let notificationIDList = monitor.notificationIDList;
|
||||
delete monitor.notificationIDList;
|
||||
|
||||
// Ensure status code ranges are strings
|
||||
if (!monitor.accepted_statuscodes.every((code) => typeof code === "string")) {
|
||||
throw new Error("Accepted status codes are not all strings");
|
||||
}
|
||||
monitor.accepted_statuscodes_json = JSON.stringify(monitor.accepted_statuscodes);
|
||||
delete monitor.accepted_statuscodes;
|
||||
|
||||
@@ -656,7 +666,10 @@ let needSetup = false;
|
||||
await updateMonitorNotification(bean.id, notificationIDList);
|
||||
|
||||
await server.sendMonitorList(socket);
|
||||
await startMonitor(socket.userID, bean.id);
|
||||
|
||||
if (monitor.active !== false) {
|
||||
await startMonitor(socket.userID, bean.id);
|
||||
}
|
||||
|
||||
log.info("monitor", `Added Monitor: ${monitor.id} User ID: ${socket.userID}`);
|
||||
|
||||
@@ -702,6 +715,11 @@ let needSetup = false;
|
||||
removeGroupChildren = true;
|
||||
}
|
||||
|
||||
// Ensure status code ranges are strings
|
||||
if (!monitor.accepted_statuscodes.every((code) => typeof code === "string")) {
|
||||
throw new Error("Accepted status codes are not all strings");
|
||||
}
|
||||
|
||||
bean.name = monitor.name;
|
||||
bean.description = monitor.description;
|
||||
bean.parent = monitor.parent;
|
||||
@@ -712,6 +730,12 @@ let needSetup = false;
|
||||
bean.headers = monitor.headers;
|
||||
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 = 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;
|
||||
@@ -765,6 +789,10 @@ 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();
|
||||
|
||||
@@ -776,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);
|
||||
}
|
||||
|
||||
@@ -1096,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.",
|
||||
@@ -1177,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;
|
||||
@@ -1194,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"
|
||||
@@ -1363,6 +1398,7 @@ let needSetup = false;
|
||||
|
||||
// Define default values
|
||||
let retryInterval = 0;
|
||||
let timeout = monitorListData[i].timeout || (monitorListData[i].interval * 0.8); // fallback to old value
|
||||
|
||||
/*
|
||||
Only replace the default value with the backup file data for the specific version, where it appears the first time
|
||||
@@ -1388,6 +1424,7 @@ let needSetup = false;
|
||||
basic_auth_pass: monitorListData[i].basic_auth_pass,
|
||||
authWorkstation: monitorListData[i].authWorkstation,
|
||||
authDomain: monitorListData[i].authDomain,
|
||||
timeout,
|
||||
interval: monitorListData[i].interval,
|
||||
retryInterval: retryInterval,
|
||||
resendInterval: monitorListData[i].resendInterval || 0,
|
||||
@@ -1589,6 +1626,8 @@ let needSetup = false;
|
||||
await shutdownFunction();
|
||||
});
|
||||
|
||||
server.start();
|
||||
|
||||
server.httpServer.listen(port, hostname, () => {
|
||||
if (hostname) {
|
||||
log.info("server", `Listening on ${hostname}:${port}`);
|
||||
@@ -1793,6 +1832,7 @@ async function pauseMonitor(userID, monitorID) {
|
||||
|
||||
if (monitorID in server.monitorList) {
|
||||
server.monitorList[monitorID].stop();
|
||||
server.monitorList[monitorID].active = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1851,8 +1891,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);
|
||||
|
@@ -162,6 +162,7 @@ module.exports.statusPageSocketHandler = (socket) => {
|
||||
statusPage.footer_text = config.footerText;
|
||||
statusPage.custom_css = config.customCSS;
|
||||
statusPage.show_powered_by = config.showPoweredBy;
|
||||
statusPage.show_certificate_expiry = config.showCertificateExpiry;
|
||||
statusPage.modified_date = R.isoDateTime();
|
||||
statusPage.google_analytics_tag_id = config.googleAnalyticsId;
|
||||
|
||||
|
@@ -10,6 +10,9 @@ const util = require("util");
|
||||
const { CacheableDnsHttpAgent } = require("./cacheable-dns-http-agent");
|
||||
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.
|
||||
|
||||
/**
|
||||
@@ -60,6 +63,8 @@ class UptimeKumaServer {
|
||||
*/
|
||||
jwtSecret = null;
|
||||
|
||||
checkMonitorsInterval = null;
|
||||
|
||||
static getInstance(args) {
|
||||
if (UptimeKumaServer.instance == null) {
|
||||
UptimeKumaServer.instance = new UptimeKumaServer(args);
|
||||
@@ -73,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) {
|
||||
@@ -99,6 +107,7 @@ class UptimeKumaServer {
|
||||
|
||||
// Set Monitor Types
|
||||
UptimeKumaServer.monitorTypeList["real-browser"] = new RealBrowserMonitorType();
|
||||
UptimeKumaServer.monitorTypeList["tailscale-ping"] = new TailscalePing();
|
||||
|
||||
this.io = new Server(this.httpServer);
|
||||
}
|
||||
@@ -212,7 +221,7 @@ class UptimeKumaServer {
|
||||
* @param {boolean} outputToConsole Should the error also be output to console?
|
||||
*/
|
||||
static errorLog(error, outputToConsole = true) {
|
||||
const errorLogStream = fs.createWriteStream(Database.dataDir + "/error.log", {
|
||||
const errorLogStream = fs.createWriteStream(path.join(Database.dataDir, "/error.log"), {
|
||||
flags: "a"
|
||||
});
|
||||
|
||||
@@ -333,9 +342,140 @@ class UptimeKumaServer {
|
||||
dayjs.tz.setDefault(timezone);
|
||||
}
|
||||
|
||||
/** Stop the server */
|
||||
async stop() {
|
||||
/**
|
||||
* TODO: Listen logic should be moved to here
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async start() {
|
||||
let enable = await Settings.get("nscd");
|
||||
|
||||
if (enable || enable === null) {
|
||||
this.startNSCDServices();
|
||||
}
|
||||
|
||||
this.checkMonitorsInterval = setInterval(() => {
|
||||
this.checkMonitors();
|
||||
}, 60 * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the server
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async stop() {
|
||||
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
|
||||
*/
|
||||
startNSCDServices() {
|
||||
if (process.env.UPTIME_KUMA_IS_CONTAINER) {
|
||||
try {
|
||||
log.info("services", "Starting nscd");
|
||||
childProcess.execSync("sudo service nscd start", { stdio: "pipe" });
|
||||
} catch (e) {
|
||||
log.info("services", "Failed to start nscd");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop all system services
|
||||
*/
|
||||
stopNSCDServices() {
|
||||
if (process.env.UPTIME_KUMA_IS_CONTAINER) {
|
||||
try {
|
||||
log.info("services", "Stopping nscd");
|
||||
childProcess.execSync("sudo service nscd stop");
|
||||
} catch (e) {
|
||||
log.info("services", "Failed to stop nscd");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,3 +485,4 @@ module.exports = {
|
||||
|
||||
// Must be at the end to avoid circular dependencies
|
||||
const { RealBrowserMonitorType } = require("./monitor-types/real-browser-monitor-type");
|
||||
const { TailscalePing } = require("./monitor-types/tailscale-ping");
|
||||
|
@@ -21,6 +21,9 @@ const grpc = require("@grpc/grpc-js");
|
||||
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: {
|
||||
rfc2865: { file, attributes },
|
||||
@@ -31,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);
|
||||
/**
|
||||
@@ -52,6 +56,43 @@ exports.initJWTSecret = async () => {
|
||||
return jwtSecretBean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decodes a jwt and returns the payload portion without verifying the jqt.
|
||||
* @param {string} jwt The input jwt as a string
|
||||
* @returns {Object} Decoded jwt payload object
|
||||
*/
|
||||
exports.decodeJwt = (jwt) => {
|
||||
return JSON.parse(Buffer.from(jwt.split(".")[1], "base64").toString());
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a Access Token form a oidc/oauth2 provider
|
||||
* @param {string} tokenEndpoint The token URI form the auth service provider
|
||||
* @param {string} clientId The oidc/oauth application client id
|
||||
* @param {string} clientSecret The oidc/oauth application client secret
|
||||
* @param {string} scope The scope the for which the token should be issued for
|
||||
* @param {string} authMethod The method on how to sent the credentials. Default client_secret_basic
|
||||
* @returns {Promise<oidc.TokenSet>} TokenSet promise if the token request was successful
|
||||
*/
|
||||
exports.getOidcTokenClientCredentials = async (tokenEndpoint, clientId, clientSecret, scope, authMethod = "client_secret_basic") => {
|
||||
const oauthProvider = new oidc.Issuer({ token_endpoint: tokenEndpoint });
|
||||
let client = new oauthProvider.Client({
|
||||
client_id: clientId,
|
||||
client_secret: clientSecret,
|
||||
token_endpoint_auth_method: authMethod
|
||||
});
|
||||
|
||||
// Increase default timeout and clock tolerance
|
||||
client[oidc.custom.http_options] = () => ({ timeout: 10000 });
|
||||
client[oidc.custom.clock_tolerance] = 5;
|
||||
|
||||
let grantParams = { grant_type: "client_credentials" };
|
||||
if (scope) {
|
||||
grantParams.scope = scope;
|
||||
}
|
||||
return await client.grant(grantParams);
|
||||
};
|
||||
|
||||
/**
|
||||
* Send TCP request to specified hostname and port
|
||||
* @param {string} hostname Hostname / address of machine
|
||||
@@ -247,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) => {
|
||||
@@ -274,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", (_) => {
|
||||
@@ -353,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) {
|
||||
@@ -373,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) {
|
||||
@@ -413,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);
|
||||
@@ -486,6 +546,7 @@ exports.radius = function (
|
||||
host: hostname,
|
||||
hostPort: port,
|
||||
timeout: timeout,
|
||||
retries: 1,
|
||||
dictionaries: [ file ],
|
||||
});
|
||||
|
||||
@@ -497,6 +558,12 @@ exports.radius = function (
|
||||
[ attributes.CALLING_STATION_ID, callingStationId ],
|
||||
[ attributes.CALLED_STATION_ID, calledStationId ],
|
||||
],
|
||||
}).catch((error) => {
|
||||
if (error.response?.code) {
|
||||
throw Error(error.response.code);
|
||||
} else {
|
||||
throw Error(error.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -674,7 +741,6 @@ exports.checkCertificate = function (res) {
|
||||
* @param {number} status The status code to check
|
||||
* @param {string[]} acceptedCodes An array of accepted status codes
|
||||
* @returns {boolean} True if status code within range, false otherwise
|
||||
* @throws {Error} Will throw an error if the provided status code is not a valid range string or code string
|
||||
*/
|
||||
exports.checkStatusCode = function (status, acceptedCodes) {
|
||||
if (acceptedCodes == null || acceptedCodes.length === 0) {
|
||||
@@ -682,6 +748,11 @@ exports.checkStatusCode = function (status, acceptedCodes) {
|
||||
}
|
||||
|
||||
for (const codeRange of acceptedCodes) {
|
||||
if (typeof codeRange !== "string") {
|
||||
log.error("monitor", `Accepted status code not a string. ${codeRange} is of type ${typeof codeRange}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const codeRangeSplit = codeRange.split("-").map(string => parseInt(string));
|
||||
if (codeRangeSplit.length === 1) {
|
||||
if (status === codeRangeSplit[0]) {
|
||||
@@ -692,7 +763,8 @@ exports.checkStatusCode = function (status, acceptedCodes) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
throw new Error("Invalid status code range");
|
||||
log.error("monitor", `${codeRange} is not a valid status code range`);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1001,3 +1073,81 @@ 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 = {
|
||||
parseCertificateInfo,
|
||||
};
|
||||
module.exports.__getPrivateFunction = (functionName) => {
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -111,6 +111,10 @@ optgroup {
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
border-radius: 25px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
color: white;
|
||||
|
||||
@@ -158,6 +162,26 @@ optgroup {
|
||||
background-color: #161B22;
|
||||
}
|
||||
|
||||
.btn-outline-normal {
|
||||
padding: 4px 10px;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 25px;
|
||||
background-color: transparent;
|
||||
|
||||
.dark & {
|
||||
color: $dark-font-color;
|
||||
border: 1px solid $dark-font-color2;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: $highlight-white;
|
||||
|
||||
.dark & {
|
||||
background-color: $dark-font-color2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 550px) {
|
||||
.table-shadow-box {
|
||||
padding: 10px !important;
|
||||
@@ -436,7 +460,6 @@ optgroup {
|
||||
.monitor-list {
|
||||
&.scrollbar {
|
||||
overflow-y: auto;
|
||||
height: calc(100% - 107px);
|
||||
}
|
||||
|
||||
@media (max-width: 770px) {
|
||||
|
86
src/components/ActionSelect.vue
Normal file
86
src/components/ActionSelect.vue
Normal file
@@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<div class="input-group mb-3">
|
||||
<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" :class="{ disabled: actionDisabled }" @click="action()">
|
||||
<font-awesome-icon :icon="icon" />
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* Generic select field with a customizable action on the right.
|
||||
* Action is passed in as a function.
|
||||
*/
|
||||
export default {
|
||||
props: {
|
||||
options: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
/**
|
||||
* The value of the select field.
|
||||
*/
|
||||
modelValue: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
/**
|
||||
* Whether the select field is enabled / disabled.
|
||||
*/
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* The icon displayed in the right button of the select field.
|
||||
* Accepts a Font Awesome icon string identifier.
|
||||
* @example "plus"
|
||||
*/
|
||||
icon: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
/**
|
||||
* The action to be performed when the button is clicked.
|
||||
* Action is passed in as a function.
|
||||
*/
|
||||
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" ],
|
||||
computed: {
|
||||
/**
|
||||
* Send value update to parent on change.
|
||||
*/
|
||||
model: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(value) {
|
||||
this.$emit("update:modelValue", value);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
56
src/components/CreateGroupDialog.vue
Normal file
56
src/components/CreateGroupDialog.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div ref="modal" class="modal fade" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
{{ $t("New Group") }}
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" />
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form @submit.prevent="confirm">
|
||||
<div>
|
||||
<label for="draftGroupName" class="form-label">{{ $t("Group Name") }}</label>
|
||||
<input id="draftGroupName" v-model="groupName" type="text" class="form-control">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
{{ $t("Cancel") }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" :disabled="groupName == '' || groupName == null" @click="confirm">
|
||||
{{ $t("Confirm") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Modal } from "bootstrap";
|
||||
|
||||
export default {
|
||||
props: {},
|
||||
emits: [ "added" ],
|
||||
data: () => ({
|
||||
modal: null,
|
||||
groupName: null,
|
||||
}),
|
||||
mounted() {
|
||||
this.modal = new Modal(this.$refs.modal);
|
||||
},
|
||||
methods: {
|
||||
/** Show the confirm dialog */
|
||||
show() {
|
||||
this.modal.show();
|
||||
},
|
||||
confirm() {
|
||||
this.$emit("added", this.groupName);
|
||||
this.modal.hide();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
@@ -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();
|
||||
}
|
||||
});
|
||||
|
@@ -5,15 +5,24 @@
|
||||
v-for="(beat, index) in shortBeatList"
|
||||
:key="index"
|
||||
class="beat"
|
||||
:class="{ 'empty' : (beat === 0), 'down' : (beat.status === 0), 'pending' : (beat.status === 2), 'maintenance' : (beat.status === 3) }"
|
||||
:class="{ 'empty': (beat === 0), 'down': (beat.status === 0), 'pending': (beat.status === 2), 'maintenance': (beat.status === 3) }"
|
||||
:style="beatStyle"
|
||||
:title="getBeatTitle(beat)"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
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>
|
||||
<div v-if="$root.styleElapsedTime === 'with-line'" class="connecting-line"></div>
|
||||
<div>{{ timeSinceLastBeat }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dayjs from "dayjs";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@@ -56,8 +65,30 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Calculates the amount of beats of padding needed to fill the length of shortBeatList.
|
||||
*
|
||||
* @return {number} The amount of beats of padding needed to fill the length of shortBeatList.
|
||||
*/
|
||||
numPadding() {
|
||||
if (!this.beatList) {
|
||||
return 0;
|
||||
}
|
||||
let num = this.beatList.length - this.maxBeat;
|
||||
|
||||
if (this.move) {
|
||||
num = num - 1;
|
||||
}
|
||||
|
||||
if (num > 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1 * num;
|
||||
},
|
||||
|
||||
shortBeatList() {
|
||||
if (! this.beatList) {
|
||||
if (!this.beatList) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -115,6 +146,53 @@ export default {
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the style object for positioning the time element.
|
||||
* @return {Object} The style object containing the CSS properties for positioning the time element.
|
||||
*/
|
||||
timeStyle() {
|
||||
return {
|
||||
"margin-left": this.numPadding * (this.beatWidth + this.beatMargin * 2) + "px",
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Calculates the time elapsed since the first valid beat.
|
||||
*
|
||||
* @return {string} The time elapsed in minutes or hours.
|
||||
*/
|
||||
timeSinceFirstBeat() {
|
||||
const firstValidBeat = this.shortBeatList.at(this.numPadding);
|
||||
const minutes = dayjs().diff(dayjs.utc(firstValidBeat?.time), "minutes");
|
||||
if (minutes > 60) {
|
||||
return (minutes / 60).toFixed(0) + "h";
|
||||
} else {
|
||||
return minutes + "m";
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Calculates the elapsed time since the last valid beat was registered.
|
||||
*
|
||||
* @return {string} The elapsed time in a minutes, hours or "now".
|
||||
*/
|
||||
timeSinceLastBeat() {
|
||||
const lastValidBeat = this.shortBeatList.at(-1);
|
||||
const seconds = dayjs().diff(dayjs.utc(lastValidBeat?.time), "seconds");
|
||||
|
||||
let tolerance = 60 * 2; // default for when monitorList not available
|
||||
if (this.$root.monitorList[this.monitorId] != null) {
|
||||
tolerance = this.$root.monitorList[this.monitorId].interval * 2;
|
||||
}
|
||||
|
||||
if (seconds < tolerance) {
|
||||
return "now";
|
||||
} else if (seconds < 60 * 60) {
|
||||
return (seconds / 60).toFixed(0) + "m ago";
|
||||
} else {
|
||||
return (seconds / 60 / 60).toFixed(0) + "h ago";
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
beatList: {
|
||||
@@ -133,14 +211,14 @@ export default {
|
||||
},
|
||||
beforeMount() {
|
||||
if (this.heartbeatList === null) {
|
||||
if (! (this.monitorId in this.$root.heartbeatList)) {
|
||||
if (!(this.monitorId in this.$root.heartbeatList)) {
|
||||
this.$root.heartbeatList[this.monitorId] = [];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.size === "small") {
|
||||
if (this.size !== "big") {
|
||||
this.beatWidth = 5;
|
||||
this.beatHeight = 16;
|
||||
this.beatMargin = 2;
|
||||
@@ -151,11 +229,11 @@ export default {
|
||||
const actualWidth = this.beatWidth * window.devicePixelRatio;
|
||||
const actualMargin = this.beatMargin * window.devicePixelRatio;
|
||||
|
||||
if (! Number.isInteger(actualWidth)) {
|
||||
if (!Number.isInteger(actualWidth)) {
|
||||
this.beatWidth = Math.round(actualWidth) / window.devicePixelRatio;
|
||||
}
|
||||
|
||||
if (! Number.isInteger(actualMargin)) {
|
||||
if (!Number.isInteger(actualMargin)) {
|
||||
this.beatMargin = Math.round(actualMargin) / window.devicePixelRatio;
|
||||
}
|
||||
|
||||
@@ -229,4 +307,21 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.word {
|
||||
color: #aaa;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.connecting-line {
|
||||
flex-grow: 1;
|
||||
height: 1px;
|
||||
background-color: #ededed;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
margin-top: 2px;
|
||||
|
||||
.dark & {
|
||||
background-color: #333;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -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>
|
||||
|
@@ -2,6 +2,10 @@
|
||||
<div class="shadow-box mb-3" :style="boxStyle">
|
||||
<div class="list-header">
|
||||
<div class="header-top">
|
||||
<button class="btn btn-outline-normal ms-2" :class="{ 'active': selectMode }" type="button" @click="selectMode = !selectMode">
|
||||
{{ $t("Select") }}
|
||||
</button>
|
||||
|
||||
<div class="placeholder"></div>
|
||||
<div class="search-wrapper">
|
||||
<a v-if="searchText == ''" class="search-icon">
|
||||
@@ -21,27 +25,55 @@
|
||||
<div class="header-filter">
|
||||
<MonitorListFilter :filterState="filterState" @update-filter="updateFilter" />
|
||||
</div>
|
||||
|
||||
<!-- Selection Controls -->
|
||||
<div v-if="selectMode" class="selection-controls px-2 pt-2">
|
||||
<input
|
||||
v-model="selectAll"
|
||||
class="form-check-input select-input"
|
||||
type="checkbox"
|
||||
/>
|
||||
|
||||
<button class="btn-outline-normal" @click="pauseDialog"><font-awesome-icon icon="pause" size="sm" /> {{ $t("Pause") }}</button>
|
||||
<button class="btn-outline-normal" @click="resumeSelected"><font-awesome-icon icon="play" size="sm" /> {{ $t("Resume") }}</button>
|
||||
|
||||
<span v-if="selectedMonitorCount > 0">
|
||||
{{ $t("selectedMonitorCount", [ selectedMonitorCount ]) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="monitor-list" :class="{ scrollbar: scrollbar }">
|
||||
<div ref="monitorList" class="monitor-list" :class="{ scrollbar: scrollbar }" :style="monitorListStyle">
|
||||
<div v-if="Object.keys($root.monitorList).length === 0" class="text-center mt-3">
|
||||
{{ $t("No Monitors, please") }} <router-link to="/add">{{ $t("add one") }}</router-link>
|
||||
</div>
|
||||
|
||||
<MonitorListItem
|
||||
v-for="(item, index) in sortedMonitorList" :key="index" :monitor="item"
|
||||
:isSearch="searchText !== ''"
|
||||
v-for="(item, index) in sortedMonitorList"
|
||||
:key="index"
|
||||
:monitor="item"
|
||||
:showPathName="filtersActive"
|
||||
:isSelectMode="selectMode"
|
||||
:isSelected="isSelected"
|
||||
:select="select"
|
||||
:deselect="deselect"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Confirm ref="confirmPause" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="pauseSelected">
|
||||
{{ $t("pauseMonitorMsg") }}
|
||||
</Confirm>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Confirm from "../components/Confirm.vue";
|
||||
import MonitorListItem from "../components/MonitorListItem.vue";
|
||||
import MonitorListFilter from "./MonitorListFilter.vue";
|
||||
import { getMonitorRelativeURL } from "../util.ts";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Confirm,
|
||||
MonitorListItem,
|
||||
MonitorListFilter,
|
||||
},
|
||||
@@ -54,6 +86,10 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
searchText: "",
|
||||
selectMode: false,
|
||||
selectAll: false,
|
||||
disableSelectAllWatcher: false,
|
||||
selectedMonitors: {},
|
||||
windowTop: 0,
|
||||
filterState: {
|
||||
status: null,
|
||||
@@ -81,31 +117,68 @@ export default {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a sorted list of monitors based on the applied filters and search text.
|
||||
*
|
||||
* @return {Array} The sorted list of monitors.
|
||||
*/
|
||||
sortedMonitorList() {
|
||||
let result = Object.values(this.$root.monitorList);
|
||||
|
||||
// Simple filter by search text
|
||||
// finds monitor name, tag name or tag value
|
||||
if (this.searchText !== "") {
|
||||
const loweredSearchText = this.searchText.toLowerCase();
|
||||
result = result.filter(monitor => {
|
||||
return monitor.name.toLowerCase().includes(loweredSearchText)
|
||||
result = result.filter(monitor => {
|
||||
// filter by search text
|
||||
// finds monitor name, tag name or tag value
|
||||
let searchTextMatch = true;
|
||||
if (this.searchText !== "") {
|
||||
const loweredSearchText = this.searchText.toLowerCase();
|
||||
searchTextMatch =
|
||||
monitor.name.toLowerCase().includes(loweredSearchText)
|
||||
|| monitor.tags.find(tag => tag.name.toLowerCase().includes(loweredSearchText)
|
||||
|| tag.value?.toLowerCase().includes(loweredSearchText));
|
||||
});
|
||||
} else {
|
||||
result = result.filter(monitor => monitor.parent === null);
|
||||
}
|
||||
}
|
||||
|
||||
// filter by status
|
||||
let statusMatch = true;
|
||||
if (this.filterState.status != null && this.filterState.status.length > 0) {
|
||||
if (monitor.id in this.$root.lastHeartbeatList && this.$root.lastHeartbeatList[monitor.id]) {
|
||||
monitor.status = this.$root.lastHeartbeatList[monitor.id].status;
|
||||
}
|
||||
statusMatch = this.filterState.status.includes(monitor.status);
|
||||
}
|
||||
|
||||
// filter by active
|
||||
let activeMatch = true;
|
||||
if (this.filterState.active != null && this.filterState.active.length > 0) {
|
||||
activeMatch = this.filterState.active.includes(monitor.active);
|
||||
}
|
||||
|
||||
// filter by tags
|
||||
let tagsMatch = true;
|
||||
if (this.filterState.tags != null && this.filterState.tags.length > 0) {
|
||||
tagsMatch = monitor.tags.map(tag => tag.tag_id) // convert to array of tag IDs
|
||||
.filter(monitorTagId => this.filterState.tags.includes(monitorTagId)) // perform Array Intersaction between filter and monitor's tags
|
||||
.length > 0;
|
||||
}
|
||||
|
||||
// Hide children if not filtering
|
||||
let showChild = true;
|
||||
if (this.filterState.status == null && this.filterState.active == null && this.filterState.tags == null && this.searchText === "") {
|
||||
if (monitor.parent !== null) {
|
||||
showChild = false;
|
||||
}
|
||||
}
|
||||
|
||||
return searchTextMatch && statusMatch && activeMatch && tagsMatch && showChild;
|
||||
});
|
||||
|
||||
// Filter result by active state, weight and alphabetical
|
||||
result.sort((m1, m2) => {
|
||||
|
||||
if (m1.active !== m2.active) {
|
||||
if (m1.active === 0) {
|
||||
if (m1.active === false) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (m2.active === 0) {
|
||||
if (m2.active === false) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -123,29 +196,69 @@ export default {
|
||||
return m1.name.localeCompare(m2.name);
|
||||
});
|
||||
|
||||
if (this.filterState.status != null && this.filterState.status.length > 0) {
|
||||
result.map(monitor => {
|
||||
if (monitor.id in this.$root.lastHeartbeatList && this.$root.lastHeartbeatList[monitor.id]) {
|
||||
monitor.status = this.$root.lastHeartbeatList[monitor.id].status;
|
||||
}
|
||||
});
|
||||
result = result.filter(monitor => this.filterState.status.includes(monitor.status));
|
||||
}
|
||||
|
||||
if (this.filterState.active != null && this.filterState.active.length > 0) {
|
||||
result = result.filter(monitor => this.filterState.active.includes(monitor.active));
|
||||
}
|
||||
|
||||
if (this.filterState.tags != null && this.filterState.tags.length > 0) {
|
||||
result = result.filter(monitor => {
|
||||
return monitor.tags.map(tag => tag.tag_id) // convert to array of tag IDs
|
||||
.filter(monitorTagId => this.filterState.tags.includes(monitorTagId)) // perform Array Intersaction between filter and monitor's tags
|
||||
.length > 0;
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
isDarkTheme() {
|
||||
return document.body.classList.contains("dark");
|
||||
},
|
||||
|
||||
monitorListStyle() {
|
||||
let listHeaderHeight = 107;
|
||||
|
||||
if (this.selectMode) {
|
||||
listHeaderHeight += 42;
|
||||
}
|
||||
|
||||
return {
|
||||
"height": `calc(100% - ${listHeaderHeight}px)`
|
||||
};
|
||||
},
|
||||
|
||||
selectedMonitorCount() {
|
||||
return Object.keys(this.selectedMonitors).length;
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines if any filters are active.
|
||||
*
|
||||
* @return {boolean} True if any filter is active, false otherwise.
|
||||
*/
|
||||
filtersActive() {
|
||||
return this.filterState.status != null || this.filterState.active != null || this.filterState.tags != null || this.searchText !== "";
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
searchText() {
|
||||
for (let monitor of this.sortedMonitorList) {
|
||||
if (!this.selectedMonitors[monitor.id]) {
|
||||
if (this.selectAll) {
|
||||
this.disableSelectAllWatcher = true;
|
||||
this.selectAll = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
selectAll() {
|
||||
if (!this.disableSelectAllWatcher) {
|
||||
this.selectedMonitors = {};
|
||||
|
||||
if (this.selectAll) {
|
||||
this.sortedMonitorList.forEach((item) => {
|
||||
this.selectedMonitors[item.id] = true;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.disableSelectAllWatcher = false;
|
||||
}
|
||||
},
|
||||
selectMode() {
|
||||
if (!this.selectMode) {
|
||||
this.selectAll = false;
|
||||
this.selectedMonitors = {};
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener("scroll", this.onScroll);
|
||||
@@ -181,6 +294,53 @@ export default {
|
||||
updateFilter(newFilter) {
|
||||
this.filterState = newFilter;
|
||||
},
|
||||
/**
|
||||
* Deselect a monitor
|
||||
* @param {number} id ID of monitor
|
||||
*/
|
||||
deselect(id) {
|
||||
delete this.selectedMonitors[id];
|
||||
},
|
||||
/**
|
||||
* Select a monitor
|
||||
* @param {number} id ID of monitor
|
||||
*/
|
||||
select(id) {
|
||||
this.selectedMonitors[id] = true;
|
||||
},
|
||||
/**
|
||||
* Determine if monitor is selected
|
||||
* @param {number} id ID of monitor
|
||||
* @returns {bool}
|
||||
*/
|
||||
isSelected(id) {
|
||||
return id in this.selectedMonitors;
|
||||
},
|
||||
/** Disable select mode and reset selection */
|
||||
cancelSelectMode() {
|
||||
this.selectMode = false;
|
||||
this.selectedMonitors = {};
|
||||
},
|
||||
/** Show dialog to confirm pause */
|
||||
pauseDialog() {
|
||||
this.$refs.confirmPause.show();
|
||||
},
|
||||
/** Pause each selected monitor */
|
||||
pauseSelected() {
|
||||
Object.keys(this.selectedMonitors)
|
||||
.filter(id => this.$root.monitorList[id].active)
|
||||
.forEach(id => this.$root.getSocket().emit("pauseMonitor", id));
|
||||
|
||||
this.cancelSelectMode();
|
||||
},
|
||||
/** Resume each selected monitor */
|
||||
resumeSelected() {
|
||||
Object.keys(this.selectedMonitors)
|
||||
.filter(id => !this.$root.monitorList[id].active)
|
||||
.forEach(id => this.$root.getSocket().emit("resumeMonitor", id));
|
||||
|
||||
this.cancelSelectMode();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -271,4 +431,12 @@ export default {
|
||||
padding-left: 67px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.selection-controls {
|
||||
margin-top: 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
@@ -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>
|
||||
@@ -44,6 +44,7 @@ export default {
|
||||
|
||||
<style lang="scss">
|
||||
@import "../assets/vars.scss";
|
||||
@import "../assets/app.scss";
|
||||
|
||||
.filter-dropdown-menu {
|
||||
z-index: 100;
|
||||
@@ -102,17 +103,22 @@ export default {
|
||||
}
|
||||
|
||||
.filter-dropdown-status {
|
||||
@extend .btn-outline-normal;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px 10px;
|
||||
margin-left: 5px;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 25px;
|
||||
background-color: transparent;
|
||||
color: $link-color;
|
||||
|
||||
.dark & {
|
||||
color: $dark-font-color;
|
||||
border: 1px solid $dark-font-color2;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background-color: $highlight-white;
|
||||
|
||||
.dark & {
|
||||
background-color: $dark-font-color2;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
|
@@ -1,34 +1,56 @@
|
||||
<template>
|
||||
<div>
|
||||
<router-link :to="monitorURL(monitor.id)" class="item" :class="{ 'disabled': ! monitor.active }">
|
||||
<div class="row">
|
||||
<div class="col-9 col-md-8 small-padding" :class="{ 'monitor-item': $root.userHeartbeatBar == 'bottom' || $root.userHeartbeatBar == 'none' }">
|
||||
<div class="info" :style="depthMargin">
|
||||
<Uptime :monitor="monitor" type="24" :pill="true" />
|
||||
<span v-if="hasChildren" class="collapse-padding" @click.prevent="changeCollapsed">
|
||||
<font-awesome-icon icon="chevron-down" class="animated" :class="{ collapsed: isCollapsed}" />
|
||||
</span>
|
||||
{{ monitorName }}
|
||||
</div>
|
||||
<div class="tags">
|
||||
<Tag v-for="tag in monitor.tags" :key="tag" :item="tag" :size="'sm'" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="$root.userHeartbeatBar == 'normal'" :key="$root.userHeartbeatBar" class="col-3 col-md-4">
|
||||
<HeartbeatBar size="small" :monitor-id="monitor.id" />
|
||||
</div>
|
||||
<div :style="depthMargin">
|
||||
<!-- Checkbox -->
|
||||
<div v-if="isSelectMode" class="select-input-wrapper">
|
||||
<input
|
||||
class="form-check-input select-input"
|
||||
type="checkbox"
|
||||
:aria-label="$t('Check/Uncheck')"
|
||||
:checked="isSelected(monitor.id)"
|
||||
@click.stop="toggleSelection"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="$root.userHeartbeatBar == 'bottom'" class="row">
|
||||
<div class="col-12 bottom-style">
|
||||
<HeartbeatBar size="small" :monitor-id="monitor.id" />
|
||||
<router-link :to="monitorURL(monitor.id)" class="item" :class="{ 'disabled': ! monitor.active }">
|
||||
<div class="row">
|
||||
<div class="col-9 col-md-8 small-padding" :class="{ 'monitor-item': $root.userHeartbeatBar == 'bottom' || $root.userHeartbeatBar == 'none' }">
|
||||
<div class="info">
|
||||
<Uptime :monitor="monitor" type="24" :pill="true" />
|
||||
<span v-if="hasChildren" class="collapse-padding" @click.prevent="changeCollapsed">
|
||||
<font-awesome-icon icon="chevron-down" class="animated" :class="{ collapsed: isCollapsed}" />
|
||||
</span>
|
||||
{{ monitorName }}
|
||||
</div>
|
||||
<div v-if="monitor.tags.length > 0" class="tags">
|
||||
<Tag v-for="tag in monitor.tags" :key="tag" :item="tag" :size="'sm'" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="$root.userHeartbeatBar == 'normal'" :key="$root.userHeartbeatBar" class="col-3 col-md-4">
|
||||
<HeartbeatBar ref="heartbeatBar" size="small" :monitor-id="monitor.id" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</router-link>
|
||||
|
||||
<div v-if="$root.userHeartbeatBar == 'bottom'" class="row">
|
||||
<div class="col-12 bottom-style">
|
||||
<HeartbeatBar ref="heartbeatBar" size="small" :monitor-id="monitor.id" />
|
||||
</div>
|
||||
</div>
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<transition name="slide-fade-up">
|
||||
<div v-if="!isCollapsed" class="childs">
|
||||
<MonitorListItem v-for="(item, index) in sortedChildMonitorList" :key="index" :monitor="item" :isSearch="isSearch" :depth="depth + 1" />
|
||||
<MonitorListItem
|
||||
v-for="(item, index) in sortedChildMonitorList"
|
||||
:key="index" :monitor="item"
|
||||
:showPathName="showPathName"
|
||||
:isSelectMode="isSelectMode"
|
||||
:isSelected="isSelected"
|
||||
:select="select"
|
||||
:deselect="deselect"
|
||||
:depth="depth + 1"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
@@ -53,8 +75,13 @@ export default {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
/** If the user is currently searching */
|
||||
isSearch: {
|
||||
/** Should the monitor name show it's parent */
|
||||
showPathName: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
/** If the user is in select mode */
|
||||
isSelectMode: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
@@ -63,6 +90,21 @@ export default {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
/** Callback to determine if monitor is selected */
|
||||
isSelected: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
},
|
||||
/** Callback fired when monitor is selected */
|
||||
select: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
},
|
||||
/** Callback fired when monitor is deselected */
|
||||
deselect: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -111,13 +153,19 @@ export default {
|
||||
};
|
||||
},
|
||||
monitorName() {
|
||||
if (this.isSearch) {
|
||||
if (this.showPathName) {
|
||||
return this.monitor.pathName;
|
||||
} else {
|
||||
return this.monitor.name;
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isSelectMode() {
|
||||
// TODO: Resize the heartbeat bar, but too slow
|
||||
// this.$refs.heartbeatBar.resize();
|
||||
}
|
||||
},
|
||||
beforeMount() {
|
||||
|
||||
// Always unfold if monitor is accessed directly
|
||||
@@ -164,6 +212,16 @@ export default {
|
||||
monitorURL(id) {
|
||||
return getMonitorRelativeURL(id);
|
||||
},
|
||||
/**
|
||||
* Toggle selection of monitor
|
||||
*/
|
||||
toggleSelection() {
|
||||
if (this.isSelected(this.monitor.id)) {
|
||||
this.deselect(this.monitor.id);
|
||||
} else {
|
||||
this.select(this.monitor.id);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -201,4 +259,14 @@ export default {
|
||||
transition: all 0.2s $easing-in;
|
||||
}
|
||||
|
||||
.select-input-wrapper {
|
||||
float: left;
|
||||
margin-top: 15px;
|
||||
margin-left: 3px;
|
||||
margin-right: 10px;
|
||||
padding-left: 4px;
|
||||
position: relative;
|
||||
z-index: 15;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
@@ -126,6 +126,7 @@ export default {
|
||||
"lunasea": "LunaSea",
|
||||
"matrix": "Matrix",
|
||||
"mattermost": "Mattermost",
|
||||
"nostr": "Nostr",
|
||||
"ntfy": "Ntfy",
|
||||
"octopush": "Octopush",
|
||||
"OneBot": "OneBot",
|
||||
@@ -157,6 +158,7 @@ export default {
|
||||
"AliyunSMS": "AliyunSMS (阿里云短信服务)",
|
||||
"DingDing": "DingDing (钉钉自定义机器人)",
|
||||
"Feishu": "Feishu (飞书)",
|
||||
"FlashDuty": "FlashDuty (快猫星云)",
|
||||
"FreeMobile": "FreeMobile (mobile.free.fr)",
|
||||
"PushDeer": "PushDeer",
|
||||
"promosms": "PromoSMS",
|
||||
|
@@ -61,12 +61,17 @@
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="showTags" class="tags">
|
||||
<Tag v-for="tag in monitor.element.tags" :key="tag" :item="tag" :size="'sm'" />
|
||||
<div class="extra-info">
|
||||
<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">
|
||||
<Tag v-for="tag in monitor.element.tags" :key="tag" :item="tag" :size="'sm'" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div :key="$root.userHeartbeatBar" class="col-3 col-md-4">
|
||||
<HeartbeatBar size="small" :monitor-id="monitor.element.id" />
|
||||
<HeartbeatBar size="mid" :monitor-id="monitor.element.id" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -103,6 +108,10 @@ export default {
|
||||
/** Should tags be shown? */
|
||||
showTags: {
|
||||
type: Boolean,
|
||||
},
|
||||
/** Should expiry be shown? */
|
||||
showCertificateExpiry: {
|
||||
type: Boolean,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -154,6 +163,33 @@ export default {
|
||||
}
|
||||
return monitor.element.sendUrl && monitor.element.url && monitor.element.url !== "https://" && !this.editMode;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns formatted certificate expiry or Bad cert message
|
||||
* @param {Object} monitor Monitor to show expiry for
|
||||
* @returns {string}
|
||||
*/
|
||||
formattedCertExpiryMessage(monitor) {
|
||||
if (monitor?.element?.validCert && monitor?.element?.certExpiryDaysRemaining) {
|
||||
return monitor.element.certExpiryDaysRemaining + " " + this.$tc("day", monitor.element.certExpiryDaysRemaining);
|
||||
} else if (monitor?.element?.validCert === false) {
|
||||
return this.$t("noOrBadCertificate");
|
||||
} else {
|
||||
return this.$t("Unknown") + " " + this.$tc("day", 2);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns certificate expiry based on days remaining
|
||||
* @param {Object} monitor Monitor to show expiry for
|
||||
* @returns {string}
|
||||
*/
|
||||
certExpiryColor(monitor) {
|
||||
if (monitor?.element?.validCert && monitor.element.certExpiryDaysRemaining > 7) {
|
||||
return "#059669";
|
||||
}
|
||||
return "#DC2626";
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -161,6 +197,15 @@ export default {
|
||||
<style lang="scss" scoped>
|
||||
@import "../assets/vars";
|
||||
|
||||
.extra-info {
|
||||
display: flex;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.extra-info > div > div:first-child {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
.no-monitor-msg {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
|
@@ -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>
|
||||
|
29
src/components/notifications/FlashDuty.vue
Normal file
29
src/components/notifications/FlashDuty.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="flashduty-integration-url" class="form-label">Integration Key</label>
|
||||
<HiddenInput id="flashduty-integration-url" v-model="$parent.notification.flashdutyIntegrationKey" autocomplete="false"></HiddenInput>
|
||||
<i18n-t tag="div" keypath="wayToGetFlashDutyKey" class="form-text">
|
||||
<a href="https://flashcat.cloud/product/flashduty?from=kuma" target="_blank">{{ $t("here") }}</a>
|
||||
</i18n-t>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="flashduty-severity" class="form-label">{{ $t("FlashDuty Severity") }}</label>
|
||||
<select id="flashduty-severity" v-model="$parent.notification.flashdutySeverity" class="form-select" :required="true">
|
||||
<option value="Info" selected>Info</option>
|
||||
<option value="Warning" selected>Warning</option>
|
||||
<option value="Critical">Critical</option>
|
||||
</select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HiddenInput from "../HiddenInput.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HiddenInput,
|
||||
},
|
||||
mounted() {
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -1,9 +1,7 @@
|
||||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="goalert-base-url" class="form-label">{{ $t("Base URL") }}</label>
|
||||
<div class="input-group mb-3">
|
||||
<input id="goalert-base-url" v-model="$parent.notification.goAlertBaseURL" type="text" class="form-control" required>
|
||||
</div>
|
||||
<input id="goalert-base-url" v-model="$parent.notification.goAlertBaseURL" type="text" class="form-control" required>
|
||||
<i18n-t tag="div" keypath="goAlertInfo" class="form-text">
|
||||
<a href="https://goalert.me" target="_blank">https://goalert.me</a>
|
||||
</i18n-t>
|
||||
|
@@ -1,23 +1,19 @@
|
||||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="gorush-device-token" class="form-label">{{ $t("Device Token") }}</label><span style="color: red;"><sup>*</sup></span>
|
||||
<div class="input-group mb-3">
|
||||
<input id="gorush-device-token" v-model="$parent.notification.gorushDeviceToken" type="text" class="form-control" required>
|
||||
</div>
|
||||
<input id="gorush-device-token" v-model="$parent.notification.gorushDeviceToken" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="gorush-server-url" class="form-label">{{ $t("Server URL") }}</label><span style="color: red;"><sup>*</sup></span>
|
||||
<div class="input-group mb-3">
|
||||
<input id="gorush-server-url" v-model="$parent.notification.gorushServerURL" type="text" class="form-control" required>
|
||||
</div>
|
||||
<input id="gorush-server-url" v-model="$parent.notification.gorushServerURL" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="gorush-platform" class="form-label">{{ $t("Platform") }}</label><span style="color: red;"><sup>*</sup></span>
|
||||
<select id="gorush-platform" v-model="$parent.notification.gorushPlatform" class="form-select">
|
||||
<option value="ios">iOS</option>
|
||||
<option value="android">{{ $t("Android") }}</option>
|
||||
<option value="android">Android</option>
|
||||
<option value="huawei">{{ $t("Huawei") }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
@@ -5,9 +5,7 @@
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="gotify-server-url" class="form-label">{{ $t("Server URL") }}</label>
|
||||
<div class="input-group mb-3">
|
||||
<input id="gotify-server-url" v-model="$parent.notification.gotifyserverurl" type="text" class="form-control" required>
|
||||
</div>
|
||||
<input id="gotify-server-url" v-model="$parent.notification.gotifyserverurl" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
|
@@ -9,10 +9,7 @@
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="kook-guild-id" class="form-label">{{ $t("Guild ID") }}</label>
|
||||
|
||||
<div class="input-group mb-3">
|
||||
<input id="kook-guild-id" v-model="$parent.notification.kookGuildID" type="text" class="form-control" required>
|
||||
</div>
|
||||
<input id="kook-guild-id" v-model="$parent.notification.kookGuildID" type="text" class="form-control" required>
|
||||
|
||||
<div class="form-text">
|
||||
<p style="margin-top: 8px;">
|
||||
|
@@ -13,7 +13,7 @@
|
||||
<div class="form-text">
|
||||
<span style="color: red;"><sup>*</sup></span>{{ $t("Required") }}
|
||||
<i18n-t tag="p" keypath="aboutWebhooks" style="margin-top: 8px;">
|
||||
<a href="https://docs.mattermost.com/developer/webhooks-incoming.html" target="_blank">https://docs.mattermost.com/developer/webhooks-incoming.html</a>
|
||||
<a href="https://developers.mattermost.com/integrate/webhooks/incoming/" target="_blank">https://developers.mattermost.com/integrate/webhooks/incoming/</a>
|
||||
</i18n-t>
|
||||
<p style="margin-top: 8px;">
|
||||
{{ $t("aboutMattermostChannelName") }}
|
||||
|
26
src/components/notifications/Nostr.vue
Normal file
26
src/components/notifications/Nostr.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="nostr-relays" class="form-label">{{ $t("nostrRelays") }}<span style="color: red;"><sup>*</sup></span></label>
|
||||
<textarea id="nostr-relays" v-model="$parent.notification.relays" class="form-control" :required="true" placeholder="wss://127.0.0.1:7777/"></textarea>
|
||||
<small class="form-text text-muted">{{ $t("nostrRelaysHelp") }}</small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="nostr-sender" class="form-label">{{ $t("nostrSender") }}<span style="color: red;"><sup>*</sup></span></label>
|
||||
<HiddenInput id="nostr-sender" v-model="$parent.notification.sender" autocomplete="new-password" :required="true"></HiddenInput>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="nostr-recipients" class="form-label">{{ $t("nostrRecipients") }}<span style="color: red;"><sup>*</sup></span></label>
|
||||
<textarea id="nostr-recipients" v-model="$parent.notification.recipients" class="form-control" :required="true" placeholder="npub123... npub789..."></textarea>
|
||||
<small class="form-text text-muted">{{ $t("nostrRecipientsHelp") }}</small>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HiddenInput from "../HiddenInput.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HiddenInput,
|
||||
},
|
||||
};
|
||||
</script>
|
@@ -1,14 +1,13 @@
|
||||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="ntfy-ntfytopic" class="form-label">{{ $t("ntfy Topic") }}</label>
|
||||
<div class="input-group mb-3">
|
||||
<input id="ntfy-ntfytopic" v-model="$parent.notification.ntfytopic" type="text" class="form-control" required>
|
||||
</div>
|
||||
<input id="ntfy-ntfytopic" v-model="$parent.notification.ntfytopic" type="text" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="ntfy-server-url" class="form-label">{{ $t("Server URL") }}</label>
|
||||
<div class="input-group mb-3">
|
||||
<input id="ntfy-server-url" v-model="$parent.notification.ntfyserverurl" type="text" class="form-control" required>
|
||||
<input id="ntfy-server-url" v-model="$parent.notification.ntfyserverurl" type="text" class="form-control" required>
|
||||
<div class="form-text">
|
||||
{{ $t("Server URL should not contain the nfty topic") }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
@@ -23,21 +22,15 @@
|
||||
</div>
|
||||
<div v-if="$parent.notification.ntfyAuthenticationMethod === 'usernamePassword'" class="mb-3">
|
||||
<label for="ntfy-username" class="form-label">{{ $t("Username") }}</label>
|
||||
<div class="input-group mb-3">
|
||||
<input id="ntfy-username" v-model="$parent.notification.ntfyusername" type="text" class="form-control">
|
||||
</div>
|
||||
<input id="ntfy-username" v-model="$parent.notification.ntfyusername" type="text" class="form-control">
|
||||
</div>
|
||||
<div v-if="$parent.notification.ntfyAuthenticationMethod === 'usernamePassword'" class="mb-3">
|
||||
<label for="ntfy-password" class="form-label">{{ $t("Password") }}</label>
|
||||
<div class="input-group mb-3">
|
||||
<HiddenInput id="ntfy-password" v-model="$parent.notification.ntfypassword" autocomplete="new-password"></HiddenInput>
|
||||
</div>
|
||||
<HiddenInput id="ntfy-password" v-model="$parent.notification.ntfypassword" autocomplete="new-password"></HiddenInput>
|
||||
</div>
|
||||
<div v-if="$parent.notification.ntfyAuthenticationMethod === 'accessToken'" class="mb-3">
|
||||
<label for="ntfy-access-token" class="form-label">{{ $t("Access Token") }}</label>
|
||||
<div class="input-group mb-3">
|
||||
<HiddenInput id="ntfy-access-token" v-model="$parent.notification.ntfyaccesstoken"></HiddenInput>
|
||||
</div>
|
||||
<HiddenInput id="ntfy-access-token" v-model="$parent.notification.ntfyaccesstoken"></HiddenInput>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="ntfy-icon" class="form-label">{{ $t("IconUrl") }}</label>
|
||||
|
@@ -1,4 +1,9 @@
|
||||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="pushdeer-server" class="form-label">{{ $t("PushDeer Server URL") }}</label>
|
||||
<input id="pushdeer-server" v-model="$parent.notification.pushdeerServer" type="text" class="form-control" placeholder="https://api2.pushdeer.com">
|
||||
<div class="form-text">{{ $t("pushDeerServerDescription") }}</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="pushdeer-key" class="form-label">{{ $t("PushDeer Key") }}</label>
|
||||
<HiddenInput id="pushdeer-key" v-model="$parent.notification.pushdeerKey" :required="true" autocomplete="new-password" placeholder="PDUxxxx"></HiddenInput>
|
||||
|
@@ -6,9 +6,7 @@
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="pushy-user-key" class="form-label">{{ $t("pushyToken") }}</label>
|
||||
<div class="input-group mb-3">
|
||||
<HiddenInput id="pushy-user-key" v-model="$parent.notification.pushyToken" :required="true" autocomplete="new-password"></HiddenInput>
|
||||
</div>
|
||||
<HiddenInput id="pushy-user-key" v-model="$parent.notification.pushyToken" :required="true" autocomplete="new-password"></HiddenInput>
|
||||
</div>
|
||||
<i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;">
|
||||
<a href="https://pushy.me/docs/api/send-notifications" target="_blank">https://pushy.me/docs/api/send-notifications</a>
|
||||
|
@@ -19,11 +19,13 @@ import LineNotify from "./LineNotify.vue";
|
||||
import LunaSea from "./LunaSea.vue";
|
||||
import Matrix from "./Matrix.vue";
|
||||
import Mattermost from "./Mattermost.vue";
|
||||
import Nostr from "./Nostr.vue";
|
||||
import Ntfy from "./Ntfy.vue";
|
||||
import Octopush from "./Octopush.vue";
|
||||
import OneBot from "./OneBot.vue";
|
||||
import Opsgenie from "./Opsgenie.vue";
|
||||
import PagerDuty from "./PagerDuty.vue";
|
||||
import FlashDuty from "./FlashDuty.vue";
|
||||
import PagerTree from "./PagerTree.vue";
|
||||
import PromoSMS from "./PromoSMS.vue";
|
||||
import Pushbullet from "./Pushbullet.vue";
|
||||
@@ -77,11 +79,13 @@ const NotificationFormList = {
|
||||
"lunasea": LunaSea,
|
||||
"matrix": Matrix,
|
||||
"mattermost": Mattermost,
|
||||
"nostr": Nostr,
|
||||
"ntfy": Ntfy,
|
||||
"octopush": Octopush,
|
||||
"OneBot": OneBot,
|
||||
"Opsgenie": Opsgenie,
|
||||
"PagerDuty": PagerDuty,
|
||||
"FlashDuty": FlashDuty,
|
||||
"PagerTree": PagerTree,
|
||||
"promosms": PromoSMS,
|
||||
"pushbullet": Pushbullet,
|
||||
|
@@ -112,6 +112,53 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Timeline -->
|
||||
<div class="my-4">
|
||||
<label class="form-label">{{ $t("styleElapsedTime") }}</label>
|
||||
<div>
|
||||
<div class="btn-group" role="group">
|
||||
<input
|
||||
id="styleElapsedTimeShowNoLine"
|
||||
v-model="$root.styleElapsedTime"
|
||||
type="radio"
|
||||
class="btn-check"
|
||||
name="styleElapsedTime"
|
||||
autocomplete="off"
|
||||
value="no-line"
|
||||
/>
|
||||
<label class="btn btn-outline-primary" for="styleElapsedTimeShowNoLine">
|
||||
{{ $t("styleElapsedTimeShowNoLine") }}
|
||||
</label>
|
||||
|
||||
<input
|
||||
id="styleElapsedTimeShowWithLine"
|
||||
v-model="$root.styleElapsedTime"
|
||||
type="radio"
|
||||
class="btn-check"
|
||||
name="styleElapsedTime"
|
||||
autocomplete="off"
|
||||
value="with-line"
|
||||
/>
|
||||
<label class="btn btn-outline-primary" for="styleElapsedTimeShowWithLine">
|
||||
{{ $t("styleElapsedTimeShowWithLine") }}
|
||||
</label>
|
||||
|
||||
<input
|
||||
id="styleElapsedTimeNone"
|
||||
v-model="$root.styleElapsedTime"
|
||||
type="radio"
|
||||
class="btn-check"
|
||||
name="styleElapsedTime"
|
||||
autocomplete="off"
|
||||
value="none"
|
||||
/>
|
||||
<label class="btn btn-outline-primary" for="styleElapsedTimeNone">
|
||||
{{ $t("None") }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@@ -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">
|
||||
|
@@ -455,8 +455,6 @@
|
||||
"For safety, must use secret key": "للسلامة يجب استخدام المفتاح السري",
|
||||
"Device Token": "رمز الجهاز",
|
||||
"Platform": "منصة",
|
||||
"iOS": "iOS",
|
||||
"Android": "ذكري المظهر",
|
||||
"Huawei": "هواوي",
|
||||
"High": "عالٍ",
|
||||
"Retry": "إعادة المحاولة",
|
||||
|
@@ -592,7 +592,6 @@
|
||||
"For safety, must use secret key": "للسلامة يجب استخدام المفتاح السري",
|
||||
"Device Token": "رمز الجهاز",
|
||||
"Platform": "منصة",
|
||||
"Android": "ذكري المظهر",
|
||||
"Huawei": "هواوي",
|
||||
"High": "عالٍ",
|
||||
"Retry": "إعادة المحاولة",
|
||||
@@ -684,5 +683,6 @@
|
||||
"languageName": "العربية",
|
||||
"Game": "الألعاب",
|
||||
"List": "القائمة",
|
||||
"statusMaintenance": "الصيانة"
|
||||
"statusMaintenance": "الصيانة",
|
||||
"Home": "الرئيسة"
|
||||
}
|
||||
|
@@ -333,7 +333,7 @@
|
||||
"Post": "Публикувай",
|
||||
"Please input title and content": "Моля, въведете заглавие и съдържание",
|
||||
"Created": "Създаден",
|
||||
"Last Updated": "Последно обновен",
|
||||
"Last Updated": "Последно обновена",
|
||||
"Unpin": "Откачи",
|
||||
"Switch to Light Theme": "Превключи към светла тема",
|
||||
"Switch to Dark Theme": "Превключи към тъмна тема",
|
||||
@@ -396,8 +396,6 @@
|
||||
"For safety, must use secret key": "За сигурност, трябва да се използва таен ключ",
|
||||
"Device Token": "Токен за устройство",
|
||||
"Platform": "Платформа",
|
||||
"iOS": "iOS",
|
||||
"Android": "Android",
|
||||
"Huawei": "Huawei",
|
||||
"High": "Висок",
|
||||
"Retry": "Повтори",
|
||||
@@ -645,8 +643,8 @@
|
||||
"smseaglePriority": "Приоритет на съобщението (0-9, по подразбиране = 0)",
|
||||
"IconUrl": "Икона URL адрес",
|
||||
"webhookAdditionalHeadersTitle": "Допълнителни хедъри",
|
||||
"webhookAdditionalHeadersDesc": "Задава допълнителни хедъри, изпратени с уеб куката.",
|
||||
"Enable DNS Cache": "Активирай DNS кеширане",
|
||||
"webhookAdditionalHeadersDesc": "Задава допълнителни хедъри, изпратени с уеб куката. Всеки хедър трябва да бъде дефиниран като JSON ключ/стойност.",
|
||||
"Enable DNS Cache": "Активирай DNS кеширане за HTTP(S) монитори",
|
||||
"Enable": "Активирай",
|
||||
"Disable": "Деактивирай",
|
||||
"dnsCacheDescription": "Възможно е да не работи в IPv6 среда - деактивирайте, ако срещнете проблеми.",
|
||||
@@ -740,7 +738,7 @@
|
||||
"lunaseaDeviceID": "ID на устройството",
|
||||
"lunaseaUserID": "ID на потребител",
|
||||
"twilioAccountSID": "Профил SID",
|
||||
"twilioAuthToken": "Удостоверяващ токен",
|
||||
"twilioAuthToken": "Удостоверяващ токен / Тайна на API ключа",
|
||||
"twilioFromNumber": "От номер",
|
||||
"twilioToNumber": "Към номер",
|
||||
"sameAsServerTimezone": "Kато часовата зона на сървъра",
|
||||
@@ -749,7 +747,7 @@
|
||||
"cronSchedule": "График: ",
|
||||
"invalidCronExpression": "Невалиден \"Cron\" израз: {0}",
|
||||
"cronExpression": "Израз тип \"Cron\"",
|
||||
"statusPageRefreshIn": "Обновяване след: {0}",
|
||||
"statusPageRefreshIn": "Ще се обнови след: {0}",
|
||||
"ntfyUsernameAndPassword": "Потребителско име и парола",
|
||||
"ntfyAuthenticationMethod": "Метод за удостоверяване",
|
||||
"pushoverMessageTtl": "TTL на съобщението (секунди)",
|
||||
@@ -757,7 +755,7 @@
|
||||
"Badge Generator": "Генератор на баджове на {0}",
|
||||
"Badge Type": "Тип бадж",
|
||||
"Badge Duration": "Продължителност на баджа",
|
||||
"Badge Prefix": "Префикс на баджа",
|
||||
"Badge Prefix": "Префикс за стйността на баджа",
|
||||
"Badge Label Color": "Цвят на етикета на баджа",
|
||||
"Badge Color": "Цвят на баджа",
|
||||
"Badge Label Suffix": "Суфикс на етикета на значката",
|
||||
@@ -771,9 +769,9 @@
|
||||
"Badge URL": "URL адрес на баджа",
|
||||
"Monitor Setting": "Настройка на монитор {0}",
|
||||
"Show Clickable Link": "Покажи връзка, която може да се кликне",
|
||||
"Show Clickable Link Description": "Ако е отбелязано, всеки който има достъп до тази статус страница, ще може да достъпва URL адреса на монитора.",
|
||||
"Show Clickable Link Description": "Ако е отбелязано, всеки който има достъп до тази статус страница, ще може да достъпва мониторирания URL адрес.",
|
||||
"Badge Label": "Етикет на баджа",
|
||||
"Badge Suffix": "Суфикс на баджа",
|
||||
"Badge Suffix": "Суфикс за стойността на баджа",
|
||||
"Badge Label Prefix": "Префикс на етикета на значката",
|
||||
"Badge Pending Color": "Цвят на баджа за изчакващ",
|
||||
"Badge Down Days": "Колко дни баджът да не се показва",
|
||||
@@ -784,5 +782,61 @@
|
||||
"Edit Maintenance": "Редактиране на поддръжка",
|
||||
"Home": "Главна страница",
|
||||
"noGroupMonitorMsg": "Не е налично. Първо създайте групов монитор.",
|
||||
"Close": "Затвори"
|
||||
"Close": "Затвори",
|
||||
"nostrRelays": "Nostr релета",
|
||||
"nostrRelaysHelp": "Един URL адрес за реле на ред",
|
||||
"nostrSender": "Частен ключ на изпращача (nsec)",
|
||||
"nostrRecipients": "Публични ключове на получатели (npub)",
|
||||
"nostrRecipientsHelp": "npub формат, по един на ред",
|
||||
"chromeExecutable": "Chrome/Chromium изпълним файл",
|
||||
"chromeExecutableAutoDetect": "Автоматично откриване",
|
||||
"chromeExecutableDescription": "За потребителите на Docker, ако Chromium все още не е инсталиран, инсталирането и показването на резултата от теста може да отнеме няколко минути. Заема 1GB дисково пространство.",
|
||||
"Invert Keyword": "Обърнат режим за ключова дума",
|
||||
"invertKeywordDescription": "При търсене ключовата дума трябва да отсъства, а не да присъства.",
|
||||
"webhookBodyPresetOption": "Предварителна настройка - {0}",
|
||||
"webhookBodyCustomOption": "Персонализирано тяло",
|
||||
"webhookCustomBodyDesc": "Дефинирайте персонализирано HTTP тяло за заявката. Приемат се шаблонни променливи {msg}, {heartbeat}, {monitor}.",
|
||||
"Request Body": "Тяло на заявката",
|
||||
"twilioApiKey": "API ключ (по избор)",
|
||||
"Expected Value": "Очаквана стойност",
|
||||
"Json Query": "Заявка тип JSON",
|
||||
"jsonQueryDescription": "Прави JSON заявка срещу отговора и проверява за очаквана стойност (Върнатата стойност ще бъде преобразувана в низ за сравнение). Разгледайте <a href='https://jsonata.org/'>jsonata.org</a> за документация относно езика на заявката. Имате възможност да тествате <a href='https://try.jsonata.org/'>тук</a>.",
|
||||
"Badge Duration (in hours)": "Времетраене на баджа (в часове)",
|
||||
"Badge Preview": "Преглед на баджа",
|
||||
"Notify Channel": "Канал за известяване",
|
||||
"aboutNotifyChannel": "Каналът за известяване ще задейства известие на настолен компютър или мобилно устройство за всички членове на канала, независимо дали тяхната наличност е в състояние активен или отсъстващ.",
|
||||
"filterActive": "Активен",
|
||||
"filterActivePaused": "На пауза",
|
||||
"Kafka Brokers": "Kafka брокери",
|
||||
"Enter the list of brokers": "Въведете списъка с брокери",
|
||||
"Press Enter to add broker": "Натиснете Enter, за да добавите брокер",
|
||||
"Kafka Topic Name": "Име на темата за Kafka",
|
||||
"Enable Kafka SSL": "Активирай Kafka SSL",
|
||||
"Enable Kafka Producer Auto Topic Creation": "Активирай автоматично създаване на темa в Kafka Producer",
|
||||
"Kafka Producer Message": "Съобщение на Kafka Producer",
|
||||
"Kafka SASL Options": "Опции на Kafka SASL",
|
||||
"Mechanism": "Механизъм",
|
||||
"Pick a SASL Mechanism...": "Изберете SASL механизъм...",
|
||||
"Authorization Identity": "Идентичност за оторизиране",
|
||||
"AccessKey Id": "AccessKey ID",
|
||||
"Secret AccessKey": "Таен ключ за достъп",
|
||||
"Session Token": "Токен за сесия",
|
||||
"tailscalePingWarning": "За да използвате Tailscale Ping монитор, трябва да инсталирате Uptime Kuma без Docker и също така да инсталирате Tailscale клиент на вашия сървър.",
|
||||
"Server URL should not contain the nfty topic": "URL адресът на сървъра не трябва да съдържа nfty темата",
|
||||
"FlashDuty Severity": "Степен на тежест",
|
||||
"showCertificateExpiry": "Показвай изтичащ сертификат",
|
||||
"noOrBadCertificate": "Няма/лош сертификат",
|
||||
"Select": "Избери",
|
||||
"selectedMonitorCount": "Избрано: {0}",
|
||||
"wayToGetFlashDutyKey": "Можете да отидете на страница 'Channel -> (Select a Channel) -> Integrations -> Add a new integration' и да добавите 'Custom Event', за да получите 'push' адрес и да копирате ключа за интегриране в адреса. За повече информация, моля посетете",
|
||||
"PushDeer Server": "PushDeer сървър",
|
||||
"pushDeerServerDescription": "Оставете празно, за да използвате официалния сървър",
|
||||
"Check/Uncheck": "Постави/Премахни отметка",
|
||||
"Request Timeout": "Време за изтичане на заявката",
|
||||
"timeoutAfter": "Времето изтича след {0} секунди",
|
||||
"styleElapsedTime": "Изминало време под лентата с проверки",
|
||||
"styleElapsedTimeShowNoLine": "Покажи (без ред)",
|
||||
"gamedigGuessPort": "Gamedig: Познай порт",
|
||||
"gamedigGuessPortDescription": "Портът, използван от Valve Server Query Protocol, може да е различен от клиентския порт. Опитайте това, ако мониторът не може да се свърже с вашия сървър.",
|
||||
"styleElapsedTimeShowWithLine": "Покажи (с ред)"
|
||||
}
|
||||
|
1
src/lang/bn.json
Normal file
1
src/lang/bn.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
@@ -18,13 +18,13 @@
|
||||
"Pick Affected Monitors...": "Vyberte dotčené dohledy…",
|
||||
"Start of maintenance": "Zahájit údržbu",
|
||||
"All Status Pages": "Všechny stavové stránky",
|
||||
"Select status pages...": "Vyberte stavovou stránku…",
|
||||
"Select status pages...": "Vyberte stavové stránky…",
|
||||
"recurringIntervalMessage": "Spustit jednou každý den | Spustit jednou každých {0} dní",
|
||||
"affectedMonitorsDescription": "Vyberte dohledy, které budou ovlivněny touto údržbou",
|
||||
"affectedStatusPages": "Zobrazit tuto zprávu o údržbě na vybraných stavových stránkách",
|
||||
"atLeastOneMonitor": "Vyberte alespoň jeden dotčený dohled",
|
||||
"passwordNotMatchMsg": "Hesla se neshodují.",
|
||||
"notificationDescription": "Aby byla upozornění fungovalo, je nutné ho přiřadit k dohledu.",
|
||||
"notificationDescription": "Aby oznámení fungovala, je nutné jej přiřadit k dohledu.",
|
||||
"keywordDescription": "Vyhledat klíčové slovo v prosté odpovědi HTML nebo JSON. Při hledání se rozlišuje velikost písmen.",
|
||||
"pauseDashboardHome": "Pauza",
|
||||
"deleteMonitorMsg": "Opravdu chcete odstranit tento dohled?",
|
||||
@@ -208,7 +208,7 @@
|
||||
"Status Page": "Stavová stránka",
|
||||
"Status Pages": "Stavová stránka",
|
||||
"defaultNotificationName": "Moje {notification} upozornění ({číslo})",
|
||||
"here": "sem",
|
||||
"here": "klikněte sem",
|
||||
"Required": "Vyžadováno",
|
||||
"telegram": "Telegram",
|
||||
"ZohoCliq": "ZohoCliq",
|
||||
@@ -225,7 +225,7 @@
|
||||
"webhookJsonDesc": "{0} je vhodný pro všechny moderní servery HTTP, jako je Express.js",
|
||||
"webhookFormDataDesc": "{multipart} je vhodné pro PHP. JSON bude nutné analyzovat prostřednictvím {decodeFunction}",
|
||||
"webhookAdditionalHeadersTitle": "Dodatečné hlavičky",
|
||||
"webhookAdditionalHeadersDesc": "Nastavte dodatečné hlavičky, které se odešlou společně s webhookem.",
|
||||
"webhookAdditionalHeadersDesc": "Nastavte dodatečné hlavičky, které se odešlou společně s webhookem. Každá hlavička by měla být definována jako klíč/hodnota v JSON.",
|
||||
"smtp": "E-mail (SMTP)",
|
||||
"secureOptionNone": "Žádné / STARTTLS (25, 587)",
|
||||
"secureOptionTLS": "TLS (465)",
|
||||
@@ -243,8 +243,8 @@
|
||||
"Hello @everyone is...": "Dobrý den, {'@'}všichni jsou…",
|
||||
"teams": "Microsoft Teams",
|
||||
"Webhook URL": "URL adresa webhooku",
|
||||
"wayToGetTeamsURL": "Informace o tom, jak vytvořit URL adresu webhooku naleznete na {0}.",
|
||||
"wayToGetZohoCliqURL": "Informace o tom, jak vytvořit URL adresu webhooku naleznete na {0}.",
|
||||
"wayToGetTeamsURL": "Pro informace o tom, jak vytvořit URL adresu webhooku {0}.",
|
||||
"wayToGetZohoCliqURL": "Pro informace o tom, jak vytvořit URL adresu webhooku {0}.",
|
||||
"signal": "Signal",
|
||||
"Number": "Číslo",
|
||||
"Recipients": "Příjemci",
|
||||
@@ -339,7 +339,7 @@
|
||||
"PasswordsDoNotMatch": "Hesla se neshodují.",
|
||||
"records": "záznamů",
|
||||
"One record": "Jeden záznam",
|
||||
"steamApiKeyDescription": "Pro monitorování Steam Game Serveru je nutné zadat Steam Web-API klíč. Svůj API klíč získáte na následující stránce: ",
|
||||
"steamApiKeyDescription": "Pro monitorování herního serveru ve službě Steam je nutné zadat Steam Web-API klíč. Svůj API klíč získáte na následující stránce: ",
|
||||
"Current User": "Aktuálně přihlášený uživatel",
|
||||
"topic": "Téma",
|
||||
"topicExplanation": "MQTT téma, které chcete sledovat",
|
||||
@@ -454,8 +454,6 @@
|
||||
"For safety, must use secret key": "Z důvodu bezpečnosti použijte secret key",
|
||||
"Device Token": "Token zařízení",
|
||||
"Platform": "Platforma",
|
||||
"iOS": "iOS",
|
||||
"Android": "Android",
|
||||
"Huawei": "Huawei",
|
||||
"High": "Vysoký",
|
||||
"Retry": "Opakovat",
|
||||
@@ -527,8 +525,8 @@
|
||||
"RadiusCalledStationIdDescription": "Identifikátor volaného zařízení",
|
||||
"RadiusCallingStationId": "ID volajícího zařízení",
|
||||
"RadiusCallingStationIdDescription": "Identifikátor volajícího zařízení",
|
||||
"Certificate Expiry Notification": "Oznámení na blížící se konec platnosti certifikátu",
|
||||
"API Username": "Uživatelské jména API",
|
||||
"Certificate Expiry Notification": "Upozornění na blížící se konec platnosti certifikátu",
|
||||
"API Username": "Uživatelské jméno API",
|
||||
"API Key": "API klíč",
|
||||
"Recipient Number": "Číslo příjemce",
|
||||
"From Name/Number": "Jméno/číslo odesílatele",
|
||||
@@ -569,7 +567,7 @@
|
||||
"Also check beta release": "Kontrolovat také dostupnost beta verzí",
|
||||
"Using a Reverse Proxy?": "Používáte reverzní proxy?",
|
||||
"Check how to config it for WebSocket": "Zjistěte, jak ji nakonfigurovat pro WebSockety",
|
||||
"Steam Game Server": "Steam Game Server",
|
||||
"Steam Game Server": "Herní server ve službě Steam",
|
||||
"Most likely causes:": "Nejčastější důvody:",
|
||||
"The resource is no longer available.": "Zdroj již není k dispozici.",
|
||||
"There might be a typing error in the address.": "Při zadávání adresy jste udělali chybu.",
|
||||
@@ -640,7 +638,7 @@
|
||||
"dayOfWeek": "Den v týdnu",
|
||||
"dayOfMonth": "Den v měsíci",
|
||||
"lastDay": "Poslední den",
|
||||
"lastDay1": "1. poslední den v měsíci",
|
||||
"lastDay1": "Poslední den v měsíci",
|
||||
"lastDay2": "2. poslední den v měsíci",
|
||||
"lastDay3": "3. poslední den v měsíci",
|
||||
"lastDay4": "4. poslední den v měsíci",
|
||||
@@ -655,10 +653,10 @@
|
||||
"Server Timezone": "Časové pásmo serveru",
|
||||
"statusPageMaintenanceEndDate": "Konec",
|
||||
"IconUrl": "Adresa URL ikony",
|
||||
"Enable DNS Cache": "Povolit DNS Cache",
|
||||
"Enable DNS Cache": "Povolit DNS Cache pro HTTP(s) dohledy",
|
||||
"Enable": "Povolit",
|
||||
"Disable": "Zakázat",
|
||||
"dnsCacheDescription": "V některých IPv6 prostředích nemusí fungovat. Pokud narazíte na nějaké problémy, vypněte jej.",
|
||||
"dnsCacheDescription": "V některých IPv6 prostředích nemusí fungovat. Pokud narazíte na nějaké problémy, tuto možnost vypněte.",
|
||||
"Single Maintenance Window": "Konkrétní časové okno pro údržbu",
|
||||
"Maintenance Time Window of a Day": "Časové okno pro údržbu v daný den",
|
||||
"Effective Date Range": "Časové období (volitelné)",
|
||||
@@ -692,7 +690,7 @@
|
||||
"uninstall": "Odinstalace",
|
||||
"uninstalling": "Odinstalování",
|
||||
"Packet Size": "Velikost paketu",
|
||||
"markdownSupported": "Markdown syntaxe podporována",
|
||||
"markdownSupported": "Markdown syntaxe je podporována",
|
||||
"Google Analytics ID": "ID Google Analytics",
|
||||
"Edit Tag": "Upravit štítek",
|
||||
"Server Address": "Adresa serveru",
|
||||
@@ -743,46 +741,102 @@
|
||||
"twilioAccountSID": "SID účtu",
|
||||
"twilioFromNumber": "Číslo odesílatele",
|
||||
"twilioToNumber": "Číslo příjemce",
|
||||
"twilioAuthToken": "Autorizační token",
|
||||
"twilioAuthToken": "Autorizační token / Tajemství API klíče",
|
||||
"sameAsServerTimezone": "Stejné jako časové pásmo serveru",
|
||||
"cronExpression": "Cron výraz",
|
||||
"cronSchedule": "Plán: ",
|
||||
"invalidCronExpression": "Neplatný cron výraz: {0}",
|
||||
"startDateTime": "Počáteční datum/čas",
|
||||
"startDateTime": "Datum/čas začátku",
|
||||
"endDateTime": "Datum/čas konce",
|
||||
"ntfyAuthenticationMethod": "Způsob ověření",
|
||||
"ntfyUsernameAndPassword": "Uživatelské jméno a heslo",
|
||||
"pushoverMessageTtl": "Zpráva TTL (Sekund)",
|
||||
"Show Clickable Link": "Zobrazit klikatelný odkaz",
|
||||
"Show Clickable Link Description": "Pokud je zaškrtnuto, všichni, kdo mají přístup k této stavové stránce, mají přístup k adrese URL monitoru.",
|
||||
"Show Clickable Link Description": "Pokud je zaškrtnuto, všichni, kdo mají přístup k této stavové stránce, mají přístup k adrese URL dohledu.",
|
||||
"Open Badge Generator": "Otevřít generátor odznaků",
|
||||
"Badge Type": "Typ odznaku",
|
||||
"Badge Duration": "Platnost odznaku",
|
||||
"Badge Label": "Štítek odznaku",
|
||||
"Badge Prefix": "Prefix odznaku",
|
||||
"Monitor Setting": "{0}'s Nastavení dohledu",
|
||||
"Badge Prefix": "Prefix hodnoty odznaku",
|
||||
"Monitor Setting": "Nastavení dohledu pro {0}",
|
||||
"Badge Generator": "Generátor odznaků pro {0}",
|
||||
"Badge Label Color": "Barva štítku odznaku",
|
||||
"Badge Color": "Barva odznaku",
|
||||
"Badge Style": "Styl odznaku",
|
||||
"Badge Label Suffix": "Přípona štítku odznaku",
|
||||
"Badge URL": "URL odznaku",
|
||||
"Badge Suffix": "Přípona odznaku",
|
||||
"Badge Suffix": "Přípona hodnoty odznaku",
|
||||
"Badge Label Prefix": "Prefix štítku odznaku",
|
||||
"Badge Up Color": "Barva odznaku při Běží",
|
||||
"Badge Down Color": "Barva odznaku při Nedostupné",
|
||||
"Badge Pending Color": "Barva odznaku při Pauze",
|
||||
"Badge Maintenance Color": "Barva odznaku při Údržbě",
|
||||
"Badge Warn Color": "Barva odznaku při Upozornění",
|
||||
"Reconnecting...": "Obnovení spojení...",
|
||||
"Cannot connect to the socket server": "Nelze se připojit k soketovému serveru",
|
||||
"Reconnecting...": "Obnovování spojení…",
|
||||
"Cannot connect to the socket server": "Nelze se připojit k socketu serveru",
|
||||
"Edit Maintenance": "Upravit Údržbu",
|
||||
"Home": "Hlavní stránka",
|
||||
"Badge Down Days": "Odznak nedostupných dní",
|
||||
"Group": "Skupina",
|
||||
"Monitor Group": "Sledovaná skupina",
|
||||
"noGroupMonitorMsg": "Není k dispozici. Nejprve vytvořte skupin dohledů.",
|
||||
"noGroupMonitorMsg": "Není k dispozici. Nejprve vytvořte skupinu dohledů.",
|
||||
"Close": "Zavřít",
|
||||
"Badge value (For Testing only.)": "Hodnota odznaku (pouze pro testování)",
|
||||
"Badge Warn Days": "Odznak dní s upozorněním"
|
||||
"Badge Warn Days": "Odznak dní s upozorněním",
|
||||
"nostrSender": "Privátní klíč odesílatele (nsec)",
|
||||
"nostrRelaysHelp": "Jedno relay URL na řádku",
|
||||
"nostrRecipients": "Privátní klíče příjemců (npub)",
|
||||
"nostrRecipientsHelp": "formát npub, jeden na řádku",
|
||||
"chromeExecutable": "Spustitelný soubor Chrome/Chromium",
|
||||
"chromeExecutableAutoDetect": "Automatická detekce",
|
||||
"chromeExecutableDescription": "Pokud uživatelé nástroje Docker ještě nemají nainstalovanou aplikaci Chromium, může instalace a zobrazení výsledku testu trvat několik minut. Zabere 1 GB místa na disku.",
|
||||
"Invert Keyword": "Inverzní klíčové slovo",
|
||||
"webhookBodyPresetOption": "Uložená hodnota - {0}",
|
||||
"webhookBodyCustomOption": "Vlastní tělo",
|
||||
"invertKeywordDescription": "Hledá se klíčové slovo, které je spíše nepřítomné než přítomné.",
|
||||
"webhookCustomBodyDesc": "Nastaví vlastní tělo HTTP pro požadavek. Akceptovány jsou proměnné {msg}, {heartbeat}, {monitor}.",
|
||||
"Request Body": "Tělo požadavku",
|
||||
"twilioApiKey": "Klíč k API (volitelný)",
|
||||
"Expected Value": "Očekávaná hodnota",
|
||||
"Json Query": "Json dotaz",
|
||||
"Badge Duration (in hours)": "Zobrazení odznaku (v hodinách)",
|
||||
"Badge Preview": "Náhled odznaku",
|
||||
"Notify Channel": "Kanál nofitikací",
|
||||
"aboutNotifyChannel": "Upozornění kanálu spustí upozornění na počítači nebo v mobilu pro všechny členy kanálu, ať už jsou dostupní nebo ne.",
|
||||
"filterActive": "Aktivní",
|
||||
"filterActivePaused": "Pozastaveno",
|
||||
"Enter the list of brokers": "Vytvořte seznam zprostředkovatelů",
|
||||
"Press Enter to add broker": "Stiskem klávesy Enter přidáte zprostředkovatele",
|
||||
"Kafka Topic Name": "Název Kafka vlákna",
|
||||
"Enable Kafka SSL": "Zapnout Kafka SSL",
|
||||
"Mechanism": "Mechanismus",
|
||||
"Kafka Brokers": "Kafka zprostředkovatelé",
|
||||
"Authorization Identity": "Autorizační identita",
|
||||
"AccessKey Id": "AccessKey Id",
|
||||
"Session Token": "Token relace",
|
||||
"Pick a SASL Mechanism...": "Vyberte SASL mechanismus…",
|
||||
"Secret AccessKey": "Secret AccessKey",
|
||||
"Server URL should not contain the nfty topic": "URL serveru by neměla obsahovat nfty vlákno",
|
||||
"Kafka SASL Options": "Možnosti Kafka SASL",
|
||||
"Enable Kafka Producer Auto Topic Creation": "Povolit Kafka zprostředkovateli automatické vytváření vláken",
|
||||
"Kafka Producer Message": "Zpráva Kafka zprostředkovatele",
|
||||
"tailscalePingWarning": "Abyste mohli používat Tailscale Ping monitor, je nutné Uptime Kuma nainstalovat mimo Docker, a dále na váš server nainstalovat Tailscale klienta.",
|
||||
"jsonQueryDescription": "Proveďte JSON dotaz vůči odpovědi a zkontrolujte očekávaný výstup (za účelem porovnání bude návratová hodnota převedena na řetězec). Dokumentaci k dotazovacímu jazyku naleznete na <a href='https://jsonata.org/'>jsonata.org</a>, a využít můžete též <a href='https://try.jsonata.org/'>playground</a>.",
|
||||
"Select": "Vybrat",
|
||||
"selectedMonitorCount": "Vybráno: {0}",
|
||||
"Check/Uncheck": "Vybrat/Zrušit výběr",
|
||||
"showCertificateExpiry": "Zobrazit vypršení platnosti certifikátu",
|
||||
"pushDeerServerDescription": "Chcete-li používat oficiální server, ponechte prázdné",
|
||||
"noOrBadCertificate": "Žádný/Vadný certifikát",
|
||||
"nostrRelays": "Relé Nostr",
|
||||
"FlashDuty Severity": "Závažnost",
|
||||
"PushDeer Server": "Server PushDeer",
|
||||
"wayToGetFlashDutyKey": "Můžete přejít na stránku Kanál -> (Vyberte kanál) -> Integrace -> Přidat novou integraci, přidat \"Vlastní událost\" a získat adresu pro odeslání, zkopírovat klíč integrace do adresy. Další informace naleznete na adrese",
|
||||
"Request Timeout": "Časový limit požadavku",
|
||||
"timeoutAfter": "Vypršení časového limitu po {0} sekundách",
|
||||
"styleElapsedTime": "Čas uplynulý pod heartbeat ukazatelem",
|
||||
"styleElapsedTimeShowWithLine": "Zobrazit (s linkou)",
|
||||
"gamedigGuessPortDescription": "Port používaný protokolem Valve Server Query Protocol se může lišit od portu klienta. Pokud se monitor nemůže připojit k serveru, zkuste to.",
|
||||
"styleElapsedTimeShowNoLine": "Zobrazit (bez linky)",
|
||||
"gamedigGuessPort": "Gamedig: Guess Port"
|
||||
}
|
||||
|
@@ -211,7 +211,7 @@
|
||||
"supportTelegramChatID": "Support Direct Chat / Group / Channel's Chat ID",
|
||||
"wayToGetTelegramChatID": "Du kan få dit chat-ID ved at sende en besked til bot'en og gå til denne URL for at se chat_id'et:",
|
||||
"YOUR BOT TOKEN HERE": "DIT BOT TOKEN HER",
|
||||
"chatIDNotFound": "Chat-ID blev ikke fundet; send venligst en besked til denne bot først ",
|
||||
"chatIDNotFound": "Chat-ID blev ikke fundet; send venligst en besked til denne bot først",
|
||||
"Post URL": "Post URL",
|
||||
"Content Type": "Indholdstype",
|
||||
"webhookJsonDesc": "{0} er god til alle moderne HTTP-servere som f.eks Express.js",
|
||||
@@ -558,7 +558,6 @@
|
||||
"high": "høj",
|
||||
"Base URL": "Base URL",
|
||||
"Platform": "Platform",
|
||||
"Android": "Android",
|
||||
"Huawei": "Huawei",
|
||||
"Retry": "Forsøg igen",
|
||||
"Topic": "Emne",
|
||||
|
@@ -48,7 +48,7 @@
|
||||
"Port": "Port",
|
||||
"Heartbeat Interval": "Prüfintervall",
|
||||
"Retries": "Wiederholungen",
|
||||
"retriesDescription": "Maximale Anzahl von Wiederholungen, bevor der Dienst als inaktiv markiert und eine Benachrichtigung gesendet wird.",
|
||||
"retriesDescription": "Maximale Wiederholungen, bevor der Dienst als inaktiv markiert und eine Benachrichtigung gesendet wird",
|
||||
"Advanced": "Erweitert",
|
||||
"ignoreTLSError": "Ignoriere TLS-/SSL-Fehler von Webseiten",
|
||||
"Upside Down Mode": "Umgekehrter Modus",
|
||||
@@ -109,10 +109,10 @@
|
||||
"Last Result": "Letztes Ergebnis",
|
||||
"pauseMonitorMsg": "Bist du sicher, dass du den Monitor pausieren möchtest?",
|
||||
"clearEventsMsg": "Bist du sicher, dass du alle Ereignisse für diesen Monitor löschen möchtest?",
|
||||
"clearHeartbeatsMsg": "Bist du sicher, dass du alle Statistiken für diesen Monitor löschen möchtest?",
|
||||
"clearHeartbeatsMsg": "Bist du sicher, dass du alle Prüfintervalle für diesen Monitor löschen möchtest?",
|
||||
"Clear Data": "Lösche Daten",
|
||||
"Events": "Ereignisse",
|
||||
"Heartbeats": "Statistiken",
|
||||
"Heartbeats": "Prüfintervalle",
|
||||
"confirmClearStatisticsMsg": "Bist du dir sicher, dass du ALLE Statistiken löschen möchtest?",
|
||||
"Create your admin account": "Erstelle dein Admin-Konto",
|
||||
"Repeat Password": "Passwort erneut eingeben",
|
||||
@@ -218,7 +218,7 @@
|
||||
"wayToGetTelegramToken": "Hier kannst du einen Token erhalten {0}.",
|
||||
"Chat ID": "Chat ID",
|
||||
"supportTelegramChatID": "Unterstützt Direkt Chat / Gruppe / Kanal Chat-ID's",
|
||||
"wayToGetTelegramChatID": "Du kannst die Chat-ID erhalten, indem du eine Nachricht an den Bot sendest und zu dieser URL gehst, um die chat_id: zu sehen.",
|
||||
"wayToGetTelegramChatID": "Du kannst die Chat-ID erhalten, indem du eine Nachricht an den Bot sendest und zu dieser URL gehst, um die chat_id zu sehen",
|
||||
"YOUR BOT TOKEN HERE": "HIER DEIN BOT TOKEN",
|
||||
"chatIDNotFound": "Chat-ID wurde nicht gefunden: bitte sende zuerst eine Nachricht an diesen Bot",
|
||||
"Post URL": "Post URL",
|
||||
@@ -380,12 +380,12 @@
|
||||
"alertaAlertState": "Alarmstatus",
|
||||
"alertaRecoverState": "Wiederherstellungsstatus",
|
||||
"deleteStatusPageMsg": "Bist du sicher, dass du diese Status-Seite löschen willst?",
|
||||
"Proxies": "Proxies",
|
||||
"Proxies": "Proxys",
|
||||
"default": "Standard",
|
||||
"enabled": "Aktiviert",
|
||||
"setAsDefault": "Als Standard setzen",
|
||||
"deleteProxyMsg": "Bist du sicher, dass du diesen Proxy für alle Monitore löschen willst?",
|
||||
"proxyDescription": "Proxies müssen einem Monitor zugewiesen werden, um zu funktionieren.",
|
||||
"proxyDescription": "Proxys müssen einem Monitor zugewiesen werden, um zu funktionieren.",
|
||||
"enableProxyDescription": "Dieser Proxy wird keinen Effekt auf Monitor-Anfragen haben, bis er aktiviert ist. Du kannst ihn temporär von allen Monitoren nach Aktivierungsstatus deaktivieren.",
|
||||
"setAsDefaultProxyDescription": "Dieser Proxy wird standardmässig für alle neuen Monitore aktiviert sein. Du kannst den Proxy immer noch für jeden Monitor einzeln deaktivieren.",
|
||||
"Certificate Chain": "Zertifikatskette",
|
||||
@@ -403,8 +403,6 @@
|
||||
"For safety, must use secret key": "Zur Sicherheit muss ein geheimer Schlüssel verwendet werden",
|
||||
"Device Token": "Gerätetoken",
|
||||
"Platform": "Platform",
|
||||
"iOS": "iOS",
|
||||
"Android": "Android",
|
||||
"Huawei": "Huawei",
|
||||
"High": "Hoch",
|
||||
"Retry": "Wiederholungen",
|
||||
@@ -657,10 +655,10 @@
|
||||
"telegramSendSilentlyDescription": "Sende die Nachricht stumm. Nutzer bekommen eine Benachrichtigung ohne Ton.",
|
||||
"markdownSupported": "Markdown-Syntax unterstützt",
|
||||
"webhookAdditionalHeadersTitle": "Zusätzliche Header",
|
||||
"webhookAdditionalHeadersDesc": "Legt zusätzliche Kopfzeilen fest, die mit dem Webhook gesendet werden.",
|
||||
"webhookAdditionalHeadersDesc": "Legt zusätzliche Kopfzeilen fest, die mit dem Webhook gesendet werden. Jede Kopfzeile sollte als JSON Schlüssel/Wert definiert werden.",
|
||||
"Packet Size": "Paketgrösse",
|
||||
"IconUrl": "Symbol URL",
|
||||
"Enable DNS Cache": "DNS Cache aktivieren",
|
||||
"Enable DNS Cache": "DNS-Cache für HTTP(s)-Monitore aktivieren",
|
||||
"Help": "Hilfe",
|
||||
"Game": "Spiel",
|
||||
"General Monitor Type": "Allgemeiner Monitortyp",
|
||||
@@ -745,7 +743,7 @@
|
||||
"twilioAccountSID": "Account SID",
|
||||
"twilioFromNumber": "Absender",
|
||||
"twilioToNumber": "Empfänger",
|
||||
"twilioAuthToken": "Auth Token",
|
||||
"twilioAuthToken": "Auth Token / Api Key Secret",
|
||||
"statusPageRefreshIn": "Aktualisierung in: {0}",
|
||||
"sameAsServerTimezone": "Gleiche Zeitzone wie Server",
|
||||
"startDateTime": "Start Datum/Uhrzeit",
|
||||
@@ -758,8 +756,8 @@
|
||||
"Badge Type": "Badge Typ",
|
||||
"Badge Duration": "Badge Dauer",
|
||||
"Badge Label": "Badge Label",
|
||||
"Badge Prefix": "Badge Präfix",
|
||||
"Badge Suffix": "Badge Suffix",
|
||||
"Badge Prefix": "Badge Wert Präfix",
|
||||
"Badge Suffix": "Badge Wert Suffix",
|
||||
"Badge Label Color": "Badge Label Farbe",
|
||||
"Badge Color": "Badge Farbe",
|
||||
"Badge Label Prefix": "Badge Label Präfix",
|
||||
@@ -781,5 +779,61 @@
|
||||
"Group": "Gruppe",
|
||||
"Monitor Group": "Monitor Gruppe",
|
||||
"noGroupMonitorMsg": "Nicht verfügbar. Erstelle zunächst einen Gruppenmonitor.",
|
||||
"Close": "Schliessen"
|
||||
"Close": "Schliessen",
|
||||
"chromeExecutableAutoDetect": "Automatische Erkennung",
|
||||
"chromeExecutableDescription": "Für Docker-Benutzer, die Chromium noch nicht installiert haben, kann es ein paar Minuten dauern, bis es installiert ist und das Testergebnis angezeigt wird. Es benötigt 1 GB Speicherplatz.",
|
||||
"chromeExecutable": "Chrome/Chromium Ausführbare Datei",
|
||||
"Invert Keyword": "Schlüsselwort invertieren",
|
||||
"webhookCustomBodyDesc": "Definiere einen benutzerdefinierten HTTP-Body für die Anfrage. Die Template-Variablen {msg}, {heartbeat} und {monitor} werden akzeptiert.",
|
||||
"webhookBodyPresetOption": "Voreinstellung - {0}",
|
||||
"webhookBodyCustomOption": "Benutzerdefinierter Body",
|
||||
"invertKeywordDescription": "Achte darauf, dass das Schlüsselwort eher fehlt als vorhanden ist.",
|
||||
"Request Body": "Anforderungstext",
|
||||
"twilioApiKey": "API-Schlüssel (optional)",
|
||||
"aboutNotifyChannel": "Notify Kanal löst eine Desktop- oder Mobilbenachrichtigung für alle Mitglieder des Kanals aus, unabhängig davon, ob deine Verfügbarkeit auf aktiv oder abwesend eingestellt ist.",
|
||||
"Notify Channel": "Notify Kanal",
|
||||
"Enter the list of brokers": "Gib die Liste der Broker ein",
|
||||
"Kafka Topic Name": "Kafka Topic Name",
|
||||
"Kafka Producer Message": "Kafka Producer Nachricht",
|
||||
"Enable Kafka SSL": "Kafka SSL aktivieren",
|
||||
"Enable Kafka Producer Auto Topic Creation": "Kafka Producer Auto Topic Creation aktivieren",
|
||||
"Kafka SASL Options": "Kafka SASL Optionen",
|
||||
"Mechanism": "Mechanismus",
|
||||
"Pick a SASL Mechanism...": "Wähle ein SASL Mechanismus...",
|
||||
"AccessKey Id": "AccessKey Id",
|
||||
"Secret AccessKey": "Secret AccessKey",
|
||||
"Session Token": "Sitzungs-Token",
|
||||
"Kafka Brokers": "Kafka Brokers",
|
||||
"Press Enter to add broker": "Drücke Enter um den Broker hinzuzufügen",
|
||||
"Authorization Identity": "Authorization Identity",
|
||||
"Expected Value": "Erwarteter Wert",
|
||||
"Json Query": "Json-Abfrage",
|
||||
"filterActive": "Aktiv",
|
||||
"filterActivePaused": "Pausiert",
|
||||
"jsonQueryDescription": "Führe eine JSON-Abfrage gegen die Antwort durch und prüfe den erwarteten Wert (der Rückgabewert wird zum Vergleich in eine Zeichenkette umgewandelt). Auf <a href='https://jsonata.org/'>jsonata.org</a> findest du die Dokumentation zur Abfragesprache. <a href='https://try.jsonata.org/'>Hier</a> kannst du Abfragen üben.",
|
||||
"Badge Duration (in hours)": "Badge Dauer (in Stunden)",
|
||||
"Badge Preview": "Badge Vorschau",
|
||||
"tailscalePingWarning": "Um den Tailscale Ping Monitor nutzen zu können, musst du Uptime Kuma ohne Docker installieren und den Tailscale Client auf dem Server installieren.",
|
||||
"Server URL should not contain the nfty topic": "Die Server-URL sollte das nfty-Thema nicht enthalten",
|
||||
"pushDeerServerDescription": "Leer lassen um den offiziellen Server zu verwenden",
|
||||
"FlashDuty Severity": "Schweregrad",
|
||||
"nostrSender": "Privater Schlüssel des Absenders (nsec)",
|
||||
"nostrRecipientsHelp": "npub-Format, eine pro Zeile",
|
||||
"noOrBadCertificate": "Kein/schlechtes Zertifikat",
|
||||
"wayToGetFlashDutyKey": "Gehe zu Channel -> (Wähle einen Channel) -> Integrationen -> Neue Integration hinzufügen', füge ein 'Custom Event' hinzu, um eine Push-Adresse zu erhalten, und kopiere den Integrationsschlüssel in die Adresse. Für weitere Informationen besuche bitte",
|
||||
"nostrRelays": "Nostr relays",
|
||||
"nostrRelaysHelp": "Eine Relay-URL pro Zeile",
|
||||
"nostrRecipients": "Öffentliche Schlüssel des Empfängers (npub)",
|
||||
"gamedigGuessPort": "Gamedig: Guess Port",
|
||||
"Request Timeout": "Zeitüberschreitung der Anfrage",
|
||||
"styleElapsedTimeShowNoLine": "Anzeigen (keine Zeile)",
|
||||
"styleElapsedTimeShowWithLine": "Anzeigen (mit Zeile)",
|
||||
"Select": "Auswählen",
|
||||
"selectedMonitorCount": "Ausgewählt: {0}",
|
||||
"PushDeer Server": "PushDeer Server",
|
||||
"showCertificateExpiry": "Ablauf des Zertifikats anzeigen",
|
||||
"gamedigGuessPortDescription": "Der vom Valve Server Query Protocol verwendete Port kann sich vom Port des Clients unterscheiden. Versuche dies, wenn der Monitor keine Verbindung zum Server herstellen kann.",
|
||||
"timeoutAfter": "Zeitüberschreitung nach {0} Sekunden",
|
||||
"styleElapsedTime": "Verstrichene Zeit unter der Prüfintervallleiste",
|
||||
"Check/Uncheck": "Aktivieren/Deaktivieren"
|
||||
}
|
||||
|
@@ -48,7 +48,7 @@
|
||||
"Port": "Port",
|
||||
"Heartbeat Interval": "Prüfintervall",
|
||||
"Retries": "Wiederholungen",
|
||||
"retriesDescription": "Maximale Anzahl von Wiederholungen, bevor der Dienst als inaktiv markiert und eine Benachrichtigung gesendet wird.",
|
||||
"retriesDescription": "Maximale Wiederholungen, bevor der Dienst als inaktiv markiert und eine Benachrichtigung gesendet wird",
|
||||
"Advanced": "Erweitert",
|
||||
"ignoreTLSError": "Ignoriere TLS-/SSL-Fehler von Webseiten",
|
||||
"Upside Down Mode": "Umgekehrter Modus",
|
||||
@@ -109,10 +109,10 @@
|
||||
"Last Result": "Letztes Ergebnis",
|
||||
"pauseMonitorMsg": "Bist du sicher, dass du den Monitor pausieren möchtest?",
|
||||
"clearEventsMsg": "Bist du sicher, dass du alle Ereignisse für diesen Monitor löschen möchtest?",
|
||||
"clearHeartbeatsMsg": "Bist du sicher, dass du alle Statistiken für diesen Monitor löschen möchtest?",
|
||||
"clearHeartbeatsMsg": "Bist du sicher, dass du alle Prüfintervalle für diesen Monitor löschen möchtest?",
|
||||
"Clear Data": "Lösche Daten",
|
||||
"Events": "Ereignisse",
|
||||
"Heartbeats": "Statistiken",
|
||||
"Heartbeats": "Prüfintervalle",
|
||||
"confirmClearStatisticsMsg": "Bist du dir sicher, dass du ALLE Statistiken löschen möchtest?",
|
||||
"Create your admin account": "Erstelle dein Admin-Konto",
|
||||
"Repeat Password": "Passwort erneut eingeben",
|
||||
@@ -218,7 +218,7 @@
|
||||
"wayToGetTelegramToken": "Hier kannst du einen Token erhalten {0}.",
|
||||
"Chat ID": "Chat ID",
|
||||
"supportTelegramChatID": "Unterstützt Direkt Chat / Gruppe / Kanal Chat-ID's",
|
||||
"wayToGetTelegramChatID": "Du kannst deine Chat-ID erhalten, indem du eine Nachricht an den Bot sendest und zu dieser URL gehst, um die chat_id: zu sehen.",
|
||||
"wayToGetTelegramChatID": "Du kannst deine Chat-ID erhalten, indem du eine Nachricht an den Bot sendest und zu dieser URL gehst, um die chat_id zu sehen:",
|
||||
"YOUR BOT TOKEN HERE": "HIER DEIN BOT TOKEN",
|
||||
"chatIDNotFound": "Chat-ID wurde nicht gefunden: bitte sende zuerst eine Nachricht an diesen Bot",
|
||||
"Post URL": "Post URL",
|
||||
@@ -380,12 +380,12 @@
|
||||
"alertaAlertState": "Alarmstatus",
|
||||
"alertaRecoverState": "Wiederherstellungsstatus",
|
||||
"deleteStatusPageMsg": "Bist du sicher, dass du diese Status-Seite löschen willst?",
|
||||
"Proxies": "Proxies",
|
||||
"Proxies": "Proxys",
|
||||
"default": "Standard",
|
||||
"enabled": "Aktiviert",
|
||||
"setAsDefault": "Als Standard setzen",
|
||||
"deleteProxyMsg": "Bist du sicher, dass du diesen Proxy für alle Monitore löschen willst?",
|
||||
"proxyDescription": "Proxies müssen einem Monitor zugewiesen werden, um zu funktionieren.",
|
||||
"proxyDescription": "Proxys müssen einem Monitor zugewiesen werden, um zu funktionieren.",
|
||||
"enableProxyDescription": "Dieser Proxy wird keinen Effekt auf Monitor-Anfragen haben, bis er aktiviert ist. Du kannst ihn temporär von allen Monitoren nach Aktivierungsstatus deaktivieren.",
|
||||
"setAsDefaultProxyDescription": "Dieser Proxy wird standardmäßig für alle neuen Monitore aktiviert sein. Du kannst den Proxy immer noch für jeden Monitor einzeln deaktivieren.",
|
||||
"Certificate Chain": "Zertifikatskette",
|
||||
@@ -403,8 +403,6 @@
|
||||
"For safety, must use secret key": "Zur Sicherheit muss ein geheimer Schlüssel verwendet werden",
|
||||
"Device Token": "Gerätetoken",
|
||||
"Platform": "Platform",
|
||||
"iOS": "iOS",
|
||||
"Android": "Android",
|
||||
"Huawei": "Huawei",
|
||||
"High": "Hoch",
|
||||
"Retry": "Wiederholungen",
|
||||
@@ -646,11 +644,11 @@
|
||||
"Help": "Hilfe",
|
||||
"Game": "Spiel",
|
||||
"Custom": "Benutzerdefiniert",
|
||||
"Enable DNS Cache": "DNS-Cache aktivieren",
|
||||
"Enable DNS Cache": "DNS-Cache für HTTP(s)-Monitore aktivieren",
|
||||
"Enable": "Aktivieren",
|
||||
"Disable": "Deaktivieren",
|
||||
"Custom Monitor Type": "Benutzerdefinierter Monitortyp",
|
||||
"webhookAdditionalHeadersDesc": "Legt zusätzliche Header fest, die mit der Webhook gesendet wurden.",
|
||||
"webhookAdditionalHeadersDesc": "Legt zusätzliche Kopfzeilen fest, die mit dem Webhook gesendet werden. Jede Kopfzeile sollte als JSON Schlüssel/Wert definiert werden.",
|
||||
"dnsCacheDescription": "In einigen IPv6-Umgebungen funktioniert es möglicherweise nicht. Deaktiviere es, wenn Probleme auftreten.",
|
||||
"loadingError": "Die Daten konnten nicht abgerufen werden, bitte später noch einmal versuchen.",
|
||||
"confirmUninstallPlugin": "Möchtest du dieses Plugin wirklich deinstallieren?",
|
||||
@@ -702,7 +700,7 @@
|
||||
"Edit Tag": "bearbeite Tag",
|
||||
"Server Address": "Server Adresse",
|
||||
"Learn More": "Erfahre mehr",
|
||||
"Body Encoding": "Körperkodierung",
|
||||
"Body Encoding": "Inhaltskodierung",
|
||||
"Add API Key": "API Schlüssel hinzufügen",
|
||||
"apiKey-active": "Aktiv",
|
||||
"apiKey-expired": "Abgelaufen",
|
||||
@@ -749,7 +747,7 @@
|
||||
"twilioAccountSID": "Account SID",
|
||||
"twilioFromNumber": "Absender",
|
||||
"twilioToNumber": "Empfänger",
|
||||
"twilioAuthToken": "Auth Token",
|
||||
"twilioAuthToken": "Auth Token / Api Key Secret",
|
||||
"statusPageRefreshIn": "Aktualisierung in: {0}",
|
||||
"sameAsServerTimezone": "Gleiche Zeitzone wie Server",
|
||||
"startDateTime": "Start Datum/Uhrzeit",
|
||||
@@ -778,11 +776,67 @@
|
||||
"Badge Pending Color": "Badge Pending Farbe",
|
||||
"Badge Down Days": "Badge Down Tage",
|
||||
"Monitor Setting": "{0}'s Monitor Einstellung",
|
||||
"Badge Prefix": "Badge Präfix",
|
||||
"Badge Suffix": "Badge Suffix",
|
||||
"Badge Prefix": "Badge Wert Präfix",
|
||||
"Badge Suffix": "Badge Wert Suffix",
|
||||
"Badge Warn Days": "Badge Warnung Tage",
|
||||
"Group": "Gruppe",
|
||||
"Monitor Group": "Monitor Gruppe",
|
||||
"noGroupMonitorMsg": "Nicht verfügbar. Erstelle zunächst einen Gruppenmonitor.",
|
||||
"Close": "Schließen"
|
||||
"Close": "Schließen",
|
||||
"chromeExecutableAutoDetect": "Automatische Erkennung",
|
||||
"chromeExecutableDescription": "Für Docker-Benutzer, die Chromium noch nicht installiert haben, kann es ein paar Minuten dauern, bis es installiert ist und das Testergebnis angezeigt wird. Es benötigt 1 GB Speicherplatz.",
|
||||
"chromeExecutable": "Chrome/Chromium Ausführbare Datei",
|
||||
"Invert Keyword": "Schlüsselwort invertieren",
|
||||
"invertKeywordDescription": "Achte darauf, dass das Schlüsselwort eher fehlt als vorhanden ist.",
|
||||
"webhookCustomBodyDesc": "Definiere einen benutzerdefinierten HTTP-Body für die Anfrage. Die Template-Variablen {msg}, {heartbeat} und {monitor} werden akzeptiert.",
|
||||
"webhookBodyPresetOption": "Voreinstellung - {0}",
|
||||
"webhookBodyCustomOption": "Benutzerdefinierter Body",
|
||||
"Request Body": "Anforderungstext",
|
||||
"Badge Duration (in hours)": "Badge Dauer (in Stunden)",
|
||||
"Badge Preview": "Badge Vorschau",
|
||||
"twilioApiKey": "API-Schlüssel (optional)",
|
||||
"Notify Channel": "Notify Kanal",
|
||||
"Enter the list of brokers": "Gib die Liste der Broker ein",
|
||||
"Kafka Topic Name": "Kafka Topic Name",
|
||||
"Kafka Producer Message": "Kafka Producer Nachricht",
|
||||
"Enable Kafka SSL": "Kafka SSL aktivieren",
|
||||
"Enable Kafka Producer Auto Topic Creation": "Kafka Producer Auto Topic Creation aktivieren",
|
||||
"Kafka SASL Options": "Kafka SASL Optionen",
|
||||
"Mechanism": "Mechanismus",
|
||||
"Pick a SASL Mechanism...": "Wähle ein SASL Mechanismus...",
|
||||
"Authorization Identity": "Authorization Identity",
|
||||
"AccessKey Id": "AccessKey Id",
|
||||
"Secret AccessKey": "Secret AccessKey",
|
||||
"Session Token": "Sitzungs-Token",
|
||||
"aboutNotifyChannel": "Notify Kanal löst eine Desktop- oder Mobilbenachrichtigung für alle Mitglieder des Kanals aus, unabhängig davon, ob deine Verfügbarkeit auf aktiv oder abwesend eingestellt ist.",
|
||||
"Kafka Brokers": "Kafka Brokers",
|
||||
"Press Enter to add broker": "Drücke Enter um den Broker hinzuzufügen",
|
||||
"filterActive": "Aktiv",
|
||||
"filterActivePaused": "Pausiert",
|
||||
"Expected Value": "Erwarteter Wert",
|
||||
"Json Query": "Json-Abfrage",
|
||||
"jsonQueryDescription": "Führe eine JSON-Abfrage gegen die Antwort durch und prüfe den erwarteten Wert (der Rückgabewert wird zum Vergleich in eine Zeichenkette umgewandelt). Auf <a href='https://jsonata.org/'>jsonata.org</a> findest du die Dokumentation zur Abfragesprache. <a href='https://try.jsonata.org/'>Hier</a> kannst du Abfragen üben.",
|
||||
"tailscalePingWarning": "Um den Tailscale Ping Monitor nutzen zu können, musst du Uptime Kuma ohne Docker installieren und den Tailscale Client auf dem Server installieren.",
|
||||
"Server URL should not contain the nfty topic": "Die Server-URL sollte das nfty-Thema nicht enthalten",
|
||||
"pushDeerServerDescription": "Leer lassen um den offiziellen Server zu verwenden",
|
||||
"FlashDuty Severity": "Schweregrad",
|
||||
"nostrRelays": "Nostr relays",
|
||||
"gamedigGuessPort": "Gamedig: Guess Port",
|
||||
"Request Timeout": "Zeitüberschreitung der Anfrage",
|
||||
"styleElapsedTimeShowNoLine": "Anzeigen (keine Zeile)",
|
||||
"Select": "Auswählen",
|
||||
"selectedMonitorCount": "Ausgewählt: {0}",
|
||||
"PushDeer Server": "PushDeer Server",
|
||||
"nostrRelaysHelp": "Eine Relay-URL pro Zeile",
|
||||
"nostrSender": "Privater Schlüssel des Absenders (nsec)",
|
||||
"gamedigGuessPortDescription": "Der vom Valve Server Query Protocol verwendete Port kann sich vom Port des Clients unterscheiden. Versuche dies, wenn der Monitor keine Verbindung zum Server herstellen kann.",
|
||||
"wayToGetFlashDutyKey": "Gehe zu Channel -> (Wähle einen Channel) -> Integrationen -> Neue Integration hinzufügen', füge ein 'Custom Event' hinzu, um eine Push-Adresse zu erhalten, und kopiere den Integrationsschlüssel in die Adresse. Für weitere Informationen besuche bitte",
|
||||
"timeoutAfter": "Zeitüberschreitung nach {0} Sekunden",
|
||||
"styleElapsedTimeShowWithLine": "Anzeigen (mit Zeile)",
|
||||
"styleElapsedTime": "Verstrichene Zeit unter der Prüfintervallleiste",
|
||||
"Check/Uncheck": "Aktivieren/Deaktivieren",
|
||||
"nostrRecipients": "Öffentliche Schlüssel des Empfängers (npub)",
|
||||
"nostrRecipientsHelp": "npub-Format, eine pro Zeile",
|
||||
"showCertificateExpiry": "Ablauf des Zertifikats anzeigen",
|
||||
"noOrBadCertificate": "Kein/schlechtes Zertifikat"
|
||||
}
|
||||
|
@@ -420,8 +420,6 @@
|
||||
"For safety, must use secret key": "Για ασφάλεια, πρέπει να χρησιμοποιήσετε secret key",
|
||||
"Device Token": "Device Token",
|
||||
"Platform": "Platform",
|
||||
"iOS": "iOS",
|
||||
"Android": "Android",
|
||||
"Huawei": "Huawei",
|
||||
"High": "High",
|
||||
"Retry": "Ξαναδοκιμάσετε",
|
||||
|
@@ -59,6 +59,8 @@
|
||||
"Hostname": "Hostname",
|
||||
"Port": "Port",
|
||||
"Heartbeat Interval": "Heartbeat Interval",
|
||||
"Request Timeout": "Request Timeout",
|
||||
"timeoutAfter": "Timeout after {0} seconds",
|
||||
"Retries": "Retries",
|
||||
"Heartbeat Retry Interval": "Heartbeat Retry Interval",
|
||||
"Resend Notification if Down X times consecutively": "Resend Notification if Down X times consecutively",
|
||||
@@ -85,6 +87,9 @@
|
||||
"Dark": "Dark",
|
||||
"Auto": "Auto",
|
||||
"Theme - Heartbeat Bar": "Theme - Heartbeat Bar",
|
||||
"styleElapsedTime": "Elapsed time under the heartbeat bar",
|
||||
"styleElapsedTimeShowNoLine": "Show (No Line)",
|
||||
"styleElapsedTimeShowWithLine": "Show (With Line)",
|
||||
"Normal": "Normal",
|
||||
"Bottom": "Bottom",
|
||||
"None": "None",
|
||||
@@ -269,6 +274,9 @@
|
||||
"Services": "Services",
|
||||
"Discard": "Discard",
|
||||
"Cancel": "Cancel",
|
||||
"Select": "Select",
|
||||
"selectedMonitorCount": "Selected: {0}",
|
||||
"Check/Uncheck": "Check/Uncheck",
|
||||
"Powered by": "Powered by",
|
||||
"shrinkDatabaseDescription": "Trigger database VACUUM for SQLite. If your database is created after 1.10.0, AUTO_VACUUM is already enabled and this action is not needed.",
|
||||
"Customize": "Customize",
|
||||
@@ -361,9 +369,12 @@
|
||||
"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",
|
||||
"tailscalePingWarning": "In order to use the Tailscale Ping monitor, you need to install Uptime Kuma without Docker and also install Tailscale client on your server.",
|
||||
"Docker Container": "Docker Container",
|
||||
"Container Name / ID": "Container Name / ID",
|
||||
"Docker Host": "Docker Host",
|
||||
@@ -440,9 +451,10 @@
|
||||
"Server Timezone": "Server Timezone",
|
||||
"statusPageMaintenanceEndDate": "End",
|
||||
"IconUrl": "Icon URL",
|
||||
"Enable DNS Cache": "Enable DNS Cache",
|
||||
"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.",
|
||||
@@ -619,7 +631,6 @@
|
||||
"For safety, must use secret key": "For safety, must use secret key",
|
||||
"Device Token": "Device Token",
|
||||
"Platform": "Platform",
|
||||
"Android": "Android",
|
||||
"Huawei": "Huawei",
|
||||
"High": "High",
|
||||
"Retry": "Retry",
|
||||
@@ -690,12 +701,15 @@
|
||||
"Octopush API Version": "Octopush API Version",
|
||||
"Legacy Octopush-DM": "Legacy Octopush-DM",
|
||||
"ntfy Topic": "ntfy Topic",
|
||||
"Server URL should not contain the nfty topic": "Server URL should not contain the nfty topic",
|
||||
"onebotHttpAddress": "OneBot HTTP Address",
|
||||
"onebotMessageType": "OneBot Message Type",
|
||||
"onebotGroupMessage": "Group",
|
||||
"onebotPrivateMessage": "Private",
|
||||
"onebotUserOrGroupId": "Group/User ID",
|
||||
"onebotSafetyTips": "For safety, must set access token",
|
||||
"PushDeer Server": "PushDeer Server",
|
||||
"pushDeerServerDescription": "Leave blank to use the official server",
|
||||
"PushDeer Key": "PushDeer Key",
|
||||
"wayToGetClickSendSMSToken": "You can get API Username and API Key from {0} .",
|
||||
"Custom Monitor Type": "Custom Monitor Type",
|
||||
@@ -784,5 +798,16 @@
|
||||
"Session Token": "Session Token",
|
||||
"noGroupMonitorMsg": "Not Available. Create a Group Monitor First.",
|
||||
"Close": "Close",
|
||||
"Request Body": "Request Body"
|
||||
"Request Body": "Request Body",
|
||||
"wayToGetFlashDutyKey":"You can go to Channel -> (Select a Channel) -> Integrations -> Add a new integration' page, add a 'Custom Event' to get a push address, copy the Integration Key in the address. For more information, please visit",
|
||||
"FlashDuty Severity":"Severity",
|
||||
"nostrRelays": "Nostr relays",
|
||||
"nostrRelaysHelp": "One relay URL per line",
|
||||
"nostrSender": "Sender Private Key (nsec)",
|
||||
"nostrRecipients": "Recipients Public Keys (npub)",
|
||||
"nostrRecipientsHelp": "npub format, one per line",
|
||||
"showCertificateExpiry": "Show Certificate Expiry",
|
||||
"noOrBadCertificate": "No/Bad Certificate",
|
||||
"gamedigGuessPort": "Gamedig: Guess Port",
|
||||
"gamedigGuessPortDescription": "The port used by Valve Server Query Protocol may be different from the client port. Try this if the monitor cannot connect to your server."
|
||||
}
|
||||
|
@@ -270,7 +270,7 @@
|
||||
"Display Timezone": "Mostrar Zona Horaria",
|
||||
"Server Timezone": "Servidor de Zona Horaria",
|
||||
"statusPageMaintenanceEndDate": "Finaliza",
|
||||
"Enable DNS Cache": "Habilitar Cache DNS",
|
||||
"Enable DNS Cache": "Habilitar Cache DNS de monitores HTTP(s)",
|
||||
"No Maintenance": "Sin Mantenimiento",
|
||||
"weekdayShortSun": "Dom",
|
||||
"dayOfWeek": "Día de la Semana",
|
||||
@@ -389,7 +389,7 @@
|
||||
"emojiCheatSheet": "Hoja de trucos Emoji: {0}",
|
||||
"webhookJsonDesc": "{0} es bueno para cualquier servidor HTTP moderno como Express.js",
|
||||
"webhookFormDataDesc": "{multipart} es bueno para PHP. El JSON deberá analizarse con {decodeFunction}",
|
||||
"webhookAdditionalHeadersDesc": "Establece encabezados adicionales enviados con el webhook.",
|
||||
"webhookAdditionalHeadersDesc": "Establece encabezados adicionales enviados con el webhook. Cada cabecera debe definirse como una clave/valor JSON.",
|
||||
"appriseInstalled": "Apprise está instalado.",
|
||||
"successMessage": "Mensaje de éxito",
|
||||
"Pick Accepted Status Codes...": "Seleccione Códigos de Estado Aceptados…",
|
||||
@@ -497,8 +497,6 @@
|
||||
"Proto Method": "Método Proto",
|
||||
"Proto Content": "Contenido Proto",
|
||||
"Economy": "Económico",
|
||||
"iOS": "iOS",
|
||||
"Android": "Android",
|
||||
"Platform": "Plataforma",
|
||||
"onebotPrivateMessage": "Privado",
|
||||
"onebotMessageType": "Tipo de Mensaje OneBot",
|
||||
@@ -590,8 +588,8 @@
|
||||
"GoogleChat": "Chat de Google (sólo Google Workspace)",
|
||||
"Kook": "Kook",
|
||||
"wayToGetKookBotToken": "Crea aplicación y obtén tu token de bot en {0}",
|
||||
"wayToGetKookGuildID": "Activa 'Modo Desarrollador' en los ajustes de Kook, y haz click derecho en la unión para obtener su ID",
|
||||
"Guild ID": "ID de Gremio",
|
||||
"wayToGetKookGuildID": "Activa 'Modo Desarrollador' en los ajustes de Kook, y haz click derecho en el grupo para obtener su ID",
|
||||
"Guild ID": "ID de grupo",
|
||||
"User Key": "Key de Usuario",
|
||||
"octopushTypePremium": "Premium (Rápido - recomendado para alertas)",
|
||||
"octopushTypeLowCost": "Bajo Coste (Lento - algunas veces bloqueado por operador)",
|
||||
@@ -655,7 +653,7 @@
|
||||
"gorush": "Gorush",
|
||||
"squadcast": "Squadcast",
|
||||
"Maintenance Time Window of a Day": "Ventana de tiempo de mantenimiento de un día",
|
||||
"Effective Date Range": "Rango de Fecha Efectivo",
|
||||
"Effective Date Range": "Rango de Fecha Efectivo (Opcional)",
|
||||
"Free Mobile User Identifier": "Identificador de Usuario de Free Mobile",
|
||||
"Gateway Type": "Tipo de Puerta de Enlace",
|
||||
"SMSManager": "SMSManager",
|
||||
@@ -698,7 +696,7 @@
|
||||
"High": "Alto",
|
||||
"alertaApiEndpoint": "Endpoint API",
|
||||
"Body Encoding": "Codificación del cuerpo",
|
||||
"Expiry date": "Fecha de expiración",
|
||||
"Expiry date": "Fecha de vencimiento",
|
||||
"Expiry": "Expiración",
|
||||
"API Keys": "Claves API",
|
||||
"Key Added": "Clave añadida",
|
||||
@@ -751,7 +749,22 @@
|
||||
"statusPageRefreshIn": "Reinicio en: {0}",
|
||||
"twilioAuthToken": "Token de Autentificación",
|
||||
"ntfyUsernameAndPassword": "Nombre de Usuario y Contraseña",
|
||||
"ntfyAuthenticationMethod": "Método de Autentificación",
|
||||
"ntfyAuthenticationMethod": "Método de Autenticación",
|
||||
"Cannot connect to the socket server": "No se puede conectar al servidor socket",
|
||||
"Reconnecting...": "Reconectando..."
|
||||
"Reconnecting...": "Reconectando...",
|
||||
"Select": "Seleccionar",
|
||||
"chromeExecutableAutoDetect": "Auto Detectar",
|
||||
"Edit Maintenance": "Editar mantenimiento",
|
||||
"pushoverMessageTtl": "Mensaje TTL (segundos)",
|
||||
"Notify Channel": "Canal de notificación",
|
||||
"Show Clickable Link Description": "Si está marcado, todos los que tienen acceso a esta página de estado pueden tener acceso a la URL del monitor.",
|
||||
"webhookBodyCustomOption": "Cuerpo Personalizado",
|
||||
"selectedMonitorCount": "Seleccionado: {0}",
|
||||
"Check/Uncheck": "Marcar/Desmarcar",
|
||||
"Invert Keyword": "Invertir palabra clave",
|
||||
"filterActive": "Activo",
|
||||
"filterActivePaused": "Pausado",
|
||||
"Home": "Inicio",
|
||||
"Expected Value": "Valor esperado",
|
||||
"Json Query": "Consulta Json"
|
||||
}
|
||||
|
@@ -415,8 +415,6 @@
|
||||
"For safety, must use secret key": "For safety, must use secret key",
|
||||
"Device Token": "Gailu tokena",
|
||||
"Platform": "Plataforma",
|
||||
"iOS": "iOS",
|
||||
"Android": "Android",
|
||||
"Huawei": "Huawei",
|
||||
"High": "Altua",
|
||||
"Retry": "Errepikatu",
|
||||
@@ -571,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"
|
||||
}
|
||||
|
@@ -36,7 +36,7 @@
|
||||
"Version": "نسخه",
|
||||
"Check Update On GitHub": "بررسی بروزرسانی بر روی گیتهاب",
|
||||
"List": "لیست",
|
||||
"Add": "اضافه",
|
||||
"Add": "اضافه کردن",
|
||||
"Add New Monitor": "اضافه کردن مانیتور جدید",
|
||||
"Quick Stats": "خلاصه وضعیت",
|
||||
"Up": "فعال",
|
||||
@@ -241,7 +241,7 @@
|
||||
"apiKeyAddedMsg": "کلید API شما اضافه شده است. لطفاً آن را یادداشت کنید زیرا دیگر نمایش داده نخواهد شد.",
|
||||
"deleteAPIKeyMsg": "آیا مطمئن هستید که می خواهید این کلید API را غیرفعال کنید؟",
|
||||
"twilioAccountSID": "SID حساب",
|
||||
"twilioAuthToken": "توکن اعتبارسنجی",
|
||||
"twilioAuthToken": "توکن اعتبارسنجی / کلید مخفی API",
|
||||
"appriseNotInstalled": "Apprise نصب نشده است. {0}",
|
||||
"trustProxyDescription": "به هدرهای «X-Forwarded-*» اعتماد کن. اگر میخواهید IP مشتری صحیح را دریافت کنید و آپتایم کومای شما پشت پروکسی مانند Nginx یا Apache قرار دارد، باید این گزینه را فعال کنید.",
|
||||
"matrixDesc2": "اکیداً توصیه میشود که یک کاربر جدید ایجاد کنید و از رمز دسترسی کاربر Matrix خود استفاده نکنید زیرا امکان دسترسی کامل به حساب شما و تمام اتاقهایی را که به آنها ملحق شدهاید میدهد. در عوض، یک کاربر جدید ایجاد کنید و فقط او را به اتاقی دعوت کنید که میخواهید اعلان را دریافت کنید. میتوانید با اجرای {0} توکن دسترسی را دریافت کنید",
|
||||
@@ -568,7 +568,6 @@
|
||||
"SendKey": "کلید ارسال (SendKey)",
|
||||
"SecretAccessKey": "کلید دسترسی مخفی (AccessKey Secret)",
|
||||
"SignName": "نام امضا (SignName)",
|
||||
"Android": "اندروید",
|
||||
"Huawei": "هواوی",
|
||||
"WeCom Bot Key": "کلید ربات WeCom",
|
||||
"Setup Proxy": "تنظیم پروکسی",
|
||||
@@ -643,7 +642,7 @@
|
||||
"Schedule maintenance": "زمانبندی نگهداری (غیرفعال سازی دستی)",
|
||||
"webhookFormDataDesc": "{multipart} برای PHP مناسب است. آرایه JSON نیاز است تا به این شکل باز شود {decodeFunction}",
|
||||
"webhookAdditionalHeadersTitle": "هدر اضافی",
|
||||
"webhookAdditionalHeadersDesc": "تنظیم هدر های اضافی که نیاز است با وب هوک ارسال شود.",
|
||||
"webhookAdditionalHeadersDesc": "تنظیم هدر های اضافی که نیاز است با وب هوک ارسال شود. هر هدر باید به کیلد/مقدار JSON تعریف شده باشد.",
|
||||
"Webhook URL": "آدرس وب هوک",
|
||||
"Application Token": "توکن اپلیکیشن",
|
||||
"Style": "حالت ها",
|
||||
@@ -673,7 +672,7 @@
|
||||
"Server Timezone": "منطقه زمانی در سرور",
|
||||
"statusPageMaintenanceEndDate": "پایان",
|
||||
"IconUrl": "URL آیکون",
|
||||
"Enable DNS Cache": "فعال سازی کش DNS",
|
||||
"Enable DNS Cache": "فعال سازی کش DNS برای مانیتور های HTTP",
|
||||
"Access Token": "توکن دسترسی",
|
||||
"smtp": "ایمیل (SMTP)",
|
||||
"Device": "دستگاه",
|
||||
@@ -728,8 +727,8 @@
|
||||
"Badge Type": "نوع نشان",
|
||||
"Badge Duration": "مدت نشان",
|
||||
"Badge Label": "برچسب نشان",
|
||||
"Badge Prefix": "پیشوند نشان",
|
||||
"Badge Suffix": "پسوند نشان",
|
||||
"Badge Prefix": "مقدار پیشوند نشان",
|
||||
"Badge Suffix": "مقدار پسوند نشان",
|
||||
"Badge Label Color": "رنگ برچسب نشان",
|
||||
"Badge Color": "رنگ نشان",
|
||||
"Badge Label Prefix": "پیشوند برچسب نشان",
|
||||
@@ -753,5 +752,61 @@
|
||||
"Reconnecting...": "ارتباط مجدد...",
|
||||
"Monitor Group": "گروه مانیتور",
|
||||
"Group": "گروه",
|
||||
"Close": "بستن"
|
||||
"Close": "بستن",
|
||||
"Request Timeout": "زمان تایم اوت ریکوئست",
|
||||
"filterActive": "فعال",
|
||||
"webhookCustomBodyDesc": "یک بدنه HTTP سفارشی برای ریکوئست تعریف کنید. متغیر های قابل استفاده: {msg}, {heartbeat}, {monitor}.",
|
||||
"tailscalePingWarning": "برای استفاده از Tailscale Ping monitor، شما باید آپتایم کوما را بدون استفاده از داکر و همچنین Tailscale client را نیز بر روی سرور خود نصب داشته باشید.",
|
||||
"jsonQueryDescription": "یک کوئری json در برابر پاسخ انجام دهید و مقدار مورد انتظار را (مقدار برگشتی برای مقایسه به رشته تبدیل می شود). برای مستندات درباره زبان کوئری، <a href='https://jsonata.org/'>jsonata.org</a> مشاهده کنید. همچنین محیط تست را میتوانید در <a href='https://try.jsonata.org/'>اینجا</a> پیدا کنید.",
|
||||
"Enter the list of brokers": "لیست بروکر هارا وارد کنید",
|
||||
"Enable Kafka Producer Auto Topic Creation": "فعال سازی ایجاپ موضوع اتوماتیک تهیه کننده",
|
||||
"Secret AccessKey": "کلید محرمانه AccessKey",
|
||||
"wayToGetFlashDutyKey": "می توانید به کانال -> (انتخاب یک کانال) -> یکپارچه سازی -> صفحه یکپارچه سازی جدید بروید، یک \"رویداد سفارشی\" را برای دریافت یک آدرس فشار اضافه کنید، کلید یکپارچه سازی را در آدرس کپی کنید. برای اطلاعات بیشتر لطفا مراجعه کنید به",
|
||||
"showCertificateExpiry": "نمایش زمان به پایان رسیدن اعتبار سرتیفیکیت",
|
||||
"gamedigGuessPortDescription": "پورت مورد استفاده توسط پروتکل Query Valve Server ممکن است با پورت مشتری متفاوت باشد. اگر مانیتور نمی تواند به سرور شما متصل شود، این را امتحان کنید.",
|
||||
"invertKeywordDescription": "دنبال کلمات کلیدی ناموجود باشید تا آنهایی که موجود است.",
|
||||
"Notify Channel": "آگاه کردن کانال",
|
||||
"timeoutAfter": "تایم اوت بعد از {0} ثانیه",
|
||||
"FlashDuty Severity": "شدت",
|
||||
"nostrRelays": "ریلی Nostr",
|
||||
"nostrRelaysHelp": "یک آدرس ریلی به ازای هر خط",
|
||||
"nostrSender": "کلید محرمانه فرسته (nsec)",
|
||||
"gamedigGuessPort": "Gamedig: پورت پرسش",
|
||||
"styleElapsedTime": "زمان سپری شده زیر heartbeat bar",
|
||||
"styleElapsedTimeShowNoLine": "نمایش (بدون خط)",
|
||||
"styleElapsedTimeShowWithLine": "نمایش (با خط)",
|
||||
"filterActivePaused": "متوقف شده",
|
||||
"webhookBodyPresetOption": "از پیش تعیین شده - {0}",
|
||||
"webhookBodyCustomOption": "بدنه سفارشی",
|
||||
"selectedMonitorCount": "انتخاب شده: {0}",
|
||||
"Check/Uncheck": "انتخاب/ عدم انتخاب",
|
||||
"chromeExecutable": "فایل اجرایی کروم/کرومیوم",
|
||||
"chromeExecutableAutoDetect": "تشخیص خودکار",
|
||||
"chromeExecutableDescription": "برای کاربران داکر، اگر کرومیوم را هنوز نصب نکردید، ممکن است چند دقیقه زمان ببرد تا کرومیوم نصب و نتایج نشان داده شود. همچنین ممکن است تا 1 گیگابایت فضا بر روی سرور نیاز باشد.",
|
||||
"Select": "انتخاب",
|
||||
"aboutNotifyChannel": "آگاه کردن کانال یک نوتیفیکیشن دسکتاپ یا تلفن همراه را برای همه اعضای کانال راهاندازی میکند، خواه در دسترس بودن آنها فعال باشد یا غیرفعال.",
|
||||
"Server URL should not contain the nfty topic": "URL سرور نباید حاوی موضوع nfty باشد",
|
||||
"PushDeer Server": "سرور PushDeer",
|
||||
"pushDeerServerDescription": "برای استفاده از سرور رسمی خالی بگذارید",
|
||||
"twilioApiKey": "کلید API (اختیاری)",
|
||||
"Badge Duration (in hours)": "مدت نشان (در ساعت)",
|
||||
"Badge Preview": "پیش نمایش نشان",
|
||||
"Kafka Brokers": "بروکر Kafka",
|
||||
"Press Enter to add broker": "Enter را برای افزودن بروکر جدید فشار دهید",
|
||||
"Kafka Topic Name": "عنوان موضوع",
|
||||
"Kafka Producer Message": "پیام تهیه کننده",
|
||||
"Enable Kafka SSL": "فعال سازی SSL",
|
||||
"Kafka SASL Options": "گزینه های SASL",
|
||||
"Mechanism": "مکانیزم",
|
||||
"Pick a SASL Mechanism...": "یک مکانیزم SASL انتخاب کنید...",
|
||||
"Authorization Identity": "اطلاعات اعتبارسنجی",
|
||||
"AccessKey Id": "آیدی AccessKey",
|
||||
"Session Token": "توکن سشن",
|
||||
"Request Body": "بدنه پیام",
|
||||
"nostrRecipients": "کلید عمومی گیرنده (npub)",
|
||||
"nostrRecipientsHelp": "فرمت npub، یکی به ازای هر خط",
|
||||
"noOrBadCertificate": "بدون سرتیفیکت یا بد",
|
||||
"Invert Keyword": "کلمه کلیدی معکوس",
|
||||
"Expected Value": "مقدار مورد انتظار",
|
||||
"Json Query": "کوئری جیسون"
|
||||
}
|
||||
|
@@ -547,7 +547,6 @@
|
||||
"For safety, must use secret key": "Turvallisuuden vuoksi on käytettävä salaista avainta",
|
||||
"Device Token": "Laitteen tunnus",
|
||||
"Platform": "Alusta",
|
||||
"iOS": "iOS",
|
||||
"Bark Endpoint": "Bark päätepiste",
|
||||
"Huawei": "Huawei",
|
||||
"High": "Korkea",
|
||||
@@ -564,7 +563,6 @@
|
||||
"promosmsAllowLongSMS": "Salli pitkät tekstiviestit",
|
||||
"Feishu WebHookUrl": "Feishu WebHookURL-osoite",
|
||||
"Internal Room Id": "Huoneen sisäinen tunnus",
|
||||
"Android": "Android",
|
||||
"Channel Name": "Kanavan nimi",
|
||||
"Uptime Kuma URL": "Uptime Kuma URL-osoite",
|
||||
"Icon Emoji": "Ikoni Emoji",
|
||||
|
@@ -223,7 +223,7 @@
|
||||
"webhookJsonDesc": "{0} est bien pour tous les serveurs HTTP modernes comme Express.js",
|
||||
"webhookFormDataDesc": "{multipart} est bien pour du PHP. Le JSON aura besoin d'être parsé avec {decodeFunction}",
|
||||
"webhookAdditionalHeadersTitle": "En-têtes supplémentaires",
|
||||
"webhookAdditionalHeadersDesc": "Définit des en-têtes supplémentaires envoyés avec le webhook.",
|
||||
"webhookAdditionalHeadersDesc": "Définit des en-têtes supplémentaires envoyés avec le webhook. Chaque en-tête doit être défini comme une clé/valeur JSON.",
|
||||
"smtp": "Courriel (SMTP)",
|
||||
"secureOptionNone": "Aucun / STARTTLS (25, 587)",
|
||||
"secureOptionTLS": "TLS (465)",
|
||||
@@ -451,8 +451,6 @@
|
||||
"For safety, must use secret key": "Par sécurité, utilisation obligatoire de la clé secrète",
|
||||
"Device Token": "Jeton d'appareil",
|
||||
"Platform": "Plateforme",
|
||||
"iOS": "iOS",
|
||||
"Android": "Android",
|
||||
"Huawei": "Huawei",
|
||||
"High": "Haute",
|
||||
"Retry": "Recommencez",
|
||||
@@ -465,7 +463,7 @@
|
||||
"User": "Utilisateur",
|
||||
"Installed": "Installé",
|
||||
"Not installed": "Non installé",
|
||||
"Running": "Fonctionne",
|
||||
"Running": "En cours",
|
||||
"Not running": "Ne fonctionne pas",
|
||||
"Remove Token": "Supprimer le jeton",
|
||||
"Start": "Démarrer",
|
||||
@@ -652,7 +650,7 @@
|
||||
"Server Timezone": "Fuseau horaire du serveur",
|
||||
"statusPageMaintenanceEndDate": "Fin",
|
||||
"IconUrl": "URL vers l'icône",
|
||||
"Enable DNS Cache": "Activer le cache DNS",
|
||||
"Enable DNS Cache": "Activer le cache DNS pour les sondes HTTP(s)",
|
||||
"Enable": "Activer",
|
||||
"Disable": "Désactiver",
|
||||
"dnsCacheDescription": "Il peut ne pas fonctionner dans certains environnements IPv6, désactivez-le si vous rencontrez des problèmes.",
|
||||
@@ -743,7 +741,7 @@
|
||||
"twilioFromNumber": "Du Nombre",
|
||||
"twilioToNumber": "Au Nombre",
|
||||
"twilioAccountSID": "ID du compte",
|
||||
"twilioAuthToken": "Jeton d'authentification",
|
||||
"twilioAuthToken": "Jeton d'authentification / Clé secrète de l'API",
|
||||
"sameAsServerTimezone": "Identique au fuseau horaire du serveur",
|
||||
"startDateTime": "Date/heure de début",
|
||||
"endDateTime": "Date/heure de fin",
|
||||
@@ -758,8 +756,8 @@
|
||||
"Open Badge Generator": "Ouvrir le générateur de badges",
|
||||
"Badge Type": "Type de badge",
|
||||
"Badge Duration": "Durée du badge",
|
||||
"Badge Prefix": "Préfixe de badge",
|
||||
"Badge Suffix": "Suffixe de badge",
|
||||
"Badge Prefix": "Préfixe de la valeur du badge",
|
||||
"Badge Suffix": "Suffixe de la valeur du badge",
|
||||
"Badge Label Color": "Couleur de l'étiquette du badge",
|
||||
"Badge Color": "Couleur du badge",
|
||||
"Badge Label Prefix": "Préfixe d'étiquette de badge",
|
||||
@@ -784,5 +782,61 @@
|
||||
"Group": "Groupe",
|
||||
"Home": "Accueil",
|
||||
"noGroupMonitorMsg": "Pas disponible. Créez d'abord une sonde de groupe.",
|
||||
"Close": "Fermer"
|
||||
"Close": "Fermer",
|
||||
"chromeExecutableDescription": "Pour les utilisateurs sous Docker, si Chromium n'est pas encore installé, quelques minutes seront nécessaires pour installer et afficher le résultat du test. Cela peut prendre 1 Go d'espace disque.",
|
||||
"chromeExecutableAutoDetect": "Auto-détecter",
|
||||
"chromeExecutable": "Exécutable Chrome/Chromium",
|
||||
"Invert Keyword": "Inverser le mot-clé",
|
||||
"invertKeywordDescription": "Recherchez le mot-clé absent plutôt que présent.",
|
||||
"webhookCustomBodyDesc": "Définissez un corps HTTP personnalisé pour la requête. Les variables de modèle {msg}, {heartbeat}, {monitor} sont acceptées.",
|
||||
"webhookBodyCustomOption": "Corps personnalisé",
|
||||
"webhookBodyPresetOption": "Préréglages - {0}",
|
||||
"Request Body": "Corps de la requête",
|
||||
"twilioApiKey": "Clé API (facultatif)",
|
||||
"Expected Value": "Valeur attendue",
|
||||
"Json Query": "Requête Json",
|
||||
"jsonQueryDescription": "Faites une requête json contre la réponse et vérifiez la valeur attendue (la valeur de retour sera convertie en chaîne pour comparaison). Consultez <a href='https://jsonata.org/'>jsonata.org</a> pour la documentation sur le langage de requête. Une aire de jeux peut être trouvée <a href='https://try.jsonata.org/'>ici</a>.",
|
||||
"Badge Duration (in hours)": "Durée du badge (en heures)",
|
||||
"Badge Preview": "Aperçu du badge",
|
||||
"aboutNotifyChannel": "Notifier le canal déclenchera une notification de bureau ou mobile pour tous les membres du canal, que leur disponibilité soit active ou absente.",
|
||||
"Notify Channel": "Notifier le canal",
|
||||
"filterActive": "Actif",
|
||||
"filterActivePaused": "En pause",
|
||||
"Enter the list of brokers": "Entrez la liste des courtiers",
|
||||
"Press Enter to add broker": "Appuyez sur Entrée pour ajouter un courtier",
|
||||
"Kafka Topic Name": "Nom du sujet Kafka",
|
||||
"Enable Kafka SSL": "Activer Kafka SSL",
|
||||
"Kafka SASL Options": "Options de Kafka SAS",
|
||||
"Mechanism": "Mécanisme",
|
||||
"Pick a SASL Mechanism...": "Choisissez un mécanisme SASL...",
|
||||
"Authorization Identity": "Identité d'autorisation",
|
||||
"AccessKey Id": "ID de la clé d'accès",
|
||||
"Secret AccessKey": "Clé d'accès secrète",
|
||||
"Session Token": "Jeton de session",
|
||||
"Kafka Brokers": "Courtiers Kafka",
|
||||
"Kafka Producer Message": "Message du producteur Kafka",
|
||||
"Enable Kafka Producer Auto Topic Creation": "Activer la création automatique de rubrique Kafka",
|
||||
"tailscalePingWarning": "Afin d'utiliser la sonde Tailscale Ping, vous devez installer Uptime Kuma sans Docker et également installer le client Tailscale sur votre serveur.",
|
||||
"Server URL should not contain the nfty topic": "L'URL du serveur ne doit pas contenir le sujet nfty",
|
||||
"Select": "Sélectionner",
|
||||
"selectedMonitorCount": "Sélectionné : {0}",
|
||||
"Check/Uncheck": "Cocher/décocher",
|
||||
"nostrRelaysHelp": "Une URL relais par ligne",
|
||||
"nostrRecipients": "Clés publiques des bénéficiaires (npub)",
|
||||
"nostrSender": "Émetteur clé privée (nsec)",
|
||||
"nostrRecipientsHelp": "Format npub, un par ligne",
|
||||
"nostrRelays": "Relais Nostr",
|
||||
"PushDeer Server": "PushDeer Server",
|
||||
"showCertificateExpiry": "Afficher l'expiration du certificat",
|
||||
"noOrBadCertificate": "Pas/Mauvais certificat",
|
||||
"pushDeerServerDescription": "Laissez le champ vide pour utiliser le serveur officiel",
|
||||
"FlashDuty Severity": "Gravité",
|
||||
"wayToGetFlashDutyKey": "Vous pouvez aller dans Canal -> (Sélectionner un canal) -> Intégrations -> Ajouter une nouvelle page d'intégration, ajouter un \"événement personnalisé\" pour obtenir une adresse push, copier la clé d'intégration dans l'adresse. Pour plus d'informations, veuillez visiter",
|
||||
"Request Timeout": "Délai d'expiration de la demande",
|
||||
"timeoutAfter": "Délai dépassé après {0} secondes",
|
||||
"gamedigGuessPort": "Gamedig: Devinez le port",
|
||||
"gamedigGuessPortDescription": "Le port utilisé par Valve Server Query Protocol peut être différent du port client. Essayez ceci si la sonde ne peut pas se connecter à votre serveur.",
|
||||
"styleElapsedTimeShowNoLine": "Afficher (pas de ligne)",
|
||||
"styleElapsedTimeShowWithLine": "Afficher (avec ligne)",
|
||||
"styleElapsedTime": "Temps écoulé sous la barre d'état"
|
||||
}
|
||||
|
@@ -445,8 +445,6 @@
|
||||
"For safety, must use secret key": "לבטיחות, חייב להשתמש במפתח סודיy",
|
||||
"Device Token": "אסימון מכשיר",
|
||||
"Platform": "פּלַטפוֹרמָה",
|
||||
"iOS": "iOS",
|
||||
"Android": "דְמוּי אָדָם",
|
||||
"Huawei": "huawei",
|
||||
"High": "High",
|
||||
"Retry": "נסה שוב",
|
||||
@@ -741,5 +739,6 @@
|
||||
"pagertreeHigh": "גבוהה",
|
||||
"pagertreeCritical": "קריטי",
|
||||
"pagertreeResolve": "הגדרה אוטומטית",
|
||||
"ntfyUsernameAndPassword": "שם משתמש וסיסמא"
|
||||
"ntfyUsernameAndPassword": "שם משתמש וסיסמא",
|
||||
"Cannot connect to the socket server": "לא ניתן להתחבר לשקע השרת"
|
||||
}
|
||||
|
@@ -39,5 +39,6 @@
|
||||
"Reconnecting...": "पुनः कनेक्ट किया जा रहा है...",
|
||||
"Down": "बंद",
|
||||
"Passive Monitor Type": "निष्क्रिय मॉनिटर प्रकार",
|
||||
"Status": "स्थिति"
|
||||
"Status": "स्थिति",
|
||||
"showCertificateExpiry": "प्रमाणपत्र समाप्ति दिखाएँ"
|
||||
}
|
||||
|
@@ -113,7 +113,7 @@
|
||||
"Password": "Lozinka",
|
||||
"Remember me": "Zapamti me",
|
||||
"Login": "Prijava",
|
||||
"No Monitors, please": "Nema monitora, ",
|
||||
"No Monitors, please": "Nema monitora,",
|
||||
"add one": "dodaj jedan",
|
||||
"Notification Type": "Tip obavijesti",
|
||||
"Email": "E-pošta",
|
||||
@@ -420,8 +420,6 @@
|
||||
"For safety, must use secret key": "Korištenje tajnog ključa je obavezno",
|
||||
"Device Token": "Token uređaja",
|
||||
"Platform": "Platforma",
|
||||
"iOS": "iOS",
|
||||
"Android": "Android",
|
||||
"Huawei": "Huawei",
|
||||
"High": "Visoko",
|
||||
"Retry": "Ponovnih pokušaja",
|
||||
|
@@ -11,13 +11,13 @@
|
||||
"acceptedStatusCodesDescription": "Pilih kode status yang dianggap sebagai tanggapan yang berhasil.",
|
||||
"passwordNotMatchMsg": "Kata sandi kedua tidak cocok.",
|
||||
"notificationDescription": "Harap atur notifikasi ke monitor agar berfungsi.",
|
||||
"keywordDescription": "Cari kata kunci dalam code html atau JSON huruf besar-kecil berpengaruh",
|
||||
"keywordDescription": "Kata kunci pencarian dalam HTML biasa atau respons JSON. Pencarian bersifat peka terhadap huruf besar/kecil.",
|
||||
"pauseDashboardHome": "Jeda",
|
||||
"deleteMonitorMsg": "Apakah Anda mau menghapus monitor ini?",
|
||||
"deleteNotificationMsg": "Apakah Anda mau menghapus notifikasi untuk semua monitor?",
|
||||
"dnsPortDescription": "Port server DNS. Bawaan menggunakan 53. Anda dapat mengubah port kapan saja.",
|
||||
"resolverserverDescription": "Cloudflare adalah server bawaan, Anda dapat mengubah server resolver kapan saja.",
|
||||
"rrtypeDescription": "Pilih RR-Type yang mau Anda monitor",
|
||||
"rrtypeDescription": "Pilih RR Type yang mau Anda monitor",
|
||||
"pauseMonitorMsg": "Apakah Anda yakin mau menjeda?",
|
||||
"enableDefaultNotificationDescription": "Untuk setiap monitor baru, notifikasi ini akan diaktifkan secara bawaan. Anda masih dapat menonaktifkan notifikasi secara terpisah untuk setiap monitor.",
|
||||
"clearEventsMsg": "Apakah Anda yakin mau menghapus semua event di monitor ini?",
|
||||
@@ -25,13 +25,13 @@
|
||||
"confirmClearStatisticsMsg": "Apakah Anda yakin mau menghapus semua statistik?",
|
||||
"importHandleDescription": "Pilih 'Lewati yang ada' jika Anda ingin melewati setiap monitor atau notifikasi dengan nama yang sama. 'Timpa' akan menghapus setiap monitor dan notifikasi yang ada.",
|
||||
"confirmImportMsg": "Apakah Anda yakin untuk mengimpor cadangan? Pastikan Anda telah memilih opsi impor yang tepat.",
|
||||
"twoFAVerifyLabel": "Silakan ketik token Anda untuk memverifikasi bahwa 2FA berfungsi",
|
||||
"twoFAVerifyLabel": "Masukkan token Anda untuk memverifikasi 2FA:",
|
||||
"tokenValidSettingsMsg": "Token benar! Anda sekarang dapat menyimpan pengaturan 2FA.",
|
||||
"confirmEnableTwoFAMsg": "Apakah Anda yakin ingin mengaktifkan 2FA?",
|
||||
"confirmDisableTwoFAMsg": "Apakah Anda yakin ingin menonaktifkan 2FA?",
|
||||
"Settings": "Pengaturan",
|
||||
"Dashboard": "Dasbor",
|
||||
"New Update": "Pembaruan Baru",
|
||||
"New Update": "Update terbaru",
|
||||
"Language": "Bahasa",
|
||||
"Appearance": "Tampilan",
|
||||
"Theme": "Tema",
|
||||
@@ -58,7 +58,7 @@
|
||||
"Delete": "Hapus",
|
||||
"Current": "Saat ini",
|
||||
"Uptime": "Waktu aktif",
|
||||
"Cert Exp.": "Batas kedaluwarsa SSL",
|
||||
"Cert Exp.": "Sertifikat Kedaluwarsa.",
|
||||
"day": "hari | hari-hari",
|
||||
"-day": "-hari",
|
||||
"hour": "Jam",
|
||||
@@ -69,9 +69,9 @@
|
||||
"Keyword": "Kata Kunci",
|
||||
"Friendly Name": "Nama yang Ramah",
|
||||
"URL": "URL",
|
||||
"Hostname": "Hostname",
|
||||
"Hostname": "Nama host",
|
||||
"Port": "Port",
|
||||
"Heartbeat Interval": "Jarak Waktu Heartbeat",
|
||||
"Heartbeat Interval": "Rentang Waktu Heartbeat",
|
||||
"Retries": "Coba lagi",
|
||||
"Heartbeat Retry Interval": "Jeda Pengulangan Heartbeat",
|
||||
"Resend Notification if Down X times consecutively": "Kirim Ulang Notifikasi jika Tidak Aktif X kali",
|
||||
@@ -106,7 +106,7 @@
|
||||
"Enable Auth": "Aktifkan Autentikasi",
|
||||
"disableauth.message1": "Apakah Anda yakin ingin <strong>menonaktifkan autentikasi</strong>?",
|
||||
"disableauth.message2": "Ini untuk <strong>mereka yang memiliki autentikasi pihak ketiga</strong> diletakkan di depan Uptime Kuma, misalnya akses Cloudflare.",
|
||||
"Please use this option carefully!": "Gunakan dengan hati-hati.",
|
||||
"Please use this option carefully!": "Silahkan gunakan opsi ini dengan hati-hati!",
|
||||
"Logout": "Keluar",
|
||||
"Leave": "Pergi",
|
||||
"I understand, please disable": "Saya mengerti, silakan dinonaktifkan",
|
||||
@@ -117,14 +117,14 @@
|
||||
"Password": "Sandi",
|
||||
"Remember me": "Ingat saya",
|
||||
"Login": "Masuk",
|
||||
"No Monitors, please": "Tidak ada monitor, silakan",
|
||||
"No Monitors, please": "Tolong, jangan ada Monitor",
|
||||
"add one": "tambahkan satu",
|
||||
"Notification Type": "Tipe Notifikasi",
|
||||
"Email": "Surel",
|
||||
"Test": "Tes",
|
||||
"Certificate Info": "Info Sertifikasi",
|
||||
"Resolver Server": "Resolver Server",
|
||||
"Resource Record Type": "Resource Record Type",
|
||||
"Resource Record Type": "Jenis Rekam Sumber Daya",
|
||||
"Last Result": "Hasil Terakhir",
|
||||
"Create your admin account": "Buat akun admin Anda",
|
||||
"Repeat Password": "Ulangi Sandi",
|
||||
@@ -162,7 +162,7 @@
|
||||
"Token": "Token",
|
||||
"Show URI": "Lihat URI",
|
||||
"Tags": "Tanda",
|
||||
"Add New below or Select...": "Tambahkan Baru di bawah atau Pilih...",
|
||||
"Add New below or Select...": "Tambahkan Baru di bawah atau Pilih…",
|
||||
"Tag with this name already exist.": "Tanda dengan nama ini sudah ada.",
|
||||
"Tag with this value already exist.": "Tanda dengan nilai ini sudah ada.",
|
||||
"color": "warna",
|
||||
@@ -175,7 +175,7 @@
|
||||
"Indigo": "Biru Tua",
|
||||
"Purple": "Ungu",
|
||||
"Pink": "Merah Muda",
|
||||
"Search...": "Cari...",
|
||||
"Search...": "Cari…",
|
||||
"Avg. Ping": "Rata-rata Ping",
|
||||
"Avg. Response": "Rata-rata Tanggapan",
|
||||
"Entry Page": "Halaman Masuk",
|
||||
@@ -194,7 +194,7 @@
|
||||
"here": "di sini",
|
||||
"Required": "Wajib",
|
||||
"telegram": "Telegram",
|
||||
"Bot Token": "Bot Token",
|
||||
"Bot Token": "Token Bot",
|
||||
"wayToGetTelegramToken": "Anda dapat mendapatkan token dari {0}.",
|
||||
"Chat ID": "Chat ID",
|
||||
"supportTelegramChatID": "Mendukung Obrolan Langsung / Grup / Channel Chat ID",
|
||||
@@ -216,13 +216,13 @@
|
||||
"smtpCC": "CC",
|
||||
"smtpBCC": "BCC",
|
||||
"discord": "Discord",
|
||||
"Discord Webhook URL": "Discord Webhook URL",
|
||||
"wayToGetDiscordURL": "Anda bisa mendapatkan ini dengan pergi ke Server Pengaturan -> Integrasi -> Buat Webhook",
|
||||
"Discord Webhook URL": "URL Webhook Discord",
|
||||
"wayToGetDiscordURL": "Anda bisa mendapatkan ini dengan pergi ke Server Pengaturan -> Integrasi -> Lihat Webhooks -> Buat Webhook",
|
||||
"Bot Display Name": "Nama Bot",
|
||||
"Prefix Custom Message": "Awalan Pesan",
|
||||
"Hello @everyone is...": "Halo {'@'}everyone is...",
|
||||
"Hello @everyone is...": "Halo {'@'}everyone is…",
|
||||
"teams": "Microsoft Teams",
|
||||
"Webhook URL": "Webhook URL",
|
||||
"Webhook URL": "URL Webhook",
|
||||
"wayToGetTeamsURL": "Anda dapat mempelajari cara membuat url webhook {0}.",
|
||||
"signal": "Sinyal",
|
||||
"Number": "Nomer",
|
||||
@@ -285,7 +285,7 @@
|
||||
"lineDevConsoleTo": "Konsol Pengembang Line - {0}",
|
||||
"Basic Settings": "Pengaturan Dasar",
|
||||
"User ID": "ID User",
|
||||
"Messaging API": "Messaging API",
|
||||
"Messaging API": "API Messaging",
|
||||
"wayToGetLineChannelToken": "Pertama akses {0}, buat penyedia dan saluran (Messaging API), lalu Anda bisa mendapatkan token akses saluran dan id pengguna dari item menu yang disebutkan di atas.",
|
||||
"Icon URL": "Icon URL",
|
||||
"aboutIconURL": "Anda dapat memberikan tautan ke gambar di \"Icon URL\" untuk mengganti gambar profil bawaan. Tidak akan digunakan jika Ikon Emoji diset.",
|
||||
@@ -293,7 +293,7 @@
|
||||
"matrix": "Matrix",
|
||||
"promosmsTypeEco": "SMS ECO - murah tapi lambat dan sering kelebihan beban. Terbatas hanya untuk penerima Polandia.",
|
||||
"promosmsTypeFlash": "SMS FLASH - Pesan akan otomatis muncul di perangkat penerima. Terbatas hanya untuk penerima Polandia.",
|
||||
"promosmsTypeFull": "SMS FULL - SMS tingkat premium, Anda dapat menggunakan Nama Pengirim Anda (Anda harus mendaftarkan nama terlebih dahulu). Dapat diandalkan untuk peringatan.",
|
||||
"promosmsTypeFull": "SMS FULL - Tingkat Premium SMS, Anda dapat menggunakan Nama Pengirim Anda (Nama Anda harus didaftarkan terlebih dahulu). Dapat diandalkan untuk peringatan.",
|
||||
"promosmsTypeSpeed": "SMS SPEED - Prioritas tertinggi dalam sistem. Sangat cepat dan dapat diandalkan tetapi mahal (sekitar dua kali lipat dari harga SMS FULL).",
|
||||
"promosmsPhoneNumber": "Nomor telepon (untuk penerima Polandia Anda dapat melewati kode area)",
|
||||
"promosmsSMSSender": "Nama Pengirim SMS : Nama pra-registrasi atau salah satu bawaan: InfoSMS, Info SMS, MaxSMS, INFO, SMS",
|
||||
@@ -302,7 +302,7 @@
|
||||
"Internal Room Id": "Internal Room ID",
|
||||
"matrixDesc1": "Kamu dapat menemukan Internal Room ID dengan melihat di bagian konfigurasi ruang di Matrix. Seharusnya berbentuk seperti !QMdRCpUIfLwsfjxye6:home.server.",
|
||||
"matrixDesc2": "Sangat direkomendasikan kepada Anda untuk membuat akun baru dan jangan menggunakan token atas akun terkini yang memiliki token akses secara penuh terhadap akun dan seluruh ruang yang terdaftar. Alih - alih, buat akun baru dan undang akun tsb ke ruang tempat anda ingin menerima notifikasi. Untuk mendapatkan token akses anda dapat menjalankan {0}",
|
||||
"Method": "Method",
|
||||
"Method": "Metode",
|
||||
"Body": "Body",
|
||||
"Headers": "Headers",
|
||||
"PushUrl": "Push URL",
|
||||
@@ -315,18 +315,18 @@
|
||||
"One record": "Satu catatan",
|
||||
"steamApiKeyDescription": "Untuk monitoring Steam Game Server Anda membutuhkan kunci Steam Web-API. Anda dapat mendaftarkan Kunci API Anda melalui: ",
|
||||
"Current User": "Pengguna Saat Ini",
|
||||
"topic": "Topic",
|
||||
"topicExplanation": "MQTT topic untuk dimonitor",
|
||||
"topic": "topik",
|
||||
"topicExplanation": "MQTT topik untuk dimonitor",
|
||||
"successMessage": "Pesan Berhasil",
|
||||
"successMessageExplanation": "Pesan MQTT yang akan dianggap berhasil",
|
||||
"recent": "Baru saja",
|
||||
"Done": "Selesai",
|
||||
"Info": "Info",
|
||||
"Security": "Keamanan",
|
||||
"Steam API Key": "Steam API Key",
|
||||
"Shrink Database": "Shrink Database",
|
||||
"Pick a RR-Type...": "Pilih RR-Type...",
|
||||
"Pick Accepted Status Codes...": "Pilih Kode Status yang Diterima...",
|
||||
"Steam API Key": "Kunci API Steam",
|
||||
"Shrink Database": "Kecilkan Database",
|
||||
"Pick a RR-Type...": "Pilih RR-Type…",
|
||||
"Pick Accepted Status Codes...": "Pilih Kode Status yang Diterima…",
|
||||
"Default": "Default",
|
||||
"HTTP Options": "Opsi HTTP",
|
||||
"Create Incident": "Buat Incident",
|
||||
@@ -373,8 +373,8 @@
|
||||
"smtpDkimDesc": "Silakan merujuk ke Nodemailer DKIM {0} untuk penggunaan.",
|
||||
"documentation": "dokumentasi",
|
||||
"smtpDkimDomain": "Nama Domain",
|
||||
"smtpDkimKeySelector": "Key Selector",
|
||||
"smtpDkimPrivateKey": "Private Key",
|
||||
"smtpDkimKeySelector": "Selektor Kunci",
|
||||
"smtpDkimPrivateKey": "Kunci Pribadi",
|
||||
"smtpDkimHashAlgo": "Algoritma Hash (Opsional)",
|
||||
"smtpDkimheaderFieldNames": "Header Keys untuk ditambahkan (Optional)",
|
||||
"smtpDkimskipFields": "Header Keys not untuk ditambahkan (Optional)",
|
||||
@@ -401,8 +401,8 @@
|
||||
"proxyDescription": "Proxy harus ditambahkan ke monitor agar berfungsi.",
|
||||
"enableProxyDescription": "Proxy berikut tidak akan berdampak ke monitor hingga diaktifkan. Anda dapat mengontrol menonaktifkan sementara proxy dari semua monitor dengan status aktivasi.",
|
||||
"setAsDefaultProxyDescription": "Proxy berikut akan diaktifkan sebagai bawaan untuk monitor baru. Anda masih dapat menonaktifkan proxy secara terpisah untuk setiap monitor.",
|
||||
"Certificate Chain": "Certificate Chain",
|
||||
"Valid": "Valid",
|
||||
"Certificate Chain": "Rangkaian Sertifikat",
|
||||
"Valid": "Berlaku",
|
||||
"Invalid": "Tidak Valid",
|
||||
"AccessKeyId": "AccessKey ID",
|
||||
"SecretAccessKey": "AccessKey Secret",
|
||||
@@ -418,8 +418,6 @@
|
||||
"For safety, must use secret key": "Untuk keamaan Anda harus menggunakan kunci rahasia",
|
||||
"Device Token": "Token Perangkat",
|
||||
"Platform": "Platform",
|
||||
"iOS": "iOS",
|
||||
"Android": "Android",
|
||||
"Huawei": "Huawei",
|
||||
"High": "Tinggi",
|
||||
"Retry": "Ulang",
|
||||
@@ -447,7 +445,7 @@
|
||||
"The slug is already taken. Please choose another slug.": "Slug telah digunakan. Silakan pilih slug lain.",
|
||||
"No Proxy": "Tidak ada Proxy",
|
||||
"Authentication": "Autentikasi",
|
||||
"HTTP Basic Auth": "HTTP Basic Auth",
|
||||
"HTTP Basic Auth": "Autentikasi Dasar HTTP",
|
||||
"New Status Page": "Halaman Status Baru",
|
||||
"Page Not Found": "Halaman Tidak Ditemukan",
|
||||
"Reverse Proxy": "Proxy Terbalik",
|
||||
@@ -458,7 +456,7 @@
|
||||
"Message:": "Pesan:",
|
||||
"Don't know how to get the token? Please read the guide:": "Tidak tahu cara mendapatkan token? Silakan baca panduannya:",
|
||||
"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.": "Koneksi saat ini mungkin hilang jika Anda saat ini terhubung melalui Cloudflare Tunel. Apakah Anda yakin ingin menghentikannya? Ketik kata sandi Anda saat ini untuk mengonfirmasinya.",
|
||||
"HTTP Headers": "HTTP Headers",
|
||||
"HTTP Headers": "Header HTTP",
|
||||
"Trust Proxy": "Proxy Terpercaya",
|
||||
"Other Software": "Perangkat Lunak lainnya",
|
||||
"For example: nginx, Apache and Traefik.": "Sebagai contoh: nginx, Apache and Traefik.",
|
||||
@@ -512,12 +510,12 @@
|
||||
"pushoversounds cosmic": "Cosmic",
|
||||
"pushoversounds falling": "Falling",
|
||||
"pushoversounds gamelan": "Gamelan",
|
||||
"pushoversounds incoming": "Incoming",
|
||||
"pushoversounds intermission": "Intermission",
|
||||
"pushoversounds incoming": "Masuk",
|
||||
"pushoversounds intermission": "Jeda",
|
||||
"pushoversounds magic": "Magic",
|
||||
"pushoversounds mechanical": "Mechanical",
|
||||
"pushoversounds mechanical": "Mekanik",
|
||||
"pushoversounds pianobar": "Piano Bar",
|
||||
"pushoversounds siren": "Siren",
|
||||
"pushoversounds siren": "Sirene",
|
||||
"pushoversounds spacealarm": "Space Alarm",
|
||||
"pushoversounds tugboat": "Tug Boat",
|
||||
"pushoversounds alien": "Alien Alarm (long)",
|
||||
@@ -553,17 +551,17 @@
|
||||
"socket": "Socket",
|
||||
"tcp": "TCP / HTTP",
|
||||
"Docker Container": "Docker Container",
|
||||
"Container Name / ID": "Container Name / ID",
|
||||
"Docker Host": "Docker Host",
|
||||
"Docker Hosts": "Docker Hosts",
|
||||
"Container Name / ID": "Nama / ID Container",
|
||||
"Docker Host": "Host Docker",
|
||||
"Docker Hosts": "Hosts Docker",
|
||||
"ntfy Topic": "ntfy Topic",
|
||||
"Domain": "Domain",
|
||||
"Workstation": "Workstation",
|
||||
"disableCloudflaredNoAuthMsg": "Anda berada dalam mode Tanpa Otentikasi, kata sandi tidak diperlukan.",
|
||||
"trustProxyDescription": "Trust 'X-Forwarded-*' headers. Jika Anda ingin mendapatkan IP klien yang benar dan Uptime Kuma Anda dibalik layanan seperti Nginxor Apache, Anda harus mengaktifkan ini.",
|
||||
"trustProxyDescription": "Trust 'X-Forwarded-*' headers. Jika Anda ingin mendapatkan IP klien yang benar dan Uptime Kuma Anda dibalik proxy seperti Nginx or Apache, Anda harus mengaktifkan ini.",
|
||||
"wayToGetLineNotifyToken": "Anda bisa mendapatkan token akses dari {0}",
|
||||
"Examples": "Contoh",
|
||||
"Home Assistant URL": "Home Assistant URL",
|
||||
"Home Assistant URL": "URL Home Asisten",
|
||||
"Long-Lived Access Token": "Token Akses Berumur Panjang",
|
||||
"Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "Token Akses Berumur Panjang dapat dibuat dengan mengklik nama profil Anda (kiri bawah) dan menggulir ke bawah lalu klik Buat Token. ",
|
||||
"Notification Service": "Layanan Pemberitahuan",
|
||||
@@ -580,15 +578,215 @@
|
||||
"goAlertInfo": "GoAlert adalah aplikasi open source untuk penjadwalan panggilan, eskalasi otomatis dan pemberitahuan (seperti SMS atau panggilan suara). Secara otomatis melibatkan orang yang tepat, dengan cara yang benar, dan pada waktu yang tepat! {0}",
|
||||
"goAlertIntegrationKeyInfo": "Dapatkan kunci integrasi API generik untuk layanan dalam format ini \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\" biasanya nilai parameter token dari URL yang disalin.",
|
||||
"goAlert": "GoAlert",
|
||||
"backupOutdatedWarning": "Tidak digunakan lagi: Karena banyak fitur ditambahkan dan fitur cadangan ini agak tidak terawat, itu tidak dapat menghasilkan atau memulihkan cadangan lengkap.",
|
||||
"backupRecommend": "Harap cadangkan volume atau folder data (./data/) secara langsung.",
|
||||
"backupOutdatedWarning": "Tidak digunakan lagi: Karena banyak fitur ditambahkan dan fitur pencadangan ini agak tidak terpelihara, fitur ini tidak dapat menghasilkan atau memulihkan cadangan lengkap.",
|
||||
"backupRecommend": "Silahkan backup volume atau folder (./data/) secara langsung.",
|
||||
"Help": "Bantuan",
|
||||
"Game": "Gim/Permainan",
|
||||
"Game": "Permainan",
|
||||
"markdownSupported": "Dukungan sintaks markdown",
|
||||
"statusMaintenance": "Pemeliharaan",
|
||||
"Maintenance": "Pemeliharaan",
|
||||
"General Monitor Type": "Tipe Monitor Umum",
|
||||
"Passive Monitor Type": "Tipe Monitor Pasif",
|
||||
"Specific Monitor Type": "Tipe Monitor Spesifik",
|
||||
"Monitor": "Monitor"
|
||||
"Monitor": "Monitor",
|
||||
"Guild ID": "ID Guild",
|
||||
"twilioAccountSID": "SID akun",
|
||||
"twilioAuthToken": "Token Autentikasi",
|
||||
"ntfyAuthenticationMethod": "Metode Autentikasi",
|
||||
"ntfyUsernameAndPassword": "Nama pengguna dan kata sandi",
|
||||
"Add Another": "Tambah Lainnya",
|
||||
"Key Added": "Kunci Ditambahkan",
|
||||
"Google Analytics ID": "ID Google Analytics",
|
||||
"pagertreeIntegrationUrl": "URL integrasi",
|
||||
"pagertreeUrgency": "Darurat",
|
||||
"Home": "Beranda",
|
||||
"startDateTime": "Tanggal/Waktu Mulai",
|
||||
"Recurring": "Berulang",
|
||||
"strategyManual": "Aktif/TidakAktif Secara Manual",
|
||||
"infiniteRetention": "Setel ke 0 untuk retensi tak terbatas.",
|
||||
"enableGRPCTls": "Izinkan untuk mengirim permintaan gRPC dengan koneksi TLS",
|
||||
"grpcMethodDescription": "Nama metode dikonversi ke format cammelCase seperti sayHello, check, dll.",
|
||||
"deleteMaintenanceMsg": "Apakah Anda yakin ingin menghapus pemeliharaan ini?",
|
||||
"Free Mobile API Key": "Kunci API Seluler Gratis",
|
||||
"Enable TLS": "Aktifkan TLS",
|
||||
"Proto Method": "Metode Proto",
|
||||
"Proto Content": "Konten Proto",
|
||||
"Economy": "Ekonomi",
|
||||
"Free Mobile User Identifier": "Pengidentifikasi Pengguna Seluler",
|
||||
"Proto Service Name": "Nama Layanan Proto",
|
||||
"SMSManager API Docs": "Dokumen API SMSManager ",
|
||||
"Expiry date": "Tanggal kadaluarsa",
|
||||
"No API Keys": "Tidak ada Kunci API",
|
||||
"Expires": "Berakhir",
|
||||
"pagertreeCritical": "Penting",
|
||||
"pagertreeResolve": "Penyelesaian Otomatis",
|
||||
"lunaseaDeviceID": "ID perangkat",
|
||||
"lunaseaUserID": "ID Pengguna",
|
||||
"twilioFromNumber": "Dari Nomor",
|
||||
"twilioToNumber": "Ke Nomor",
|
||||
"Badge Generator": "Pembuat Lencana {0}",
|
||||
"Badge Duration": "Durasi Lencana",
|
||||
"Badge Label": "Label Lencana",
|
||||
"Badge Prefix": "Prefiks Lencana",
|
||||
"Badge Suffix": "Suffix Lencana",
|
||||
"Badge Label Color": "Warna Label Lencana",
|
||||
"Badge Color": "Warna Lencana",
|
||||
"Badge Label Prefix": "Prefiks Label Lencana",
|
||||
"telegramSendSilently": "Kirim Secara Senyap",
|
||||
"Invert Keyword": "Balikkan Kata Kunci",
|
||||
"Pick Affected Monitors...": "Pilih Monitor yang Terkena Dampak…",
|
||||
"Badge Label Suffix": "Suffix Label Lencana",
|
||||
"statusPageMaintenanceEndDate": "berakhir",
|
||||
"Add API Key": "Tambahkan Kunci API",
|
||||
"apiKey-expired": "Kedaluwarsa",
|
||||
"apiKey-active": "Aktif",
|
||||
"apiKey-inactive": "Tidak aktif",
|
||||
"Monitor Setting": "Pengaturan Pemantauan {0}",
|
||||
"Show Clickable Link": "Tampilkan Tautan yang Dapat Diklik",
|
||||
"Badge Type": "Tipe lencana",
|
||||
"confirmDeleteTagMsg": "Yakin ingin menghapus tag ini? Monitor yang terkait dengan tag ini tidak akan dihapus.",
|
||||
"Gateway Type": "tipe Gateway",
|
||||
"Don't expire": "Jangan sampai kadaluarsa",
|
||||
"apiKeyAddedMsg": "Kunci API Anda telah ditambahkan. Mohon dicatat karena tidak akan ditampilkan lagi.",
|
||||
"disableAPIKeyMsg": "Yakin ingin menonaktifkan kunci API ini?",
|
||||
"pagertreeSilent": "Bisu",
|
||||
"pagertreeLow": "Rendah",
|
||||
"pagertreeDoNothing": "Jangan Lakukan Apa-apa",
|
||||
"wayToGetPagerTreeIntegrationURL": "Setelah membuat integrasi Uptime Kuma di PagerTree, salin Endpoint. Lihat detail lengkap {0}",
|
||||
"lunaseaTarget": "Sasaran",
|
||||
"Show Clickable Link Description": "Jika dicentang, setiap orang yang memiliki akses ke halaman status ini dapat memiliki akses ke URL monitor.",
|
||||
"Open Badge Generator": "Buka Pembuat Lencana",
|
||||
"Cannot connect to the socket server": "Tidak dapat terhubung ke server soket",
|
||||
"Reconnecting...": "Menghubungkan ulang...",
|
||||
"deleteAPIKeyMsg": "Apakah Anda yakin ingin menghapus kunci API ini?",
|
||||
"Generate": "Hasilkan",
|
||||
"pagertreeMedium": "Sedang",
|
||||
"pagertreeHigh": "Tinggi",
|
||||
"Group": "Grup",
|
||||
"Body Encoding": "Body Encoding",
|
||||
"Add New Tag": "Tambahkan Tag Baru",
|
||||
"chromeExecutableDescription": "Untuk pengguna Docker, jika Chromium belum diinstal, mungkin perlu waktu beberapa menit untuk menginstal dan menampilkan hasil pengujian. Dibutuhkan 1GB ruang penyimpanan.",
|
||||
"recurringIntervalMessage": "Jalankan sekali setiap hari | Jalankan sekali setiap {0} hari",
|
||||
"wayToGetKookBotToken": "Buat aplikasi dan dapatkan token bot Anda di {0}",
|
||||
"Custom Monitor Type": "Tipe Monitor Khusus",
|
||||
"API Keys": "Kunci API",
|
||||
"Expiry": "Kadaluarsa",
|
||||
"noGroupMonitorMsg": "Tidak tersedia. Buat Monitor Grup Terlebih Dahulu.",
|
||||
"Close": "Tutup",
|
||||
"telegramMessageThreadID": "(Opsional) ID Pesan",
|
||||
"Date and Time": "Tanggal dan waktu",
|
||||
"Single Maintenance Window": "Jendela Pemeliharaan Tunggal",
|
||||
"wayToGetZohoCliqURL": "Anda dapat mempelajari cara membuat URL webhook {0}.",
|
||||
"dayOfWeek": "Hari dalam seminggu",
|
||||
"dayOfMonth": "Hari dalam Bulan",
|
||||
"lastDay": "Hari terakhir",
|
||||
"Clone Monitor": "Klon Monitor",
|
||||
"Clone": "Klon",
|
||||
"Server Address": "Alamat server",
|
||||
"Edit Tag": "Sunting Tag",
|
||||
"smseagleTo": "Nomor telepon",
|
||||
"maintenanceStatus-under-maintenance": "Dalam perbaikan",
|
||||
"webhookAdditionalHeadersDesc": "Menetapkan header tambahan yang dikirim dengan webhook. Setiap header harus didefinisikan sebagai kunci/nilai JSON.",
|
||||
"webhookCustomBodyDesc": "Tentukan Body HTTP khusus untuk permintaan tersebut. Variabel template {msg}, {heartbeat}, {monitor} yang diterima.",
|
||||
"webhookBodyPresetOption": "Prasetel - {0}",
|
||||
"webhookBodyCustomOption": "Body Kustom",
|
||||
"Packet Size": "Ukuran Paket",
|
||||
"telegramMessageThreadIDDescription": "Pengidentifikasi unik Opsional untuk pesan target (topik) forum; untuk forum supergrup saja",
|
||||
"telegramProtectContent": "Lindungi Forwarding/Saving",
|
||||
"or": "atau",
|
||||
"sameAsServerTimezone": "Sama seperti Zona Waktu Server",
|
||||
"endDateTime": "Tanggal/Waktu Berakhir",
|
||||
"cronExpression": "Ekspresi Cron",
|
||||
"cronSchedule": "Jadwal: ",
|
||||
"invalidCronExpression": "Ekspresi Cron Tidak Valid: {0}",
|
||||
"recurringInterval": "Selang waktu",
|
||||
"warningTimezone": "Itu menggunakan zona waktu server",
|
||||
"weekdayShortMon": "Senin",
|
||||
"weekdayShortTue": "Selasa",
|
||||
"weekdayShortWed": "Rabu",
|
||||
"weekdayShortThu": "Kamis",
|
||||
"weekdayShortFri": "Jum'at",
|
||||
"weekdayShortSat": "Sabtu",
|
||||
"weekdayShortSun": "Minggu",
|
||||
"lastDay1": "Hari Terakhir dalam Sebulan",
|
||||
"lastDay2": "2 Hari Terakhir Bulan Ini",
|
||||
"lastDay3": "3 Hari Terakhir Bulan Ini",
|
||||
"lastDay4": "4 Hari Terakhir Bulan Ini",
|
||||
"No Maintenance": "Tidak Ada Pemeliharaan",
|
||||
"pauseMaintenanceMsg": "Anda yakin ingin menjeda?",
|
||||
"maintenanceStatus-inactive": "Tidak aktif",
|
||||
"Display Timezone": "Tampilkan Zona Waktu",
|
||||
"IconUrl": "URL ikon",
|
||||
"Enable DNS Cache": "Aktifkan Cache DNS",
|
||||
"Enable": "Aktifkan",
|
||||
"Disable": "Nonaktifkan",
|
||||
"affectedStatusPages": "Tampilkan pesan pemeliharaan ini pada halaman status yang dipilih",
|
||||
"invertKeywordDescription": "Carilah kata kunci untuk menjadi tidak ada daripada hadir.",
|
||||
"wayToGetKookGuildID": "Aktifkan 'Mode Pengembang' di pengaturan Kook, dan klik kanan guild untuk mendapatkan ID-nya",
|
||||
"Strategy": "Strategi",
|
||||
"high": "tinggi",
|
||||
"SendKey": "SendKey",
|
||||
"Lowcost": "rendah",
|
||||
"smseagleContact": "nama kontak buku telepon",
|
||||
"smseagleRecipient": "Penerima (jika banyak harus dipisahkan dengan koma)",
|
||||
"smseagleEncoding": "Kirim sebagai Unicode",
|
||||
"smseaglePriority": "Prioritas pesan (0-9, default = 0)",
|
||||
"Learn More": "Pelajari lebih lanjut",
|
||||
"Badge Up Color": "Warna atas Lencana",
|
||||
"Badge Maintenance Color": "Warna Lencana Pemeliharaan",
|
||||
"Badge Warn Color": "Warna Lencana Peringatan",
|
||||
"Request Body": "Permintaan Body",
|
||||
"uninstalling": "Menghapus instalan",
|
||||
"notificationRegional": "Daerah",
|
||||
"atLeastOneMonitor": "Pilih setidaknya satu monitor yang terpengaruh",
|
||||
"pushoverMessageTtl": "TTL pesan (Detik)",
|
||||
"smseagleGroup": "Nama grup buku telepon",
|
||||
"smseagleRecipientType": "Tipe Penerima",
|
||||
"smseagleToken": "Token Akses API",
|
||||
"smseagleUrl": "URL perangkat SMSEagle Anda",
|
||||
"Schedule maintenance": "Jadwalkan pemeliharaan",
|
||||
"Affected Monitors": "Monitor yang Terpengaruh",
|
||||
"Start of maintenance": "Mulai pemeliharaan",
|
||||
"All Status Pages": "Semua Halaman Status",
|
||||
"Select status pages...": "Pilih halaman status…",
|
||||
"Custom": "Khusus",
|
||||
"Optional": "Opsional",
|
||||
"dnsCacheDescription": "Ini mungkin tidak berfungsi di beberapa lingkungan IPv6, nonaktifkan jika Anda mengalami masalah.",
|
||||
"Maintenance Time Window of a Day": "Jendela Waktu Perawatan dalam Sehari",
|
||||
"Effective Date Range": "Rentang Tanggal Efektif (Opsional)",
|
||||
"Schedule Maintenance": "Jadwal Pemeliharaan",
|
||||
"Badge Down Color": "Warna bawah Lencana",
|
||||
"Badge Warn Days": "Hari Lencana Peringatan",
|
||||
"statusPageRefreshIn": "Muat ulang dalam: {0}",
|
||||
"webhookAdditionalHeadersTitle": "Header Tambahan",
|
||||
"maintenanceStatus-unknown": "Tidak dikenal",
|
||||
"Server Timezone": "Zona Waktu Server",
|
||||
"maintenanceStatus-scheduled": "Dijadwalkan",
|
||||
"maintenanceStatus-ended": "Berakhir",
|
||||
"dataRetentionTimeError": "Periode retensi harus 0 atau lebih besar",
|
||||
"chromeExecutable": "Chrome/Chromium Dapat Dijalankan",
|
||||
"chromeExecutableAutoDetect": "Deteksi otomatis",
|
||||
"Edit Maintenance": "Sunting Pemeliharaan",
|
||||
"DateTime Range": "Rentang Tanggal dan Waktu",
|
||||
"loadingError": "Tidak dapat mengambil data, harap coba lagi nanti.",
|
||||
"installing": "Memasang",
|
||||
"uninstall": "Copot pemasangan",
|
||||
"confirmUninstallPlugin": "Anda yakin ingin mencopot pemasangan plugin ini?",
|
||||
"cloneOf": "Klon dari {0}",
|
||||
"affectedMonitorsDescription": "Pilih monitor yang terpengaruh oleh pemeliharaan saat ini",
|
||||
"You can divide numbers with": "Anda dapat membagi angka dengan",
|
||||
"Continue": "Lanjutkan",
|
||||
"Badge Style": "Gaya Lencana",
|
||||
"Badge value (For Testing only.)": "Nilai lencana (Hanya untuk Pengujian.)",
|
||||
"Badge URL": "URL lencana",
|
||||
"Badge Down Days": "Hari Penghentian Lencana",
|
||||
"telegramSendSilentlyDescription": "Mengirim pesan secara senyap. Pengguna akan menerima notifikasi tanpa suara.",
|
||||
"telegramProtectContentDescription": "Jika diaktifkan, pesan bot di Telegram akan dilindungi dari forwarding dan saving.",
|
||||
"plugin": "Pengaya | Plugin",
|
||||
"install": "Pasang",
|
||||
"promosmsAllowLongSMS": "Izinkan SMS panjang",
|
||||
"Badge Pending Color": "Warna Lencana Tertunda",
|
||||
"Monitor Group": "Monitor Grup",
|
||||
"Expected Value": "Value yang diharapkan",
|
||||
"Json Query": "Json Query"
|
||||
}
|
||||
|
@@ -159,7 +159,7 @@
|
||||
"Show URI": "Mostra URI",
|
||||
"Tags": "Etichette",
|
||||
"Add New below or Select...": "Aggiungi oppure scegli…",
|
||||
"Tag with this name already exist.": "Un'etichetta con questo nome già esiste.",
|
||||
"Tag with this name already exist.": "Un'etichetta con questo nome esiste già.",
|
||||
"Tag with this value already exist.": "Un'etichetta con questo valore già esiste.",
|
||||
"color": "colore",
|
||||
"value (optional)": "descrizione (opzionale)",
|
||||
@@ -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",
|
||||
@@ -213,7 +213,7 @@
|
||||
"smtpBCC": "CCn",
|
||||
"discord": "Discord",
|
||||
"Discord Webhook URL": "URL Webhook di Discord",
|
||||
"wayToGetDiscordURL": "È possibile recuperarlo da Impostazioni server -> Integrazioni -> Creare Webhook",
|
||||
"wayToGetDiscordURL": "Puoi ottenerlo andando su Impostazioni server -> Integrazioni -> Visualizza webhook -> Nuovo webhook",
|
||||
"Bot Display Name": "Nome del Bot",
|
||||
"Prefix Custom Message": "Prefisso per il messaggio personalizzato",
|
||||
"Hello @everyone is...": "Ciao a {'@'}everyone …",
|
||||
@@ -384,7 +384,7 @@
|
||||
"resendDisabled": "Reinvio disabilitato",
|
||||
"Resend Notification if Down X times consequently": "Reinvia la notifica se Down X volte di seguito",
|
||||
"Add New Status Page": "Aggiungi nuova pagina di stato",
|
||||
"webhookAdditionalHeadersDesc": "Imposta gli header aggiuntivi inviati nel webhook.",
|
||||
"webhookAdditionalHeadersDesc": "Imposta intestazioni aggiuntive inviate con il webhook. Ogni intestazione deve essere definita come chiave/valore JSON.",
|
||||
"topicExplanation": "MQTT topic da controllare",
|
||||
"successMessage": "Messaggio con successo",
|
||||
"successMessageExplanation": "Messaggio MQTT considerato come successo",
|
||||
@@ -417,5 +417,198 @@
|
||||
"Affected Monitors": "Monitoraggi interessati",
|
||||
"Pick Affected Monitors...": "Seleziona i monitoraggi interessati…",
|
||||
"Valid": "Valido",
|
||||
"Certificate Expiry Notification": "Notifica scadenza certificato"
|
||||
"Certificate Expiry Notification": "Notifica scadenza certificato",
|
||||
"styleElapsedTimeShowWithLine": "Mostra (con linea)",
|
||||
"webhookBodyPresetOption": "Predefinito - {0}",
|
||||
"webhookBodyCustomOption": "Corpo personalizzato",
|
||||
"topic": "Topic",
|
||||
"selectedMonitorCount": "Selezionato: {0}",
|
||||
"Check/Uncheck": "Seleziona/Deseleziona",
|
||||
"Proxies": "Proxy",
|
||||
"Stop": "Fermare",
|
||||
"startOrEndWithOnly": "Inizia o termina solo con {0}",
|
||||
"No consecutive dashes": "Nessun trattino consecutivo",
|
||||
"HTTP Basic Auth": "Autenticazione di base HTTP",
|
||||
"Reverse Proxy": "Proxy inverso",
|
||||
"Backup": "Backup",
|
||||
"About": "Di",
|
||||
"wayToGetCloudflaredURL": "(Scarica cloudflared da {0})",
|
||||
"cloudflareWebsite": "Sito web di Cloudflare",
|
||||
"Message:": "Messaggio:",
|
||||
"Don't know how to get the token? Please read the guide:": "Non sai come ottenere il token? Si prega di leggere la guida:",
|
||||
"Trust Proxy": "Proxy di fiducia",
|
||||
"Other Software": "Altro Software",
|
||||
"For example: nginx, Apache and Traefik.": "Ad esempio: nginx, Apache e Traefik.",
|
||||
"Please read": "Si prega di leggere",
|
||||
"Subject:": "Soggetto:",
|
||||
"Valid To:": "Valido per:",
|
||||
"Days Remaining:": "Giorni rimanenti:",
|
||||
"Issuer:": "Emittente",
|
||||
"Fingerprint:": "Impronta digitale:",
|
||||
"No status pages": "Nessuna pagina di stato",
|
||||
"Domain Name Expiry Notification": "Notifica di scadenza del nome di dominio",
|
||||
"Date Created": "Data di creazione",
|
||||
"Slug": "Slug",
|
||||
"Show Powered By": "Mostra Alimentato da",
|
||||
"Domain Names": "Nomi di dominio",
|
||||
"signedInDispDisabled": "Autenticazione disabilitata.",
|
||||
"RadiusSecret": "Radius Segreto",
|
||||
"RadiusCalledStationId": "Identificativo della stazione chiamata",
|
||||
"RadiusCallingStationId": "Id stazione chiamante",
|
||||
"RadiusCallingStationIdDescription": "Identificativo del dispositivo chiamante",
|
||||
"API Username": "Nome utente dell'API",
|
||||
"API Key": "Chiave dell'API",
|
||||
"Show update if available": "Mostra aggiornamento se disponibile",
|
||||
"RadiusSecretDescription": "Segreto condiviso tra client e server",
|
||||
"Also check beta release": "Controlla anche la versione beta",
|
||||
"Check how to config it for WebSocket": "Controlla come configurarlo per WebSocket",
|
||||
"Steam Game Server": "Server di gioco Steam",
|
||||
"Most likely causes:": "Cause più probabili:",
|
||||
"The resource is no longer available.": "La risorsa non è più disponibile.",
|
||||
"What you can try:": "Cosa puoi provare:",
|
||||
"Retype the address.": "Ridigita l'indirizzo.",
|
||||
"Go back to the previous page.": "Torna alla pagina precedente.",
|
||||
"Coming Soon": "Prossimamente",
|
||||
"Connection String": "Stringa di connessione",
|
||||
"Query": "Richiesta",
|
||||
"settingsCertificateExpiry": "Scadenza certificato TLS",
|
||||
"deleteDockerHostMsg": "Sei sicuro di voler eliminare questo host docker per tutti i monitor?",
|
||||
"tcp": "TCP / HTTP",
|
||||
"Docker Container": "Contenitore Docker",
|
||||
"Container Name / ID": "Nome/ID contenitore",
|
||||
"Docker Host": "Host Docker",
|
||||
"Docker Hosts": "Host Docker",
|
||||
"Domain": "Dominio",
|
||||
"Workstation": "Postazione di lavoro",
|
||||
"Packet Size": "Dimensione del pacchetto",
|
||||
"Setup Docker Host": "Configurare l'host Docker",
|
||||
"telegramSendSilently": "Invia silenziosamente",
|
||||
"telegramSendSilentlyDescription": "Invia il messaggio in silenzio. Gli utenti riceveranno una notifica senza audio.",
|
||||
"telegramProtectContent": "Proteggi inoltro/salvataggio",
|
||||
"disableCloudflaredNoAuthMsg": "Sei in modalità No Auth, non è richiesta una password.",
|
||||
"wayToGetLineNotifyToken": "Puoi ottenere un token di accesso da {0}",
|
||||
"Examples": "Esempi",
|
||||
"Long-Lived Access Token": "Token di accesso di lunga durata",
|
||||
"Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "Il token di accesso di lunga durata può essere creato facendo clic sul nome del tuo profilo (in basso a sinistra) e scorrendo verso il basso, quindi fai clic su Crea token. ",
|
||||
"Notification Service": "Servizio di notifica",
|
||||
"default: notify all devices": "default: notifica a tutti i dispositivi",
|
||||
"Automations can optionally be triggered in Home Assistant:": "Le automazioni possono essere facoltativamente attivate in Home Assistant:",
|
||||
"Trigger type:": "Tipo di attivazione:",
|
||||
"Event type:": "Tipo di evento:",
|
||||
"Event data:": "Dati dell'evento:",
|
||||
"Then choose an action, for example switch the scene to where an RGB light is red.": "Quindi scegli un'azione, ad esempio cambia la scena in cui una luce RGB è rossa.",
|
||||
"Frontend Version": "Versione front-end",
|
||||
"Frontend Version do not match backend version!": "La versione del frontend non corrisponde alla versione del backend!",
|
||||
"backupOutdatedWarning": "Deprecato: poiché sono state aggiunte molte funzionalità e questa funzionalità di backup è un po' trascurata, non può generare o ripristinare un backup completo.",
|
||||
"backupRecommend": "Eseguire invece il backup diretto del volume o della cartella dei dati (./data/).",
|
||||
"Optional": "Opzionale",
|
||||
"sameAsServerTimezone": "Uguale al fuso orario del server",
|
||||
"startDateTime": "Data/ora di inizio",
|
||||
"endDateTime": "Data/ora di fine",
|
||||
"cronExpression": "Espressione Cron",
|
||||
"cronSchedule": "Programma: ",
|
||||
"recurringInterval": "Intervallo",
|
||||
"Recurring": "Ricorrente",
|
||||
"strategyManual": "Attivo/Inattivo manualmente",
|
||||
"warningTimezone": "Sta usando il fuso orario del server",
|
||||
"weekdayShortMon": "Lun",
|
||||
"weekdayShortTue": "Mar",
|
||||
"weekdayShortWed": "Mer",
|
||||
"weekdayShortThu": "Gio",
|
||||
"weekdayShortSat": "Sab",
|
||||
"weekdayShortSun": "Dom",
|
||||
"dayOfWeek": "Giorno della settimana",
|
||||
"dayOfMonth": "Giorno del mese",
|
||||
"lastDay": "Ultimo giorno",
|
||||
"lastDay1": "Ultimo giorno del mese",
|
||||
"lastDay3": "3° ultimo giorno del mese",
|
||||
"lastDay4": "4° ultimo giorno del mese",
|
||||
"No Maintenance": "Nessuna manutenzione",
|
||||
"pauseMaintenanceMsg": "Sei sicuro di voler mettere in pausa?",
|
||||
"maintenanceStatus-inactive": "Inattivo",
|
||||
"maintenanceStatus-scheduled": "Programmato",
|
||||
"maintenanceStatus-ended": "Conclusa",
|
||||
"maintenanceStatus-unknown": "Sconosciuto",
|
||||
"Display Timezone": "Mostra fuso orario",
|
||||
"Server Timezone": "Fuso orario del server",
|
||||
"statusPageMaintenanceEndDate": "Fine",
|
||||
"IconUrl": "URL dell'icona",
|
||||
"Enable DNS Cache": "Abilita la cache DNS per i monitor HTTP(s).",
|
||||
"Enable": "Abilitare",
|
||||
"Disable": "Disattivare",
|
||||
"chromeExecutableAutoDetect": "Trovato automaticamente",
|
||||
"dnsCacheDescription": "Potrebbe non funzionare in alcuni ambienti IPv6, disabilitalo in caso di problemi.",
|
||||
"Single Maintenance Window": "Singola finestra di manutenzione",
|
||||
"Maintenance Time Window of a Day": "Finestra temporale di manutenzione di un giorno",
|
||||
"Effective Date Range": "Intervallo di date effettivo (facoltativo)",
|
||||
"Schedule Maintenance": "Pianificare la manutenzione",
|
||||
"Edit Maintenance": "Modifica Manutenzione",
|
||||
"Date and Time": "Data e ora",
|
||||
"DateTime Range": "Intervallo DataOra",
|
||||
"loadingError": "Impossibile recuperare i dati, riprova più tardi.",
|
||||
"plugin": "Plug-in | Plugin",
|
||||
"install": "Installare",
|
||||
"installing": "Installazione",
|
||||
"uninstall": "Disinstalla",
|
||||
"confirmUninstallPlugin": "Sei sicuro di voler disinstallare questo plugin?",
|
||||
"notificationRegional": "Regionale",
|
||||
"Clone": "Clone",
|
||||
"cloneOf": "Clone di {0}",
|
||||
"wayToGetZohoCliqURL": "Puoi scoprire come creare un URL webhook {0}.",
|
||||
"dataRetentionTimeError": "Il periodo di conservazione deve essere pari o superiore a 0",
|
||||
"infiniteRetention": "Impostare su 0 per la conservazione infinita.",
|
||||
"enableGRPCTls": "Consenti l'invio di richieste gRPC con connessione TLS",
|
||||
"grpcMethodDescription": "Il nome del metodo viene convertito nel formato cammelCase come sayHello, check, ecc.",
|
||||
"styleElapsedTimeShowNoLine": "Mostra (nessuna riga)",
|
||||
"Add New Tag": "Aggiungi nuova etichetta",
|
||||
"webhookCustomBodyDesc": "Definire un corpo HTTP personalizzato per la richiesta. Le variabili modello {msg}, {heartbeat}, {monitor} sono accettate.",
|
||||
"Select": "Selezionare",
|
||||
"Accept characters:": "Accetta caratteri:",
|
||||
"The slug is already taken. Please choose another slug.": "La lumaca è già slug. Scegli un'altra slug.",
|
||||
"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.": "La connessione corrente potrebbe andare persa se ti stai connettendo tramite Cloudflare Tunnel. Sei sicuro di volerlo fermare? Digita la tua password attuale per confermarla.",
|
||||
"Footer Text": "Testo piè di pagina",
|
||||
"signedInDisp": "Accesso eseguito come {0}",
|
||||
"RadiusCalledStationIdDescription": "Identificativo del dispositivo chiamato",
|
||||
"Clone Monitor": "Monitoraggio clonazione",
|
||||
"styleElapsedTime": "Tempo trascorso sotto la barra del battito cardiaco",
|
||||
"enableProxyDescription": "Questo proxy non avrà effetto sulle richieste di monitoraggio fino a quando non viene attivato. È possibile controllare la disabilitazione temporanea del proxy da tutti i monitor in base allo stato di attivazione.",
|
||||
"Using a Reverse Proxy?": "Utilizzo di un proxy inverso?",
|
||||
"There might be a typing error in the address.": "Potrebbe esserci un errore di battitura nell'indirizzo.",
|
||||
"certificationExpiryDescription": "HTTPS monitora la notifica di attivazione quando il certificato TLS scade tra:",
|
||||
"tailscalePingWarning": "Per utilizzare il monitor Tailscale Ping, è necessario installare Uptime Kuma senza Docker e installare anche il client Tailscale sul server.",
|
||||
"telegramMessageThreadID": "(Facoltativo) ID thread messaggio",
|
||||
"telegramMessageThreadIDDescription": "Facoltativo Identificatore univoco per il thread del messaggio di destinazione (argomento) del forum; solo per i supergruppi del forum",
|
||||
"telegramProtectContentDescription": "Se abilitato, i messaggi del bot in Telegram saranno protetti dall'inoltro e dal salvataggio.",
|
||||
"trustProxyDescription": "Fidati delle intestazioni 'X-Forwarded-*'. Se vuoi ottenere l'IP client corretto e il tuo Uptime Kuma è dietro un proxy come Nginx o Apache, dovresti abilitarlo.",
|
||||
"A list of Notification Services can be found in Home Assistant under \"Developer Tools > Services\" search for \"notification\" to find your device/phone name.": "Un elenco di servizi di notifica è disponibile in Home Assistant in \"Strumenti per sviluppatori> Servizi\" cerca \"notifica\" per trovare il nome del tuo dispositivo/telefono.",
|
||||
"invalidCronExpression": "Espressione Cron non valida: {0}",
|
||||
"lastDay2": "2° ultimo giorno del mese",
|
||||
"maintenanceStatus-under-maintenance": "In manutenzione",
|
||||
"chromeExecutable": "Cromo/cromo eseguibile",
|
||||
"chromeExecutableDescription": "Per gli utenti Docker, se Chromium non è ancora installato, potrebbero essere necessari alcuni minuti per l'installazione e la visualizzazione del risultato del test. Richiede 1 GB di spazio su disco.",
|
||||
"uninstalling": "Disinstallazione",
|
||||
"confirmDeleteTagMsg": "Sei sicuro di voler eliminare questo tag? I monitor associati a questo tag non verranno eliminati.",
|
||||
"Request Timeout": "Richiedi timeout",
|
||||
"timeoutAfter": "Timeout dopo {0} secondi",
|
||||
"Resend Notification if Down X times consecutively": "Invia di nuovo la notifica se giù X volte consecutivamente",
|
||||
"Proxy": "Proxy",
|
||||
"or": "o",
|
||||
"statusPageRefreshIn": "Aggiorna tra: {0}",
|
||||
"HTTP Headers": "Intestazioni HTTP",
|
||||
"Custom": "Personalizzato",
|
||||
"Connection Type": "Tipo di connessione",
|
||||
"Docker Daemon": "Deamon Docker",
|
||||
"socket": "Socket",
|
||||
"Home Assistant URL": "URL dell'assistente domestico",
|
||||
"weekdayShortFri": "Ven",
|
||||
"Home": "Accoglienza",
|
||||
"Invert Keyword": "Inverti parola chiave",
|
||||
"filterActive": "Attiva",
|
||||
"filterActivePaused": "In pausa",
|
||||
"Cannot connect to the socket server": "Impossibile connettersi al server socket",
|
||||
"Reconnecting...": "Riconnessione...",
|
||||
"Expected Value": "Valore atteso",
|
||||
"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."
|
||||
}
|
||||
|
@@ -507,7 +507,6 @@
|
||||
"lineDevConsoleTo": "Line Developers Console - {0}",
|
||||
"Basic Settings": "基本設定",
|
||||
"User ID": "User ID",
|
||||
"Android": "Android",
|
||||
"Huawei": "Huawei",
|
||||
"Device Token": "デバイストークン",
|
||||
"recurringIntervalMessage": "毎日1回実行する|{0} 日に1回実行する",
|
||||
@@ -516,5 +515,42 @@
|
||||
"Body Encoding": "ボディエンコード",
|
||||
"Learn More": "さらに詳しく",
|
||||
"infiniteRetention": "保持期間を無制限にしたい場合は、0に設定してください。",
|
||||
"Display Timezone": "表示タイムゾーン"
|
||||
"Display Timezone": "表示タイムゾーン",
|
||||
"startDateTime": "開始日時",
|
||||
"User Key": "ユーザーキー",
|
||||
"SecretKey": "シークレットキー",
|
||||
"Home": "ホーム",
|
||||
"webhookBodyCustomOption": "カスタムbody",
|
||||
"octopushPhoneNumber": "電話番号 (初期フォーマット, 例: +33612345678) ",
|
||||
"Topic": "トピック",
|
||||
"pushoverMessageTtl": "メッセージTTL(秒)",
|
||||
"apiCredentials": "API認証情報",
|
||||
"Economy": "エコノミー",
|
||||
"statusPageRefreshIn": "{0}後に再読み込みします",
|
||||
"filterActivePaused": "停止中",
|
||||
"filterActive": "有効",
|
||||
"Example:": "例: {0}",
|
||||
"Read more:": "さらに: {0}",
|
||||
"Status:": "ステータス: {0}",
|
||||
"Enable TLS": "TLS 有効",
|
||||
"AccessKeyId": "アクセスキーID",
|
||||
"SecretAccessKey": "アクセスキーシークレット",
|
||||
"PhoneNumbers": "携帯電話番号",
|
||||
"Date and Time": "日時",
|
||||
"chromeExecutableAutoDetect": "自動検出",
|
||||
"More info on:": "詳細はこちら: {0}",
|
||||
"Cannot connect to the socket server": "ソケットサーバーに接続できません",
|
||||
"Reconnecting...": "再接続中...",
|
||||
"endDateTime": "終了日時",
|
||||
"cronSchedule": "スケジュール ",
|
||||
"Edit Maintenance": "メンテナンスの編集",
|
||||
"WebHookUrl": "ウェブフックUrl",
|
||||
"Notification Service": "通知サービス",
|
||||
"atLeastOneMonitor": "最低一つは影響を受けるモニターを選択してください",
|
||||
"Json Query": "Jsonクエリ",
|
||||
"octopushSMSSender": "SMS送信者名:3~11文字の英数字とスペース(a~zA~z0~9)",
|
||||
"Lowcost": "低コスト",
|
||||
"A list of Notification Services can be found in Home Assistant under \"Developer Tools > Services\" search for \"notification\" to find your device/phone name.": "通知サービスの一覧からデバイス名を見つけるには、Home Assistantの「開発者ツール > サービス」から通知で検索してください。",
|
||||
"Notify Channel": "通知チャンネル",
|
||||
"Icon Emoji": "絵文字アイコン"
|
||||
}
|
||||
|
@@ -413,8 +413,6 @@
|
||||
"For safety, must use secret key": "안전을 위해 꼭 Secret Key를 사용하세요.",
|
||||
"Device Token": "기기 Token",
|
||||
"Platform": "플랫폼",
|
||||
"iOS": "iOS",
|
||||
"Android": "Android",
|
||||
"Huawei": "Huawei",
|
||||
"High": "High",
|
||||
"Retry": "재시도",
|
||||
@@ -591,6 +589,8 @@
|
||||
"RadiusCalledStationIdDescription": "접속 스테이션의 식별자",
|
||||
"RadiusCallingStationId": "접속 요청 스테이션의 Id",
|
||||
"RadiusCallingStationIdDescription": "접속 요청 스테이션의 식별자",
|
||||
"timeoutAfter": "{0}초 후 타임아웃",
|
||||
"Request Timeout": "요청 타임아웃",
|
||||
"Query": "쿼리",
|
||||
"settingsCertificateExpiry": "TLS 인증서 만료",
|
||||
"certificationExpiryDescription": "HTTPS 모니터링 TLS 인증서가 만료되면 알림을 활성화해요:",
|
||||
|
93
src/lang/my.json
Normal file
93
src/lang/my.json
Normal file
@@ -0,0 +1,93 @@
|
||||
{
|
||||
"languageName": "အင်္ဂလိပ်ဘာသာ",
|
||||
"Settings": "ပြင်ဆင်ချက်များ",
|
||||
"Help": "အကူအညီ",
|
||||
"New Update": "အသစ်ထွက်ရှိခြင်း",
|
||||
"Language": "ဘာသာစကား",
|
||||
"Appearance": "သွင်ပြင်လက္ခဏာ",
|
||||
"Theme": "သဏ္ဍာန်",
|
||||
"General": "အထွေထွေ",
|
||||
"Primary Base URL": "ဦးစားပေးအင်တာနက်လိပ်စာ",
|
||||
"Version": "စနစ်အဆင့်",
|
||||
"List": "စာရင်း",
|
||||
"Home": "ပင်မစာမျက်နှာ",
|
||||
"Dashboard": "ခြုံငုံသုံးသပ်ချက်စာမျက်နှာ",
|
||||
"Add": "အသစ်ပေါင်းထည့်မည်",
|
||||
"Quick Stats": "စာရင်းအားခြုံငုံကြည့်ရှုခြင်း",
|
||||
"Up": "ချိတ်ဆက်မှုအောင်မြင်နေသည်",
|
||||
"Maintenance": "ပြုပြင်နေသည်",
|
||||
"Unknown": "အမည်မသိအကြောင်းအရာ",
|
||||
"Reconnecting...": "ပြန်လည်ချိတ်ဆက်နေပါသည်...",
|
||||
"General Monitor Type": "အထွေထွေစောင့်ကြည့်မှုပုံစံ",
|
||||
"markdownSupported": "Markdown syntax အားထောက်ပံ့သည်",
|
||||
"pauseDashboardHome": "ခေတ္တရပ်တန့်မည်",
|
||||
"Pause": "ခေတ္တရပ်တန့်မည်",
|
||||
"Name": "အမ်",
|
||||
"Status": "အခြေအနေများ",
|
||||
"DateTime": "နေ့ရက်နှင့်အချိန်",
|
||||
"Message": "စာတို",
|
||||
"No important events": "အရေးမကြီးသောဖြစ်ရပ်များ",
|
||||
"Game": "ဂိမ်း",
|
||||
"Check Update On GitHub": "အသစ်ထွက်ရှိမှုအား GitHub တွင် စစ်ဆေးရန်",
|
||||
"Add New Monitor": "စောင့်ကြည့်မှုအသစ်ပေါင်းထည့်မည်",
|
||||
"Down": "ကွန်ရက်ပြတ်တောက်နေသည်",
|
||||
"Pending": "ဆိုင်းငံ့ဆဲ",
|
||||
"statusMaintenance": "ပြုပြင်ထိန်းသိမ်းခြင်း",
|
||||
"Cannot connect to the socket server": "Socket Server အားချိတ်ဆက်မှုမအောင်မြင်ပါ",
|
||||
"Passive Monitor Type": "Passive စောင့်ကြည့်မှုပုံစံ",
|
||||
"Specific Monitor Type": "သီးခြားစောင့်ကြည့်မှုပုံစံ",
|
||||
"Resume": "ဆက်သွားမည်",
|
||||
"Edit": "ပြောင်းလဲမည်",
|
||||
"Delete": "ဖျက်သိမ်းမည်",
|
||||
"Current": "ယခုလက်ရှိ",
|
||||
"Uptime": "ကွန်ယက်ချိတ်ဆက်မှုကြာချိန်",
|
||||
"Cert Exp.": "ဆာတီဖီကိတ်ကုန်ဆုံးချိန်",
|
||||
"Monitor": "စောင့်ကြည့်မှု | စောင့်ကြည့်မှုများ",
|
||||
"day": "နေ့ရက် | ရက်များ",
|
||||
"-day": "-နေ့ရက်",
|
||||
"hour": "နာရီ",
|
||||
"-hour": "-နာရီ",
|
||||
"Response": "တုံ့ပြန်မှု",
|
||||
"Monitor Type": "စောင့်ကြည့်မှုပုံစံ",
|
||||
"Keyword": "စာသား",
|
||||
"Invert Keyword": "စာသားပြောင်းပြန်",
|
||||
"Friendly Name": "မှတ်သားရန်လွယ်ကူသည့်အမည်",
|
||||
"URL": "အင်တာနက်လိပ်စာ",
|
||||
"Hostname": "စောင့်ကြည့်မှုအမည်",
|
||||
"Port": "အပေါက်",
|
||||
"Advanced": "အဆင့်မြင့်ပြင်ဆင်မှုများ",
|
||||
"checkEverySecond": "{0} စက္ကန့်တိုင်းစောင့်ကြည့်မည်",
|
||||
"retryCheckEverySecond": "{0} စက္ကန့်တိုင်းထပ်မံကြိုးစားမည်",
|
||||
"resendEveryXTimes": "{0} စက္ကန့်တိုင်းထပ်မံပေးပို့မည်",
|
||||
"resendDisabled": "ပိတ်ဆို့ထားခြင်းအားပေးပို့မ်",
|
||||
"ignoreTLSError": "HTTPS ဝက်ဘ်ဆိုဒ်များအတွက် TLS/SSL အပြစ်အားလျစ်လျှူရှုမည်",
|
||||
"Save": "သိမ်းဆည်းမည်",
|
||||
"Notifications": "သတိပေးချက်များ",
|
||||
"Not available, please setup.": "လတ်တလောမရရှိနိုင်ပါ၊ ကျေးဇူးပြု၍ပြင်ဆင်ပါ",
|
||||
"Setup Notification": "ပြင်ဆင်ခြင်းသတိပေးချက်",
|
||||
"Light": "အလင်း",
|
||||
"Dark": "အမှောင်",
|
||||
"Auto": "အလိုအလျောက်",
|
||||
"Theme - Heartbeat Bar": "သဏ္ဍာန်-ကွန်ယက်တိုင်းတာနှုန်းပေတံ",
|
||||
"Normal": "ပုံမှန်",
|
||||
"Bottom": "အောက်ဆုံး",
|
||||
"None": "ဘာမှမရှိပါ",
|
||||
"Timezone": "အချိန်ဇုန်",
|
||||
"Allow indexing": "အစီအစဉ်ချခြင်းကိုခွင့်ပြုရန်",
|
||||
"Change Password": "စကားဝှက်ပြောင်းမည်",
|
||||
"Current Password": "လက်ရှိစကားဝှက်",
|
||||
"New Password": "စကားဝှက်အသစ်",
|
||||
"Repeat New Password": "စကားဝှက်အသစ်အားထပ်မံရိုက်ထည့်ပါ",
|
||||
"Update Password": "စကားဝှက်အားပြင်ဆင်မည်",
|
||||
"Disable Auth": "ဝင်ရောက်မှုထပ်မံစစ်ဆေးခြင်းအားဖျက်သိမ်းမည်",
|
||||
"Enable Auth": "ဝင်ရောက်မှုထပ်မံစစ်ဆေးခြင်းအားအတည်ပြုမည်",
|
||||
"disableauth.message1": "</strong>ဝင်ရောက်မှုထပ်မံစစ်ဆေးခြင်းကိုဖျက်သိမ်းရန်</strong>သေချာပါသလား?",
|
||||
"Ping": "ချိတ်ဆက်မှုတိုင်းတာခြင်း",
|
||||
"Expected Value": "မျှော်လင့်ထားသည့်တန်ဖိုး",
|
||||
"Heartbeat Interval": "ကွန်ယက်ချိတ်ဆက်နိုင်မှုတိုင်းတာခြင်း အချိန်ကွာဟချက်",
|
||||
"Retries": "ထပ်မံကြိုးစားမှုများ",
|
||||
"Heartbeat Retry Interval": "ကွန်ယက်ချိတ်ဆက်နိုင်မှုတိုင်းတာခြင်း ထပ်မံကြိုးစားခြင်း အချိန်ကွာဟချက်",
|
||||
"Resend Notification if Down X times consecutively": "ကွန်ယက်ချိတ်ဆက်မှု X အကြိမ်ထိ ဆက်တိုက်ကျနေပါက သတိပေးချက်ထပ်မံပေးပို့ရန်",
|
||||
"retriesDescription": "ဝန်ဆောင်မှုကွန်ယက်ပြတ်တောက်နေ၍ သတိပေးချက်ပေးပို့ပြီး အများဆုံးထပ်မံကြိုးစားနိုင်မှု",
|
||||
"Search Engine Visibility": "ရှာဖွေမှုအင်ဂျင်များ၏မြင်နိုင်စွမ်း"
|
||||
}
|
@@ -404,8 +404,6 @@
|
||||
"For safety, must use secret key": "Voor de veiligheid moet je de secret key gebruiken",
|
||||
"Device Token": "Apparaat Token",
|
||||
"Platform": "Platform",
|
||||
"iOS": "iOS",
|
||||
"Android": "Android",
|
||||
"Huawei": "Huawei",
|
||||
"High": "Hoog",
|
||||
"Retry": "Opnieuw",
|
||||
@@ -586,7 +584,7 @@
|
||||
"Enable": "Inschakelen",
|
||||
"Disable": "Uitschakelen",
|
||||
"Single Maintenance Window": "Enkel onderhoudsperiode",
|
||||
"Effective Date Range": "Effectieve periode",
|
||||
"Effective Date Range": "Effectieve periode (Optioneel)",
|
||||
"Schedule Maintenance": "Onderhoud inplannen",
|
||||
"Date and Time": "Datum en tijd",
|
||||
"DateTime Range": "Datum en tijd periode",
|
||||
@@ -620,7 +618,7 @@
|
||||
"Lowcost": "Lowcost",
|
||||
"Economy": "Economy",
|
||||
"webhookAdditionalHeadersTitle": "Extra Headers",
|
||||
"webhookAdditionalHeadersDesc": "Voegt extra headers toe die meegestuurd worden met de webhook.",
|
||||
"webhookAdditionalHeadersDesc": "Voegt extra headers toe die meegestuurd worden met de webhook. Elke header moet worden gedefinieerd als een JSON key/value.",
|
||||
"Help": "Hulp",
|
||||
"Game": "Game",
|
||||
"statusMaintenance": "Onderhoud",
|
||||
@@ -703,5 +701,106 @@
|
||||
"pagertreeHigh": "Hoog",
|
||||
"Clone": "Dupliceer",
|
||||
"cloneOf": "Duplicaat van {0}",
|
||||
"Add New Tag": "Voeg nieuw label toe"
|
||||
"Add New Tag": "Voeg nieuw label toe",
|
||||
"Body Encoding": "Body Encoding",
|
||||
"twilioAuthToken": "Auth Token / Api Sleutel Secret",
|
||||
"twilioAccountSID": "Account SID",
|
||||
"Badge Preview": "Badge voorbeeld",
|
||||
"ntfyAuthenticationMethod": "Authenticatiemethode",
|
||||
"ntfyUsernameAndPassword": "Gebruikersnaam en Wachtwoord",
|
||||
"twilioApiKey": "Api Sleutel (optioneel)",
|
||||
"Badge Label Prefix": "Badge Label Voorvoegsel",
|
||||
"Badge Label Suffix": "Badge label achtervoegsel",
|
||||
"API Keys": "API Sleutels",
|
||||
"Expiry": "Verval",
|
||||
"noGroupMonitorMsg": "Niet beschikbaar. Creëer eerst een Groep Monitor.",
|
||||
"Notify Channel": "Notify Channel",
|
||||
"Expiry date": "Vervaldatum",
|
||||
"Key Added": "Sleutel toegevoegd",
|
||||
"Badge value (For Testing only.)": "Badgewaarde (Alleen voor testen)",
|
||||
"aboutNotifyChannel": "Notify channel activeert een melding op bureaublad of mobiel voor alle leden van de channel, ongeacht of hun beschikbaarheid is ingesteld op actief of afwezig.",
|
||||
"apiKey-inactive": "Inactief",
|
||||
"disableAPIKeyMsg": "Weet je zeker dat je deze API-sleutel wilt uitschakelen?",
|
||||
"Show Clickable Link Description": "Als deze optie is aangevinkt, heeft iedereen die toegang heeft tot deze statuspagina toegang tot de monitor URL.",
|
||||
"Badge Duration (in hours)": "Duur badge (in uren)",
|
||||
"Badge Maintenance Color": "Badge Onderhoud Kleur",
|
||||
"Badge URL": "Badge URL",
|
||||
"Close": "Sluit",
|
||||
"Request Body": "Request Body",
|
||||
"pagertreeIntegrationUrl": "Integratie URL",
|
||||
"pagertreeUrgency": "Urgentie",
|
||||
"pagertreeSilent": "Stil",
|
||||
"telegramMessageThreadID": "(Optioneel) Message Thread ID",
|
||||
"Clone Monitor": "Kloon Monitor",
|
||||
"Expires": "Vervalt",
|
||||
"webhookCustomBodyDesc": "Definieer een aangepaste HTTP Body voor de request. Template variabelen {msg}, {heartbeat}, {monitor} worden geaccepteerd.",
|
||||
"webhookBodyPresetOption": "Vooraf ingesteld - {0}",
|
||||
"webhookBodyCustomOption": "Aangepaste Body",
|
||||
"notificationRegional": "Regionaal",
|
||||
"No API Keys": "Geen API Sleutels",
|
||||
"apiKeyAddedMsg": "Je API-sleutel is toegevoegd. Noteer deze, want hij wordt niet meer weergegeven.",
|
||||
"Add API Key": "Voeg API Sleutel toe",
|
||||
"telegramSendSilently": "Stil verzenden",
|
||||
"telegramSendSilentlyDescription": "Stille verzending van het bericht. Gebruikers ontvangen een melding zonder geluid.",
|
||||
"Home": "Home",
|
||||
"Don't expire": "Verval nooit",
|
||||
"Continue": "Ga verder",
|
||||
"Add Another": "Nog een toevoegen",
|
||||
"lunaseaTarget": "Doel",
|
||||
"lunaseaDeviceID": "Apparaat ID",
|
||||
"lunaseaUserID": "Gebruiker ID",
|
||||
"Badge Color": "Badge kleur",
|
||||
"wayToGetPagerTreeIntegrationURL": "Nadat je de Uptime Kuma-integratie in PagerTree hebt gemaakt, kopieert je het eindpunt. Bekijk alle details {0}",
|
||||
"Badge Warn Color": "Badge Waarschuwing Kleur",
|
||||
"Invert Keyword": "Trefwoord omkeren",
|
||||
"filterActive": "Actief",
|
||||
"filterActivePaused": "Gepauzeerd",
|
||||
"statusPageRefreshIn": "Vernieuwen in: {0}",
|
||||
"telegramMessageThreadIDDescription": "Optioneel Unique identifier voor de target message thread (topic) van het forum; alleen voor forum-supergroepen",
|
||||
"telegramProtectContentDescription": "Als deze optie is ingeschakeld, zijn de botberichten in Telegram beveiligd tegen doorsturen en opslaan.",
|
||||
"telegramProtectContent": "Beveilig Doorsturen/Opslaan",
|
||||
"sameAsServerTimezone": "Zelfde als Server Tijdzone",
|
||||
"startDateTime": "Begindatum/Tijd",
|
||||
"endDateTime": "Einddatum/Tijd",
|
||||
"cronExpression": "Cron expressie",
|
||||
"cronSchedule": "Planning: ",
|
||||
"invalidCronExpression": "Ongeldige Cron expressie: {0}",
|
||||
"chromeExecutableDescription": "Voor Docker-gebruikers, als Chromium nog niet is geïnstalleerd, kan het een paar minuten duren om te installeren en het testresultaat weer te geven. Het neemt 1GB schijfruimte in beslag.",
|
||||
"invertKeywordDescription": "Kijk of het trefwoord afwezig is in plaats van aanwezig.",
|
||||
"pushoverMessageTtl": "Bericht TTL (seconden)",
|
||||
"goAlertInfo": "GoAlert is een open source applicatie voor het plannen van aanwezigheidsdiensten, geautomatiseerde escalaties en meldingen (zoals SMS of spraakoproepen). Schakel automatisch de juiste persoon in, op de juiste manier en op het juiste moment! {0}",
|
||||
"goAlertIntegrationKeyInfo": "Verkrijg generieke API-integratiesleutel voor de service in dit formaat \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\" meestal de waarde van tokenparameter van gekopieerde URL.",
|
||||
"deleteAPIKeyMsg": "Weet je zeker dat je deze API-sleutel wilt verwijderen?",
|
||||
"Generate": "Genereer",
|
||||
"pagertreeMedium": "Medium",
|
||||
"pagertreeCritical": "Kritisch",
|
||||
"pagertreeResolve": "Automatisch oplossen",
|
||||
"pagertreeDoNothing": "Doe niks",
|
||||
"twilioFromNumber": "Van Nummer",
|
||||
"twilioToNumber": "Naar Nummer",
|
||||
"Monitor Setting": "{0}'s Monitor Instelling",
|
||||
"Show Clickable Link": "Laat klikbare link zien",
|
||||
"Open Badge Generator": "Open Badge Generator",
|
||||
"Badge Generator": "{0}'s Badge Generator",
|
||||
"Badge Type": "Badge type",
|
||||
"Badge Up Color": "Badge Online Kleur",
|
||||
"Badge Down Color": "Badge Offline Kleur",
|
||||
"Badge Warn Days": "Badge Waarschuwing dagen",
|
||||
"Badge Down Days": "Badge Offline dagen",
|
||||
"Badge Style": "Badge stijl",
|
||||
"chromeExecutable": "Chrome/Chromium Executable",
|
||||
"chromeExecutableAutoDetect": "Automatisch detecteren",
|
||||
"Edit Maintenance": "Onderhoud bewerken",
|
||||
"Badge Label": "Badge Label",
|
||||
"Badge Label Color": "Badge label kleur",
|
||||
"Badge Prefix": "Badge Waarde Voorvoegsel",
|
||||
"Badge Pending Color": "Badge In Afwachting Kleur",
|
||||
"Badge Suffix": "Badge waarde achtervoegsel",
|
||||
"Group": "Groep",
|
||||
"Monitor Group": "Monitor Groep",
|
||||
"Cannot connect to the socket server": "Kan geen verbinding maken met de socket-server",
|
||||
"Reconnecting...": "Opnieuw verbinden...",
|
||||
"Expected Value": "Verwachte waarde",
|
||||
"Json Query": "Json Query",
|
||||
"jsonQueryDescription": "Voer een JSON-query uit op de respons en controleer de verwachte waarde (De retourwaarde wordt omgezet naar een string voor vergelijking). Bekijk <a href='https://jsonata.org/'>jsonata.org</a> voor de documentatie over de querytaal. Een speelplaats is beschikbaar <a href='https://try.jsonata.org/'>hier</a>."
|
||||
}
|
||||
|
103
src/lang/pl.json
103
src/lang/pl.json
@@ -414,8 +414,6 @@
|
||||
"For safety, must use secret key": "Ze względów bezpieczeństwa musisz użyć tajnego klucza",
|
||||
"Device Token": "Token urządzenia",
|
||||
"Platform": "Platforma",
|
||||
"iOS": "iOS",
|
||||
"Android": "Android",
|
||||
"Huawei": "Huawei",
|
||||
"High": "Wysoki",
|
||||
"Retry": "Ponów",
|
||||
@@ -450,7 +448,7 @@
|
||||
"Backup": "Backup",
|
||||
"About": "O skrypcie",
|
||||
"wayToGetCloudflaredURL": "(Pobierz cloudflared z {0})",
|
||||
"cloudflareWebsite": "Strona Cloudflare",
|
||||
"cloudflareWebsite": "strona Cloudflare",
|
||||
"Message:": "Wiadomość:",
|
||||
"Don't know how to get the token? Please read the guide:": "Nie wiesz jak uzyksać token? Przeczytaj proszę poradnik:",
|
||||
"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.": "Bieżące połączenie może zostać utracone, jeśli aktualnie łączysz się przez tunel Cloudflare. Czy na pewno chcesz to przerwać? Wpisz swoje aktualne hasło, aby je potwierdzić.",
|
||||
@@ -615,7 +613,7 @@
|
||||
"You can divide numbers with": "Możesz dzielić liczby przez",
|
||||
"or": "lub",
|
||||
"recurringInterval": "odstęp czasu",
|
||||
"Recurring": "powtarzający się",
|
||||
"Recurring": "Powtarzający się",
|
||||
"strategyManual": "Aktywowany/dezaktywowany ręcznie",
|
||||
"warningTimezone": "Używa strefy czasowej serwera",
|
||||
"weekdayShortMon": "pon",
|
||||
@@ -625,8 +623,8 @@
|
||||
"weekdayShortFri": "pt",
|
||||
"weekdayShortSat": "sob",
|
||||
"weekdayShortSun": "niedz",
|
||||
"dayOfWeek": "Dzień tygodnia",
|
||||
"dayOfMonth": "Dzień miesiąca",
|
||||
"dayOfWeek": "dzień tygodnia",
|
||||
"dayOfMonth": "dzień miesiąca",
|
||||
"lastDay": "Ostatni dzień",
|
||||
"lastDay1": "Ostatni dzień miesiąca",
|
||||
"lastDay2": "2. ostatni dzień miesiąca",
|
||||
@@ -648,8 +646,8 @@
|
||||
"confirmDeleteTagMsg": "Czy na pewno chcesz usunąć ten tag? Monitory powiązane z tym tagiem nie zostaną usunięte.",
|
||||
"Kook": "Kook",
|
||||
"Enable TLS": "Włącz TLS",
|
||||
"webhookAdditionalHeadersDesc": "Ustawia dodatkowe nagłówki wysyłane z webhookiem.",
|
||||
"dnsCacheDescription": "Może nie działać w niektórych środowiskach z IPv6. Wyłącz, jeśli napotkasz jakiekolwiek problemy.",
|
||||
"webhookAdditionalHeadersDesc": "Ustawia dodatkowe nagłówki wysyłane z webhookiem. Każdy nagłówek powinien być zdefiniowany jako klucz/wartość JSON.",
|
||||
"dnsCacheDescription": "Może nie działać w niektórych środowiskach IPv6, wyłącz ją, jeśli napotkasz jakiekolwiek problemy.",
|
||||
"wayToGetKookBotToken": "Utwórz aplikację i uzyskaj swój token bota na {0}",
|
||||
"wayToGetKookGuildID": "Włącz 'Developer Mode' w ustawieniach Kook'a i kliknij prawym przyciskiem myszy na gildię, aby uzyskać jej ID",
|
||||
"Game": "Gra",
|
||||
@@ -661,7 +659,7 @@
|
||||
"Disable": "Wyłącz",
|
||||
"Date and Time": "Data i czas",
|
||||
"IconUrl": "URL ikony",
|
||||
"Enable DNS Cache": "Włącz pamięć podręczną DNS",
|
||||
"Enable DNS Cache": "Włącz pamięć podręczną DNS dla monitorów HTTP",
|
||||
"Single Maintenance Window": "Pojedyncze okno konserwacji",
|
||||
"Effective Date Range": "Zakres dat obowiązywania (opcjonalnie)",
|
||||
"Schedule Maintenance": "Planowanie konserwacji",
|
||||
@@ -732,7 +730,7 @@
|
||||
"twilioToNumber": "Do numeru",
|
||||
"lunaseaTarget": "Cel",
|
||||
"twilioAccountSID": "SID konta",
|
||||
"twilioAuthToken": "Token autoryzacyjny",
|
||||
"twilioAuthToken": "Token autoryzacji / klucz Api Secret",
|
||||
"apiKeyAddedMsg": "Twój klucz API został dodany. Prosimy o zanotowanie go, ponieważ nie będzie on już więcej pokazywany.",
|
||||
"telegramMessageThreadID": "(Opcjonalne) ID wątku wiadomości",
|
||||
"telegramMessageThreadIDDescription": "Opcjonalny Unikalny identyfikator dla docelowego wątku wiadomości (tematu) forum; tylko dla supergrup forum",
|
||||
@@ -750,7 +748,7 @@
|
||||
"sameAsServerTimezone": "Tak jak strefa czasowa serwera",
|
||||
"endDateTime": "Data/godzina zakończenia",
|
||||
"cronExpression": "Wyrażenie Cron",
|
||||
"ntfyAuthenticationMethod": "Metoda Uwierzytelnienia",
|
||||
"ntfyAuthenticationMethod": "Metoda uwierzytelniania",
|
||||
"ntfyUsernameAndPassword": "Nazwa użytkownika i hasło",
|
||||
"noGroupMonitorMsg": "Niedostępna. Stwórz najpierw grupę monitorów.",
|
||||
"Close": "Zamknij",
|
||||
@@ -759,5 +757,86 @@
|
||||
"Group": "Grupa",
|
||||
"Monitor Group": "Grupa monitora",
|
||||
"Reconnecting...": "Ponowne łączenie...",
|
||||
"Cannot connect to the socket server": "Nie można połączyć się z serwerem gniazda"
|
||||
"Cannot connect to the socket server": "Nie można połączyć się z serwerem gniazda",
|
||||
"Badge Label Suffix": "Sufiks etykiety odznaki",
|
||||
"Badge Warn Color": "Kolor ostrzeżenia odznaki",
|
||||
"Badge Style": "Styl odznaki",
|
||||
"Badge value (For Testing only.)": "Wartość odznaki (tylko do testów.)",
|
||||
"Show Clickable Link": "Pokaż klikalne łącze",
|
||||
"Show Clickable Link Description": "Jeśli to pole jest zaznaczone, każdy, kto ma dostęp do tej strony stanu, może mieć dostęp do adresu URL monitora.",
|
||||
"Open Badge Generator": "Generator odznak Open Badge",
|
||||
"Badge Generator": "{0} Generator odznak",
|
||||
"Monitor Setting": "{0} Ustawienia monitora",
|
||||
"Badge Duration": "Czas trwania odznaki",
|
||||
"Badge Label": "Etykieta odznaki",
|
||||
"Badge Suffix": "Sufiks wartości odznaki",
|
||||
"chromeExecutable": "Plik wykonywalny Chrome/Chromium",
|
||||
"chromeExecutableAutoDetect": "Wykryj automatycznie",
|
||||
"chromeExecutableDescription": "W przypadku użytkowników Dockera, jeśli Chromium nie jest jeszcze zainstalowany, instalacja i wyświetlenie wyniku testu może zająć kilka minut. Zajmuje 1 GB miejsca na dysku.",
|
||||
"Edit Maintenance": "Edycja konserwacji",
|
||||
"Badge Type": "Typ odznaki",
|
||||
"Badge Prefix": "Prefiks wartości odznaki",
|
||||
"Badge Color": "Kolor odznaki",
|
||||
"Badge Label Color": "Kolor etykiety Odznaki",
|
||||
"Badge Label Prefix": "Prefiks etykiety identyfikatora",
|
||||
"Badge Pending Color": "Oczekujący kolor odznaki",
|
||||
"Badge Maintenance Color": "Kolor utrzymania odznaki",
|
||||
"Badge URL": "Adres URL odznaki",
|
||||
"gamedigGuessPort": "Gamedig: Port Guess",
|
||||
"filterActive": "Aktywny",
|
||||
"webhookCustomBodyDesc": "Definiuje niestandardową treść HTTP dla żądania. Akceptowane są zmienne szablonowe {msg}, {heartbeat}, {monitor}.",
|
||||
"Select": "Wybierz",
|
||||
"aboutNotifyChannel": "Powiadomienie kanału wywoła powiadomienie na komputerze lub urządzeniu mobilnym dla wszystkich członków kanału, niezależnie od tego, czy ich dostępność jest ustawiona jako aktywna, czy nie.",
|
||||
"twilioApiKey": "Klucz API (opcjonalnie)",
|
||||
"styleElapsedTime": "Czas, który upłynął pod paskiem bicia serca",
|
||||
"tailscalePingWarning": "Aby korzystać z monitora Tailscale Ping, należy zainstalować Uptime Kuma bez Dockera, a także zainstalować klienta Tailscale na serwerze.",
|
||||
"invertKeywordDescription": "Słowo kluczowe powinno być raczej nieobecne niż obecne.",
|
||||
"jsonQueryDescription": "Wykonaj zapytanie json względem odpowiedzi i sprawdź oczekiwaną wartość (wartość zwracana zostanie przekonwertowana na ciąg znaków do porównania). Sprawdź <a href='https://jsonata.org/'>jsonata.org</a>, aby uzyskać dokumentację dotyczącą języka zapytań. Plac zabaw można znaleźć <a href='https://try.jsonata.org/'>tutaj</a>.",
|
||||
"Server URL should not contain the nfty topic": "Adres URL serwera nie powinien zawierać tematu nfty",
|
||||
"Badge Duration (in hours)": "Czas trwania odznaki (w godzinach)",
|
||||
"Enter the list of brokers": "Wprowadź listę brokerów",
|
||||
"Enable Kafka Producer Auto Topic Creation": "Włącz automatyczne tworzenie tematów w producencie Kafka",
|
||||
"showCertificateExpiry": "Pokaż wygaśnięcie certyfikatu",
|
||||
"gamedigGuessPortDescription": "Port używany przez Valve Server Query Protocol może różnić się od portu klienta. Spróbuj tego, jeśli monitor nie może połączyć się z serwerem.",
|
||||
"Secret AccessKey": "Tajny klucz AccessKey",
|
||||
"wayToGetFlashDutyKey": "Możesz przejść do Channel -> (Select a Channel) -> Integrations -> Add a new integration' page, dodać \"Custom Event\", aby uzyskać adres push, skopiować klucz integracji w adresie. Więcej informacji można znaleźć na stronie",
|
||||
"Badge Down Color": "Kolor odznaki Offline",
|
||||
"Notify Channel": "Powiadom kanał",
|
||||
"Request Timeout": "Limit czasu żądania",
|
||||
"timeoutAfter": "Przekroczenie limitu czasu po {0} sekundach",
|
||||
"styleElapsedTimeShowNoLine": "Pokaż (bez linii)",
|
||||
"styleElapsedTimeShowWithLine": "Pokaż (z linią)",
|
||||
"filterActivePaused": "Wstrzymany",
|
||||
"webhookBodyCustomOption": "Niestandardowa treść",
|
||||
"webhookBodyPresetOption": "Wstępne ustawienie - {0}",
|
||||
"selectedMonitorCount": "Wybrano: {0}",
|
||||
"Check/Uncheck": "Zaznacz/Odznacz",
|
||||
"PushDeer Server": "Serwer PushDeer",
|
||||
"pushDeerServerDescription": "Pozostaw puste, aby korzystać z oficjalnego serwera",
|
||||
"Badge Preview": "Podgląd odznaki",
|
||||
"Kafka Brokers": "Broker Kafka",
|
||||
"Press Enter to add broker": "Naciśnij Enter, aby dodać brokera",
|
||||
"Kafka Topic Name": "Nazwa tematu Kafka",
|
||||
"Kafka Producer Message": "Wiadomość producenta Kafka",
|
||||
"Enable Kafka SSL": "Włącz Kafka SSL",
|
||||
"Kafka SASL Options": "Opcje SASL Kafki",
|
||||
"Mechanism": "Mechanizm",
|
||||
"Pick a SASL Mechanism...": "Wybierz mechanizm SASL...",
|
||||
"nostrRelaysHelp": "Jeden adres URL przekaźnika w wierszu",
|
||||
"nostrRecipients": "Klucze publiczne odbiorców (npub)",
|
||||
"nostrRecipientsHelp": "format npub, po jednym w wierszu",
|
||||
"Authorization Identity": "Identyfikacja autoryzacji",
|
||||
"AccessKey Id": "Identyfikator klucza AccessKey",
|
||||
"Session Token": "Token sesji",
|
||||
"Request Body": "Treść żądania",
|
||||
"FlashDuty Severity": "Poziom istotności",
|
||||
"nostrRelays": "Przekaźniki Nostr",
|
||||
"nostrSender": "Prywatny klucz nadawcy (nsec)",
|
||||
"Badge Up Color": "Kolor odznaki Online",
|
||||
"Badge Down Days": "Odznaka dni offline",
|
||||
"Badge Warn Days": "Odznaka dni z ostrzeżeniem",
|
||||
"noOrBadCertificate": "Brak/zły certyfikat",
|
||||
"Invert Keyword": "Odwróć słowo kluczowe",
|
||||
"Expected Value": "Oczekiwana wartość",
|
||||
"Json Query": "Zapytanie Json"
|
||||
}
|
||||
|
@@ -29,7 +29,7 @@
|
||||
"Settings": "Configurações",
|
||||
"Dashboard": "Painel",
|
||||
"New Update": "Nova Atualização",
|
||||
"Language": "Linguagem",
|
||||
"Language": "Idioma",
|
||||
"Appearance": "Aparência",
|
||||
"Theme": "Tema",
|
||||
"General": "Geral",
|
||||
@@ -476,13 +476,13 @@
|
||||
"Query": "Query",
|
||||
"settingsCertificateExpiry": "O Certificado TLS Expira",
|
||||
"Connection Type": "Tipo Da Conexão",
|
||||
"signedInDisp": "Assinado como {0}",
|
||||
"signedInDisp": "Logado como {0}",
|
||||
"RadiusCallingStationId": "ID Da Estação De Chamada",
|
||||
"RadiusCalledStationIdDescription": "Identificador do dispositivo de chamada",
|
||||
"Coming Soon": "Em Breve",
|
||||
"Connection String": "String De Conexão",
|
||||
"Docker Daemon": "Daemon Do Docker",
|
||||
"Show Powered By": "Mostrar Distribuído Por",
|
||||
"Show Powered By": "Mostrar Fornecido Por",
|
||||
"RadiusSecret": "Segredo Radius",
|
||||
"RadiusCalledStationId": "ID Da Estação Chamada",
|
||||
"deleteDockerHostMsg": "Você tem certeza que quer deletar esse host do Docker para todos os monitores?",
|
||||
@@ -523,7 +523,6 @@
|
||||
"Example:": "Exemplo: {0}",
|
||||
"Read more:": "Leia mais em: {0}",
|
||||
"promosmsAllowLongSMS": "Permitir SMS grandes",
|
||||
"Android": "Android",
|
||||
"Huawei": "Huawei",
|
||||
"smseagleTo": "Números Dos Telefones",
|
||||
"smseaglePriority": "Prioridade da mensagem (0-9, padrão=0)",
|
||||
@@ -570,10 +569,10 @@
|
||||
"disableAPIKeyMsg": "Você tem certeza de que quer desativar essa chave de API?",
|
||||
"smtp": "Email (SMTP)",
|
||||
"secureOptionTLS": "TLS (465)",
|
||||
"From Email": "Email De",
|
||||
"From Email": "Remetente",
|
||||
"smtpCC": "CC",
|
||||
"smtpBCC": "CCO",
|
||||
"To Email": "Email Para",
|
||||
"To Email": "Destinatário",
|
||||
"Recipients": "Destinatários",
|
||||
"Google Analytics ID": "ID Google Analytics",
|
||||
"Post": "Post",
|
||||
@@ -584,5 +583,10 @@
|
||||
"Automations can optionally be triggered in Home Assistant:": "Automações podem opcionalmente ser disparadas no Home Assistant:",
|
||||
"secureOptionNone": "Nenhum / STARTTLS (25, 587)",
|
||||
"apiKeyAddedMsg": "Sua chave de API foi adicionada. Por favor anote essa chave, ela não será mostrada novamente.",
|
||||
"Show Clickable Link": "Mostrar Link Clicável"
|
||||
"Show Clickable Link": "Mostrar Link Clicável",
|
||||
"backupOutdatedWarning": "Obsoleto: Já que muitos recursos foram adicionados e este recurso de backup não foi atualizado, ele não consegue gerar ou restaurar um backup completo.",
|
||||
"wayToGetDiscordURL": "Voce pode configurar isso indo à Configurações do Servidor -> Integrações -> Ver Webhooks -> Novo Webhook",
|
||||
"Home": "Início",
|
||||
"Reconnecting...": "Reconectando...",
|
||||
"Cannot connect to the socket server": "Não foi possível conectar ao servidor socket"
|
||||
}
|
||||
|
@@ -114,7 +114,7 @@
|
||||
"Notification Type": "Tipo de Notificação",
|
||||
"Email": "Email",
|
||||
"Test": "Testar",
|
||||
"Certificate Info": "Info. do Certificado ",
|
||||
"Certificate Info": "Info. do Certificado",
|
||||
"Resolver Server": "Resolver Servidor",
|
||||
"Resource Record Type": "Tipo de registro de aplicação",
|
||||
"Last Result": "Último resultado",
|
||||
|
39
src/lang/pt.json
Normal file
39
src/lang/pt.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"Settings": "Definições",
|
||||
"Help": "Ajuda",
|
||||
"New Update": "Nova actualização",
|
||||
"Language": "Linguagem",
|
||||
"Appearance": "Aspecto",
|
||||
"Theme": "Tema",
|
||||
"General": "Geral",
|
||||
"Game": "Jogo",
|
||||
"Version": "Versão",
|
||||
"List": "Lista",
|
||||
"Add": "Adicionar",
|
||||
"Quick Stats": "Estatísticas rápidas",
|
||||
"Up": "Acima",
|
||||
"Down": "Abaixo",
|
||||
"Pending": "Pendente",
|
||||
"statusMaintenance": "Manutenção",
|
||||
"Maintenance": "Manutenção",
|
||||
"Unknown": "Desconhecido",
|
||||
"Reconnecting...": "Reconectando...",
|
||||
"pauseDashboardHome": "Pausa",
|
||||
"Pause": "Pausa",
|
||||
"Name": "Nome",
|
||||
"Status": "Estado",
|
||||
"Message": "Mensagem",
|
||||
"Resume": "Retomar",
|
||||
"Edit": "Editar",
|
||||
"Delete": "Remover",
|
||||
"Current": "Actual",
|
||||
"Uptime": "Tempo de atividade",
|
||||
"day": "dia | dias",
|
||||
"languageName": "Português",
|
||||
"Primary Base URL": "URL base primário",
|
||||
"No important events": "Nenhum evento importante",
|
||||
"Dashboard": "Dashboard",
|
||||
"Add New Monitor": "Adicionar Novo Monitor",
|
||||
"Home": "Home",
|
||||
"Check Update On GitHub": "Verificar por Actualizações no GitHub"
|
||||
}
|
@@ -6,7 +6,7 @@
|
||||
"upsideDownModeDescription": "Реверс статуса сервиса. Если сервис доступен, то он помечается как НЕДОСТУПНЫЙ.",
|
||||
"maxRedirectDescription": "Максимальное количество перенаправлений. Поставьте 0, чтобы отключить перенаправления.",
|
||||
"acceptedStatusCodesDescription": "Выберите коды статусов для определения доступности сервиса.",
|
||||
"passwordNotMatchMsg": "Введёные пароли не совпадают",
|
||||
"passwordNotMatchMsg": "Введённые пароли не совпадают",
|
||||
"notificationDescription": "Привяжите уведомления к мониторам.",
|
||||
"keywordDescription": "Поиск слова в чистом HTML или в JSON-ответе (чувствительно к регистру).",
|
||||
"pauseDashboardHome": "Пауза",
|
||||
@@ -43,7 +43,7 @@
|
||||
"Delete": "Удалить",
|
||||
"Current": "Текущий",
|
||||
"Uptime": "Аптайм",
|
||||
"Cert Exp.": "Сертификат истекает",
|
||||
"Cert Exp.": "Сертификат ист.",
|
||||
"day": "день | дней",
|
||||
"-day": "-дней",
|
||||
"hour": "час",
|
||||
@@ -421,8 +421,6 @@
|
||||
"For safety, must use secret key": "В целях безопасности необходимо использовать секретный ключ",
|
||||
"Device Token": "Токен устройства",
|
||||
"Platform": "Платформа",
|
||||
"iOS": "iOS",
|
||||
"Android": "Android",
|
||||
"Huawei": "Huawei",
|
||||
"High": "High",
|
||||
"Retry": "Повторить",
|
||||
@@ -448,7 +446,7 @@
|
||||
"Message:": "Сообщение:",
|
||||
"Don't know how to get the token? Please read the guide:": "Не знаете, как получить токен? Пожалуйста, прочтите руководство:",
|
||||
"The current connection may be lost if you are currently connecting via Cloudflare Tunnel. Are you sure want to stop it? Type your current password to confirm it.": "Текущее соединение может быть потеряно, если вы в данный момент подключаетесь через туннель Cloudflare. Вы уверены, что хотите это остановить? Введите свой текущий пароль, чтобы подтвердить это.",
|
||||
"HTTP Headers": "заголовки HTTP",
|
||||
"HTTP Headers": "Заголовки HTTP",
|
||||
"Trust Proxy": "Доверять прокси",
|
||||
"Other Software": "Другое программное обеспечение",
|
||||
"For example: nginx, Apache and Traefik.": "К примеру: nginx, Apache и Traefik.",
|
||||
@@ -544,7 +542,7 @@
|
||||
"Container Name / ID": "Название контейнера / ID",
|
||||
"Docker Host": "Хост Docker",
|
||||
"Docker Hosts": "Хосты Docker",
|
||||
"ntfy Topic": "тема ntfy",
|
||||
"ntfy Topic": "Тема ntfy",
|
||||
"Domain": "Домен",
|
||||
"Workstation": "Рабочая станция",
|
||||
"disableCloudflaredNoAuthMsg": "Вы находитесь в режиме без авторизации, пароль не требуется.",
|
||||
@@ -641,7 +639,7 @@
|
||||
"Server Timezone": "Часовой пояс сервера",
|
||||
"statusPageMaintenanceEndDate": "Конец",
|
||||
"IconUrl": "URL иконки",
|
||||
"Enable DNS Cache": "Включить DNS кэш",
|
||||
"Enable DNS Cache": "Включить DNS кэш для мониторов HTTP(S)",
|
||||
"Enable": "Включить",
|
||||
"Disable": "Отключить",
|
||||
"Single Maintenance Window": "Единое окно техбслуживания",
|
||||
@@ -654,7 +652,7 @@
|
||||
"enableGRPCTls": "Разрешить отправлять gRPC запрос через TLS соединение",
|
||||
"Free Mobile API Key": "API ключ Free Mobile",
|
||||
"Edit Tag": "Редактировать тэг",
|
||||
"webhookAdditionalHeadersDesc": "Устанавливает дополнительные заголовки, отправляемые с помощью веб-хука.",
|
||||
"webhookAdditionalHeadersDesc": "Устанавливает дополнительные заголовки, отправляемые с помощью веб-хука. Каждый заголовок должен быть определён как JSON ключ/значение.",
|
||||
"topic": "Тема",
|
||||
"Customize": "Персонализировать",
|
||||
"Custom Footer": "Пользовательский footer",
|
||||
@@ -748,9 +746,9 @@
|
||||
"Proto Method": "Метод Proto",
|
||||
"Proto Content": "Содержание Proto",
|
||||
"telegramMessageThreadID": "(Необязательно) ID цепочки сообщений",
|
||||
"statusPageRefreshIn": "Обновлять каждые: {0}",
|
||||
"statusPageRefreshIn": "Обновление через: {0}",
|
||||
"twilioAccountSID": "SID учетной записи",
|
||||
"twilioAuthToken": "Токен авторизации",
|
||||
"twilioAuthToken": "Токен авторизации / Секретный API ключ",
|
||||
"twilioFromNumber": "С номера",
|
||||
"twilioToNumber": "На номер",
|
||||
"sameAsServerTimezone": "Аналогично часовому поясу сервера",
|
||||
@@ -767,7 +765,7 @@
|
||||
"Badge Type": "Тип значка",
|
||||
"Badge Duration": "Срок действия значка",
|
||||
"Badge Label": "Надпись для значка",
|
||||
"Badge Prefix": "Префикс значка",
|
||||
"Badge Prefix": "Значение префикса значка",
|
||||
"Badge Label Color": "Цвет надписи значка",
|
||||
"Badge Color": "Цвет значка",
|
||||
"Badge Label Prefix": "Префикс надписи для значка",
|
||||
@@ -776,7 +774,7 @@
|
||||
"Badge Pending Color": "Цвет значка для статуса \"Ожидание\"",
|
||||
"Badge Maintenance Color": "Цвет значка для статуса \"Техобслуживание\"",
|
||||
"Badge Style": "Стиль значка",
|
||||
"Badge Suffix": "Суффикс значка",
|
||||
"Badge Suffix": "Значение суффикса значка",
|
||||
"Badge value (For Testing only.)": "Значение значка (только для тестирования)",
|
||||
"Badge URL": "URL значка",
|
||||
"Group": "Группа",
|
||||
@@ -793,5 +791,61 @@
|
||||
"Badge Down Days": "Значок для \"дней недоступности\"",
|
||||
"Home": "Главная",
|
||||
"noGroupMonitorMsg": "Не доступно. Создайте сначала группу мониторов.",
|
||||
"Close": "Закрыть"
|
||||
"Close": "Закрыть",
|
||||
"chromeExecutableDescription": "Для пользователей Docker, если Chromium еще не установлен, может потребоваться несколько минут для установки и отображения результата тестирования. Он занимает 1 ГБ дискового пространства.",
|
||||
"chromeExecutable": "Исполняемый файл Chrome/Chromium",
|
||||
"chromeExecutableAutoDetect": "Автообнаружение",
|
||||
"Badge Preview": "Предпросмотр значка",
|
||||
"Badge Duration (in hours)": "Срок действия значка (в часах)",
|
||||
"twilioApiKey": "API ключ (необязательно)",
|
||||
"Expected Value": "Ожидаемое значение",
|
||||
"Json Query": "JSON запрос",
|
||||
"Kafka Brokers": "Kafka Brokers",
|
||||
"Press Enter to add broker": "Нажмите Enter чтобы добавить брокера",
|
||||
"Kafka Topic Name": "Название темы Kafka",
|
||||
"Kafka Producer Message": "Сообщение продюсера Kafka",
|
||||
"Kafka SASL Options": "Параметры SASL в Kafka",
|
||||
"Mechanism": "Механизм",
|
||||
"Pick a SASL Mechanism...": "Выберите механизм SASL...",
|
||||
"AccessKey Id": "AccessKey Id",
|
||||
"Secret AccessKey": "Секретный ключ доступа",
|
||||
"Session Token": "Токен сессии",
|
||||
"jsonQueryDescription": "Выполните json-запрос к ответу и проверьте наличие ожидаемого значения (возвращаемое значение будет преобразовано в строку для сравнения). Посмотрите <a href='https://jsonata.org/'>jsonata.org</a> для получения документации по языку запросов. A Потренироваться вы можете <a href='https://try.jsonata.org/'>сдесь</a>.",
|
||||
"Notify Channel": "Канал оповещений",
|
||||
"aboutNotifyChannel": "Уведомление о канале вызовет настольное или мобильное уведомление для всех участников канала, независимо от того, установлена ли их доступность как активная или отсутствующая.",
|
||||
"Enter the list of brokers": "Введите список брокеров",
|
||||
"Enable Kafka SSL": "Включение протокола Kafka SSL",
|
||||
"Enable Kafka Producer Auto Topic Creation": "Включение автоматического создания тем в Kafka Producer",
|
||||
"Authorization Identity": "Авторизационная идентичность",
|
||||
"Request Body": "Тело запроса",
|
||||
"webhookCustomBodyDesc": "Определите пользовательское HTTP Body для запроса. Принимаются шаблонные переменные {msg}, {heartbeat}, {monitor}.",
|
||||
"webhookBodyCustomOption": "Кастомное тело",
|
||||
"webhookBodyPresetOption": "Пресет - {}",
|
||||
"invertKeywordDescription": "Искать, чтобы ключевое слово отсутствовало, а не присутствовало.",
|
||||
"filterActive": "Активный",
|
||||
"filterActivePaused": "На паузе",
|
||||
"Invert Keyword": "Инвертировать ключевое слово",
|
||||
"tailscalePingWarning": "Для того чтобы использовать монитор Tailscale Ping, необходимо установить Uptime Kuma без Docker, а также установить на сервер клиент Tailscale.",
|
||||
"PushDeer Server": "Сервер PushDeer",
|
||||
"pushDeerServerDescription": "Оставьте пустым для использования официального сервера",
|
||||
"showCertificateExpiry": "Показывать истекающий сертификат",
|
||||
"Request Timeout": "Тайм-Аут запроса",
|
||||
"timeoutAfter": "Тайм-Аут через {0} секунд",
|
||||
"Select": "Выбрать",
|
||||
"selectedMonitorCount": "Выбрано: {0}",
|
||||
"Check/Uncheck": "Отметить/Снять",
|
||||
"gamedigGuessPort": "Gamedig: Угадай порт",
|
||||
"styleElapsedTime": "Прошедшее время под полосой частоты опроса",
|
||||
"noOrBadCertificate": "Отсутствие сертификата",
|
||||
"gamedigGuessPortDescription": "Порт, используемый протоколом Valve Server Query Protocol, может отличаться от порта клиента. Попробуйте это сделать, если монитор не может подключиться к серверу.",
|
||||
"nostrSender": "Закрытый ключ отправителя (nsec)",
|
||||
"wayToGetFlashDutyKey": "Вы можете перейти на страницу \"Канал\" -> (Выберите канал) -> \"Интеграции\" -> \"Добавить новую страницу интеграции\", добавить \"Пользовательское событие\", чтобы получить push-адрес, скопировать ключ интеграции в адрес. Для получения дополнительной информации, пожалуйста, посетите",
|
||||
"styleElapsedTimeShowNoLine": "Показать (Без линии)",
|
||||
"styleElapsedTimeShowWithLine": "Показать (С линией)",
|
||||
"Server URL should not contain the nfty topic": "URL сервера не должен содержать тему nfty",
|
||||
"nostrRecipients": "Открытые ключи получателей (npub)",
|
||||
"nostrRecipientsHelp": "формат npub, по одному в строке",
|
||||
"FlashDuty Severity": "Серьёзность",
|
||||
"nostrRelays": "Реле Nostr",
|
||||
"nostrRelaysHelp": "Один URL-адрес ретрансляции в каждой строке"
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user