Compare commits
276 Commits
1.19.0-bet
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
6ccf741bc4 | ||
|
0a58069742 | ||
|
2b57b3e863 | ||
|
6cd6a2edf0 | ||
|
6961b1bdd2 | ||
|
54d4c4d3f7 | ||
|
c47b6c5995 | ||
|
7ef404ccc1 | ||
|
a5ff27da7a | ||
|
7bb12a7e00 | ||
|
27585d0812 | ||
|
e675316635 | ||
|
b073ec2287 | ||
|
31f45dcfc9 | ||
|
49ac71e25c | ||
|
1a9b013fc2 | ||
|
1326761a8a | ||
|
e48a987b9c | ||
|
712a3c29d4 | ||
|
e9497ac1ab | ||
|
6437ef198f | ||
|
973d5692d0 | ||
|
468cb004d6 | ||
|
f7d41a30fa | ||
|
0ef686ac2f | ||
|
3b5893ea60 | ||
|
21cd4d64c3 | ||
|
db757123ba | ||
|
4dcf31621e | ||
|
9c1ba97e7d | ||
|
e9564619f1 | ||
|
8433bceb32 | ||
|
98d001b38b | ||
|
d9f12a6376 | ||
|
56ba133a1f | ||
|
ec30147a7f | ||
|
2bc165379a | ||
|
2172112144 | ||
|
ecd661c801 | ||
|
8fab7112a1 | ||
|
cc4ed308b0 | ||
|
362280af14 | ||
|
636fc8fcfc | ||
|
1c05ba09dc | ||
|
1565da87cf | ||
|
890e3abf58 | ||
|
33355c51b7 | ||
|
5f5c2d7c46 | ||
|
71f4ab0aa6 | ||
|
24d1dd4c34 | ||
|
c00abac834 | ||
|
439f963749 | ||
|
f15c6470af | ||
|
32f7a0084a | ||
|
f8658d6160 | ||
|
d596f8f7eb | ||
|
23a525e36a | ||
|
221d1d40f5 | ||
|
60ec87941e | ||
|
e8e4361e09 | ||
|
675806829c | ||
|
21c1921867 | ||
|
e490ec6d29 | ||
|
7ad4392529 | ||
|
f3d3e064f8 | ||
|
80c91b8234 | ||
|
dc8289df12 | ||
|
caff9ca736 | ||
|
c7eb72e73b | ||
|
fc5ec5f492 | ||
|
40ebc2df79 | ||
|
abf5e435fe | ||
|
8a372201f1 | ||
|
8ec240fe19 | ||
|
5362aab0e5 | ||
|
bc7271b99c | ||
|
4ebf5a5f07 | ||
|
fbceefec36 | ||
|
0b959514f8 | ||
|
4c5e2ea237 | ||
|
7d92351568 | ||
|
494c53971c | ||
|
fc1914bccd | ||
|
4239cf4255 | ||
|
c196c34840 | ||
|
554402b484 | ||
|
b049e4e1b4 | ||
|
90a2668272 | ||
|
49b5de7d40 | ||
|
69e1880cd3 | ||
|
610b6248aa | ||
|
e591647b60 | ||
|
da99833d4c | ||
|
4bf23fdd1a | ||
|
f99a64da67 | ||
|
2e022789f6 | ||
|
73835f3328 | ||
|
4819112e25 | ||
|
2d3fd738e4 | ||
|
edd8fe2e22 | ||
|
204792dd2d | ||
|
b8e8c1b9db | ||
|
0cead83705 | ||
|
7b0093b8b3 | ||
|
cd7e362b81 | ||
|
a8af2a418e | ||
|
39ac9b887e | ||
|
28c3291020 | ||
|
50711391d1 | ||
|
e88e10cc8e | ||
|
27146ffeef | ||
|
41a9f2ff8a | ||
|
cd7a6e4019 | ||
|
8bb064c6fa | ||
|
1006fbd873 | ||
|
5554432b31 | ||
|
d111db0321 | ||
|
4147a4c404 | ||
|
8c684e9293 | ||
|
f025de6eaf | ||
|
7f394d0630 | ||
|
9fe9e235ca | ||
|
50b84f5f45 | ||
|
c60b741406 | ||
|
f6ea1fe9a5 | ||
|
b7e3ec2372 | ||
|
625fd7c2aa | ||
|
aec80b53d5 | ||
|
06852bbf0d | ||
|
056d957c1e | ||
|
e12225e595 | ||
|
1b6c587cc9 | ||
|
4a1db336df | ||
|
9e9c5cd1d2 | ||
|
1e689d99b4 | ||
|
14fffcf06b | ||
|
6962e056ce | ||
|
b7a898326e | ||
|
a89be0e6d4 | ||
|
b3ac7c3d43 | ||
|
c79b2913a2 | ||
|
b58b83541b | ||
|
df4f91c20d | ||
|
fc6b040a4e | ||
|
9838f36b50 | ||
|
a60072adbc | ||
|
e7aeb6f6bf | ||
|
06e570c52d | ||
|
890b8f8333 | ||
|
df21f7da76 | ||
|
3ea6711053 | ||
|
20c37a70f7 | ||
|
b75db27658 | ||
|
765d8e1297 | ||
|
9dc2cc1f0d | ||
|
2532becf61 | ||
|
6154776b34 | ||
|
7e6b92203d | ||
|
1da00d19fd | ||
|
da4bdab4f6 | ||
|
86ab97ef56 | ||
|
345b0c1829 | ||
|
2ac87fcea7 | ||
|
4862bec965 | ||
|
aa784fb3b2 | ||
|
466b403a96 | ||
|
f32441e2f6 | ||
|
39987ba9ac | ||
|
3b87209e26 | ||
|
e6dc0a0293 | ||
|
5c5a339a36 | ||
|
3040bd41d9 | ||
|
3b58fd3b3c | ||
|
bc86f8bb5f | ||
|
ab5f6dc82c | ||
|
5176fd02c1 | ||
|
02b5cae577 | ||
|
54aa7d5dca | ||
|
4cd5b5563f | ||
|
f1c30204b6 | ||
|
9da28fbbc7 | ||
|
851a04b082 | ||
|
68bc7ac421 | ||
|
73bfdb9ef9 | ||
|
e478084ff9 | ||
|
2dff7dd380 | ||
|
9bfa43100b | ||
|
ad5e1957b1 | ||
|
cc68ebca39 | ||
|
aa27d976c2 | ||
|
ecbc0f0477 | ||
|
f8c7da7995 | ||
|
f3660a0cec | ||
|
92caec95fe | ||
|
2c3abdc146 | ||
|
b1170211b7 | ||
|
eadf2c810a | ||
|
8aa97635ec | ||
|
ee1a56caae | ||
|
e886df4788 | ||
|
5196abfd36 | ||
|
3e68cf2a1c | ||
|
0ab82e6de3 | ||
|
8cdbe37f6f | ||
|
28d13e198c | ||
|
14a062804e | ||
|
cf3e03ab40 | ||
|
191f3ad53b | ||
|
370d522920 | ||
|
e0a1ad8a1c | ||
|
9720006934 | ||
|
cd270bd8b5 | ||
|
bc3229828e | ||
|
a5f23b9839 | ||
|
2052fa175f | ||
|
15b63c82c3 | ||
|
b053bc61ce | ||
|
0e30843a75 | ||
|
2103edb604 | ||
|
b059a36e66 | ||
|
258ff56962 | ||
|
cb4e512dc6 | ||
|
4042c26390 | ||
|
5da2315534 | ||
|
204015f1f5 | ||
|
cc6d17d2e0 | ||
|
68862c0b3f | ||
|
fd15e7c2dc | ||
|
5c4cf68937 | ||
|
214ddc264d | ||
|
2ea71839d1 | ||
|
54efde8185 | ||
|
705124d4ac | ||
|
1cb6940590 | ||
|
0f8ad288f3 | ||
|
434174d350 | ||
|
3d1237ed53 | ||
|
b459408b10 | ||
|
f04fe4d230 | ||
|
e579610426 | ||
|
b115d3f8b9 | ||
|
134b3b8ac1 | ||
|
e7e7751e7b | ||
|
5cd58e6fa3 | ||
|
08763b700a | ||
|
781f855921 | ||
|
9e81fe120f | ||
|
7313aa6563 | ||
|
c7871427c3 | ||
|
c0e67b6de9 | ||
|
a17084f75d | ||
|
e4fe7b802a | ||
|
5761bc9b90 | ||
|
92ea019fd4 | ||
|
a774b37369 | ||
|
6b47ad07ca | ||
|
1e8a16504b | ||
|
b879428a03 | ||
|
3c5de1c889 | ||
|
a42f7416b5 | ||
|
f9be918246 | ||
|
314ae38f91 | ||
|
c03d911657 | ||
|
e12642cf21 | ||
|
618d904001 | ||
|
6f86236b63 | ||
|
68875c3091 | ||
|
f35d7c0a1a | ||
|
3a90d246a4 | ||
|
6bb79597e8 | ||
|
34ab6142db | ||
|
2232236a7a | ||
|
dcecd10c88 | ||
|
af07c7f050 | ||
|
95dba6dcaf | ||
|
90c2bf7c94 |
@ -31,6 +31,9 @@ tsconfig.json
|
||||
/tmp
|
||||
/babel.config.js
|
||||
/ecosystem.config.js
|
||||
/extra/healthcheck.exe
|
||||
/extra/healthcheck
|
||||
|
||||
|
||||
### .gitignore content (commented rules are duplicated)
|
||||
|
||||
|
@ -19,3 +19,6 @@ indent_size = 2
|
||||
|
||||
[*.vue]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.go]
|
||||
indent_style = tab
|
||||
|
18
.github/workflows/auto-test.yml
vendored
18
.github/workflows/auto-test.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
node: [ 14, 16, 17, 18 ]
|
||||
node: [ 14, 16, 18, 19 ]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
@ -66,3 +66,19 @@ jobs:
|
||||
- run: npm install
|
||||
- run: npm run build
|
||||
- run: npm run cy:test
|
||||
|
||||
frontend-unit-tests:
|
||||
needs: [ check-linters ]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: git config --global core.autocrlf false # Mainly for Windows
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Use Node.js 14
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 14
|
||||
cache: 'npm'
|
||||
- run: npm install
|
||||
- run: npm run build
|
||||
- run: npm run cy:run:unit
|
||||
|
6
.github/workflows/stale-bot.yml
vendored
6
.github/workflows/stale-bot.yml
vendored
@ -12,13 +12,11 @@ jobs:
|
||||
- uses: actions/stale@v5
|
||||
with:
|
||||
stale-issue-message: 'We are clearing up our old issues and your ticket has been open for 3 months with no activity. Remove stale label or comment or this will be closed in 2 days.'
|
||||
stale-pr-message: 'We are clearing up our old Pull Requests and yours has been open for 3 months with no activity. Remove stale label or comment or this will be closed in 2 days.'
|
||||
close-issue-message: 'This issue was closed because it has been stalled for 2 days with no activity.'
|
||||
close-pr-message: 'This PR was closed because it has been stalled for 2 days with no activity.'
|
||||
days-before-stale: 90
|
||||
days-before-close: 2
|
||||
days-before-pr-stale: 999999999
|
||||
days-before-pr-close: 1
|
||||
exempt-issue-labels: 'News,Medium,High,discussion,bug,doc,feature-request'
|
||||
exempt-pr-labels: 'awaiting-approval,work-in-progress,enhancement,feature-request'
|
||||
exempt-issue-assignees: 'louislam'
|
||||
exempt-pr-assignees: 'louislam'
|
||||
operations-per-run: 200
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -16,3 +16,7 @@ dist-ssr
|
||||
|
||||
cypress/videos
|
||||
cypress/screenshots
|
||||
|
||||
/extra/healthcheck.exe
|
||||
/extra/healthcheck
|
||||
/extra/healthcheck-armv7
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Project Info
|
||||
|
||||
First of all, thank you everyone who made pull requests for Uptime Kuma, I never thought GitHub Community can be that nice! And also because of this, I also never thought other people actually read my code and edit my code. It is not structured and commented so well, lol. Sorry about that.
|
||||
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.
|
||||
|
||||
@ -27,7 +27,7 @@ The frontend code build into "dist" directory. The server (express.js) exposes t
|
||||
|
||||
## Can I create a pull request for Uptime Kuma?
|
||||
|
||||
Yes or no, it depends on what you will try to do. Since I don't want to waste your time, be sure to **create an empty draft pull request or open an issue, so we can discuss 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 it will be merged or not.
|
||||
|
||||
Here are some references:
|
||||
|
||||
@ -48,8 +48,13 @@ Here are some references:
|
||||
- UI/UX is not close to Uptime Kuma
|
||||
- Existing logic is completely modified or deleted for no reason
|
||||
- A function that is completely out of scope
|
||||
- Convert existing code into other programming languages
|
||||
- Unnecessary large code changes (Hard to review, causes code conflicts to other pull requests)
|
||||
|
||||
The above cases cannot cover all 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.
|
||||
|
||||
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.
|
||||
@ -72,9 +77,9 @@ Before deep into coding, discussion first is preferred. Creating an empty pull r
|
||||
|
||||
## Project Styles
|
||||
|
||||
I personally do not like something need to learn so much and need to config so much before you can finally start the app.
|
||||
I personally do not like it when something requires so much learning and configuration before you can finally start the app.
|
||||
|
||||
- Easy to install for non-Docker users, no native build dependency is needed (at least for x86_64), no extra config, no extra effort to get it run
|
||||
- Easy to install for non-Docker users, no native build dependency is needed (at least for x86_64), no extra config, 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`.
|
||||
- Easy to use
|
||||
@ -172,15 +177,11 @@ The data and socket logic are in `src/mixins/socket.js`.
|
||||
|
||||
## Unit Test
|
||||
|
||||
It is an end-to-end testing. It is using Jest and Puppeteer.
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
npm test
|
||||
```
|
||||
|
||||
By default, the Chromium window will be shown up during the test. Specifying `HEADLESS_TEST=1` for terminal environments.
|
||||
|
||||
## Dependencies
|
||||
|
||||
Both frontend and backend share the same package.json. However, the frontend dependencies are eventually not used in the production environment, because it is usually also baked into dist files. So:
|
||||
|
30
README.md
30
README.md
@ -7,9 +7,9 @@
|
||||
<img src="./public/icon.svg" width="128" alt="" />
|
||||
</div>
|
||||
|
||||
It is a self-hosted monitoring tool like "Uptime Robot".
|
||||
Uptime Kuma is an easy-to-use self-hosted monitoring tool.
|
||||
|
||||
<img src="https://uptime.kuma.pet/img/dark.jpg" width="700" alt="" />
|
||||
<img src="https://user-images.githubusercontent.com/1336778/212262296-e6205815-ad62-488c-83ec-a5b0d0689f7c.jpg" width="700" alt="" />
|
||||
|
||||
## 🥔 Live Demo
|
||||
|
||||
@ -22,17 +22,17 @@ It is a temporary live demo, all data will be deleted after 10 minutes. Use the
|
||||
|
||||
## ⭐ Features
|
||||
|
||||
* Monitoring uptime for HTTP(s) / TCP / HTTP(s) Keyword / 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.
|
||||
* Monitoring uptime for HTTP(s) / TCP / HTTP(s) Keyword / 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
|
||||
* [Multi Languages](https://github.com/louislam/uptime-kuma/tree/master/src/languages)
|
||||
* Multiple Status Pages
|
||||
* Map Status Page to Domain
|
||||
* Ping Chart
|
||||
* Certificate Info
|
||||
* Proxy Support
|
||||
* 2FA available
|
||||
* Multiple status pages
|
||||
* Map status pages to specific domains
|
||||
* Ping chart
|
||||
* Certificate info
|
||||
* Proxy support
|
||||
* 2FA support
|
||||
|
||||
## 🔧 How to Install
|
||||
|
||||
@ -44,14 +44,14 @@ docker run -d --restart=always -p 3001:3001 -v uptime-kuma:/app/data --name upti
|
||||
|
||||
⚠️ Please use a **local volume** only. Other types such as NFS are not supported.
|
||||
|
||||
Browse to http://localhost:3001 after starting.
|
||||
Uptime Kuma is now running on http://localhost:3001
|
||||
|
||||
### 💪🏻 Non-Docker
|
||||
|
||||
Required Tools:
|
||||
- [Node.js](https://nodejs.org/en/download/) >= 14
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
- [pm2](https://pm2.keymetrics.io/) - For run in background
|
||||
- [pm2](https://pm2.keymetrics.io/) - For running Uptime Kuma in the background
|
||||
|
||||
```bash
|
||||
# Update your npm to the latest version
|
||||
@ -73,7 +73,7 @@ pm2 start server/server.js --name uptime-kuma
|
||||
|
||||
|
||||
```
|
||||
Browse to http://localhost:3001 after starting.
|
||||
Uptime Kuma is now running on http://localhost:3001
|
||||
|
||||
More useful PM2 Commands
|
||||
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please report security issues to uptime@kuma.pet.
|
||||
Please report security issues to https://github.com/louislam/uptime-kuma/security/advisories/new.
|
||||
|
||||
Do not use the 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 the public as it will cause more damage.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
|
10
config/cypress.frontend.config.js
Normal file
10
config/cypress.frontend.config.js
Normal file
@ -0,0 +1,10 @@
|
||||
const { defineConfig } = require("cypress");
|
||||
|
||||
module.exports = defineConfig({
|
||||
e2e: {
|
||||
supportFile: false,
|
||||
specPattern: [
|
||||
"test/cypress/unit/**/*.js"
|
||||
],
|
||||
}
|
||||
});
|
25
db/patch-grpc-monitor.sql
Normal file
25
db/patch-grpc-monitor.sql
Normal file
@ -0,0 +1,25 @@
|
||||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
ALTER TABLE monitor
|
||||
ADD grpc_url VARCHAR(255) default null;
|
||||
|
||||
ALTER TABLE monitor
|
||||
ADD grpc_protobuf TEXT default null;
|
||||
|
||||
ALTER TABLE monitor
|
||||
ADD grpc_body TEXT default null;
|
||||
|
||||
ALTER TABLE monitor
|
||||
ADD grpc_metadata TEXT default null;
|
||||
|
||||
ALTER TABLE monitor
|
||||
ADD grpc_method VARCHAR(255) default null;
|
||||
|
||||
ALTER TABLE monitor
|
||||
ADD grpc_service_name VARCHAR(255) default null;
|
||||
|
||||
ALTER TABLE monitor
|
||||
ADD grpc_enable_tls BOOLEAN default 0 not null;
|
||||
|
||||
COMMIT;
|
@ -4,5 +4,5 @@ WORKDIR /app
|
||||
|
||||
# Install apprise, iputils for non-root ping, setpriv
|
||||
RUN apk add --no-cache iputils setpriv dumb-init python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib && \
|
||||
pip3 --no-cache-dir install apprise==1.0.0 && \
|
||||
pip3 --no-cache-dir install apprise==1.2.1 && \
|
||||
rm -rf /root/.cache
|
||||
|
16
docker/builder-go.dockerfile
Normal file
16
docker/builder-go.dockerfile
Normal file
@ -0,0 +1,16 @@
|
||||
############################################
|
||||
# Build in Golang
|
||||
# Run npm run build-healthcheck-armv7 in the host first, another it will be super slow where it is building the armv7 healthcheck
|
||||
############################################
|
||||
FROM golang:1.19.4-buster
|
||||
WORKDIR /app
|
||||
ARG TARGETPLATFORM
|
||||
COPY ./extra/ ./extra/
|
||||
|
||||
# Compile healthcheck.go
|
||||
RUN apt update && \
|
||||
apt --yes --no-install-recommends install curl && \
|
||||
curl -sL https://deb.nodesource.com/setup_18.x | bash && \
|
||||
apt --yes --no-install-recommends install nodejs && \
|
||||
node ./extra/build-healthcheck.js $TARGETPLATFORM && \
|
||||
apt --yes remove nodejs
|
@ -11,7 +11,7 @@ WORKDIR /app
|
||||
RUN apt update && \
|
||||
apt --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 && \
|
||||
pip3 --no-cache-dir install apprise==1.0.0 && \
|
||||
pip3 --no-cache-dir install apprise==1.2.1 && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
apt --yes autoremove
|
||||
|
||||
|
@ -1,30 +1,50 @@
|
||||
############################################
|
||||
# Build in Golang
|
||||
# Run npm run build-healthcheck-armv7 in the host first, another it will be super slow where it is building the armv7 healthcheck
|
||||
# Check file: builder-go.dockerfile
|
||||
############################################
|
||||
FROM louislam/uptime-kuma:builder-go AS build_healthcheck
|
||||
|
||||
############################################
|
||||
# Build in Node.js
|
||||
############################################
|
||||
FROM louislam/uptime-kuma:base-debian AS build
|
||||
WORKDIR /app
|
||||
|
||||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1
|
||||
|
||||
COPY .npmrc .npmrc
|
||||
COPY package.json package.json
|
||||
COPY package-lock.json package-lock.json
|
||||
RUN npm ci --omit=dev
|
||||
COPY . .
|
||||
RUN npm ci --production && \
|
||||
chmod +x /app/extra/entrypoint.sh
|
||||
|
||||
COPY --from=build_healthcheck /app/extra/healthcheck /app/extra/healthcheck
|
||||
RUN chmod +x /app/extra/entrypoint.sh
|
||||
|
||||
############################################
|
||||
# ⭐ Main Image
|
||||
############################################
|
||||
FROM louislam/uptime-kuma:base-debian AS release
|
||||
WORKDIR /app
|
||||
|
||||
# Copy app files from build layer
|
||||
COPY --from=build /app /app
|
||||
|
||||
|
||||
EXPOSE 3001
|
||||
VOLUME ["/app/data"]
|
||||
HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --retries=5 CMD node extra/healthcheck.js
|
||||
HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --retries=5 CMD extra/healthcheck
|
||||
ENTRYPOINT ["/usr/bin/dumb-init", "--", "extra/entrypoint.sh"]
|
||||
CMD ["node", "server/server.js"]
|
||||
|
||||
|
||||
############################################
|
||||
# Mark as Nightly
|
||||
############################################
|
||||
FROM release AS nightly
|
||||
RUN npm run mark-as-nightly
|
||||
|
||||
############################################
|
||||
# Build an image for testing pr
|
||||
############################################
|
||||
FROM louislam/uptime-kuma:base-debian AS pr-test
|
||||
|
||||
WORKDIR /app
|
||||
@ -54,8 +74,9 @@ VOLUME ["/app/data"]
|
||||
HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --retries=5 CMD node extra/healthcheck.js
|
||||
CMD ["npm", "run", "start-pr-test"]
|
||||
|
||||
|
||||
############################################
|
||||
# Upload the artifact to Github
|
||||
############################################
|
||||
FROM louislam/uptime-kuma:base-debian AS upload-artifact
|
||||
WORKDIR /
|
||||
RUN apt update && \
|
||||
|
@ -3,10 +3,12 @@ WORKDIR /app
|
||||
|
||||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1
|
||||
|
||||
COPY .npmrc .npmrc
|
||||
COPY package.json package.json
|
||||
COPY package-lock.json package-lock.json
|
||||
RUN npm ci --omit=dev
|
||||
COPY . .
|
||||
RUN npm ci --production && \
|
||||
chmod +x /app/extra/entrypoint.sh
|
||||
|
||||
RUN chmod +x /app/extra/entrypoint.sh
|
||||
|
||||
FROM louislam/uptime-kuma:base-alpine AS release
|
||||
WORKDIR /app
|
||||
|
@ -32,6 +32,10 @@ if (! exists) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit updated files
|
||||
* @param {string} version Version to update to
|
||||
*/
|
||||
function commit(version) {
|
||||
let msg = "Update to " + version;
|
||||
|
||||
@ -47,6 +51,10 @@ function commit(version) {
|
||||
console.log(res.stdout.toString().trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a tag with the specified version
|
||||
* @param {string} version Tag to create
|
||||
*/
|
||||
function tag(version) {
|
||||
let res = childProcess.spawnSync("git", [ "tag", version ]);
|
||||
console.log(res.stdout.toString().trim());
|
||||
@ -55,6 +63,11 @@ function tag(version) {
|
||||
console.log(res.stdout.toString().trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a tag exists for the specified version
|
||||
* @param {string} version Version to check
|
||||
* @returns {boolean} Does the tag already exist
|
||||
*/
|
||||
function tagExists(version) {
|
||||
if (! version) {
|
||||
throw new Error("invalid version");
|
||||
|
27
extra/build-healthcheck.js
Normal file
27
extra/build-healthcheck.js
Normal file
@ -0,0 +1,27 @@
|
||||
const childProcess = require("child_process");
|
||||
const fs = require("fs");
|
||||
const platform = process.argv[2];
|
||||
|
||||
if (!platform) {
|
||||
console.error("No platform??");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (platform === "linux/arm/v7") {
|
||||
console.log("Arch: armv7");
|
||||
if (fs.existsSync("./extra/healthcheck-armv7")) {
|
||||
fs.renameSync("./extra/healthcheck-armv7", "./extra/healthcheck");
|
||||
console.log("Already built in the host, skip.");
|
||||
process.exit(0);
|
||||
} else {
|
||||
console.log("prebuilt not found, it will be slow! You should execute `npm run build-healthcheck-armv7` before build.");
|
||||
}
|
||||
} else {
|
||||
if (fs.existsSync("./extra/healthcheck-armv7")) {
|
||||
fs.rmSync("./extra/healthcheck-armv7");
|
||||
}
|
||||
}
|
||||
|
||||
const output = childProcess.execSync("go build -x -o ./extra/healthcheck ./extra/healthcheck.go").toString("utf8");
|
||||
console.log(output);
|
||||
|
@ -25,6 +25,10 @@ if (platform === "linux/amd64") {
|
||||
const file = fs.createWriteStream("cloudflared.deb");
|
||||
get("https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-" + arch + ".deb");
|
||||
|
||||
/**
|
||||
* Download specified file
|
||||
* @param {string} url URL to request
|
||||
*/
|
||||
function get(url) {
|
||||
http.get(url, function (res) {
|
||||
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
||||
|
81
extra/healthcheck.go
Normal file
81
extra/healthcheck.go
Normal file
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* If changed, have to run `npm run build-docker-builder-go`.
|
||||
* This script should be run after a period of time (180s), because the server may need some time to prepare.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
isFreeBSD := runtime.GOOS == "freebsd"
|
||||
|
||||
// process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
||||
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
|
||||
client := http.Client{
|
||||
Timeout: 28 * time.Second,
|
||||
}
|
||||
|
||||
sslKey := os.Getenv("UPTIME_KUMA_SSL_KEY")
|
||||
if len(sslKey) == 0 {
|
||||
sslKey = os.Getenv("SSL_KEY")
|
||||
}
|
||||
|
||||
sslCert := os.Getenv("UPTIME_KUMA_SSL_CERT")
|
||||
if len(sslCert) == 0 {
|
||||
sslCert = os.Getenv("SSL_CERT")
|
||||
}
|
||||
|
||||
hostname := os.Getenv("UPTIME_KUMA_HOST")
|
||||
if len(hostname) == 0 && !isFreeBSD {
|
||||
hostname = os.Getenv("HOST")
|
||||
}
|
||||
if len(hostname) == 0 {
|
||||
hostname = "127.0.0.1"
|
||||
}
|
||||
|
||||
port := os.Getenv("UPTIME_KUMA_PORT")
|
||||
if len(port) == 0 {
|
||||
port = os.Getenv("PORT")
|
||||
}
|
||||
if len(port) == 0 {
|
||||
port = "3001"
|
||||
}
|
||||
|
||||
protocol := ""
|
||||
if len(sslKey) != 0 && len(sslCert) != 0 {
|
||||
protocol = "https"
|
||||
} else {
|
||||
protocol = "http"
|
||||
}
|
||||
|
||||
url := protocol + "://" + hostname + ":" + port
|
||||
|
||||
log.Println("Checking " + url)
|
||||
resp, err := client.Get(url)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
_, err = ioutil.ReadAll(resp.Body)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
log.Printf("Health Check OK [Res Code: %d]\n", resp.StatusCode)
|
||||
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
/*
|
||||
* ⚠️ Deprecated: Changed to healthcheck.go, it will be deleted in the future.
|
||||
* This script should be run after a period of time (180s), because the server may need some time to prepare.
|
||||
*/
|
||||
const { FBSD } = require("../server/util-server");
|
||||
|
@ -5,7 +5,7 @@ const util = require("../src/util");
|
||||
util.polyfill();
|
||||
|
||||
const oldVersion = pkg.version;
|
||||
const newVersion = oldVersion + "-nightly";
|
||||
const newVersion = oldVersion + "-nightly-" + util.genSecret(8);
|
||||
|
||||
console.log("Old Version: " + oldVersion);
|
||||
console.log("New Version: " + newVersion);
|
||||
|
@ -43,6 +43,11 @@ const main = async () => {
|
||||
console.log("Finished.");
|
||||
};
|
||||
|
||||
/**
|
||||
* Ask question of user
|
||||
* @param {string} question Question to ask
|
||||
* @returns {Promise<string>} Users response
|
||||
*/
|
||||
function question(question) {
|
||||
return new Promise((resolve) => {
|
||||
rl.question(question, (answer) => {
|
||||
|
@ -53,6 +53,11 @@ const main = async () => {
|
||||
console.log("Finished.");
|
||||
};
|
||||
|
||||
/**
|
||||
* Ask question of user
|
||||
* @param {string} question Question to ask
|
||||
* @returns {Promise<string>} Users response
|
||||
*/
|
||||
function question(question) {
|
||||
return new Promise((resolve) => {
|
||||
rl.question(question, (answer) => {
|
||||
|
@ -135,6 +135,11 @@ server.listen({
|
||||
udp: 5300
|
||||
});
|
||||
|
||||
/**
|
||||
* Get human readable request type from request code
|
||||
* @param {number} code Request code to translate
|
||||
* @returns {string} Human readable request type
|
||||
*/
|
||||
function type(code) {
|
||||
for (let name in Packet.TYPE) {
|
||||
if (Packet.TYPE[name] === code) {
|
||||
|
@ -11,6 +11,7 @@ class SimpleMqttServer {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
/** Start the MQTT server */
|
||||
start() {
|
||||
this.server.listen(this.port, () => {
|
||||
console.log("server started and listening on port ", this.port);
|
||||
|
@ -36,10 +36,8 @@ if (! exists) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the version number in package.json and commits it to git.
|
||||
* @param {string} version - The new version number
|
||||
*
|
||||
* Generated by Trelent
|
||||
* Commit updated files
|
||||
* @param {string} version Version to update to
|
||||
*/
|
||||
function commit(version) {
|
||||
let msg = "Update to " + version;
|
||||
@ -53,16 +51,19 @@ function commit(version) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a tag with the specified version
|
||||
* @param {string} version Tag to create
|
||||
*/
|
||||
function tag(version) {
|
||||
let res = childProcess.spawnSync("git", [ "tag", version ]);
|
||||
console.log(res.stdout.toString().trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given version is already tagged in the git repository.
|
||||
* @param {string} version - The version to check for.
|
||||
*
|
||||
* Generated by Trelent
|
||||
* Check if a tag exists for the specified version
|
||||
* @param {string} version Version to check
|
||||
* @returns {boolean} Does the tag already exist
|
||||
*/
|
||||
function tagExists(version) {
|
||||
if (! version) {
|
||||
|
@ -10,6 +10,10 @@ if (!newVersion) {
|
||||
|
||||
updateWiki(newVersion);
|
||||
|
||||
/**
|
||||
* Update the wiki with new version number
|
||||
* @param {string} newVersion Version to update to
|
||||
*/
|
||||
function updateWiki(newVersion) {
|
||||
const wikiDir = "./tmp/wiki";
|
||||
const howToUpdateFilename = "./tmp/wiki/🆙-How-to-Update.md";
|
||||
@ -39,6 +43,10 @@ function updateWiki(newVersion) {
|
||||
safeDelete(wikiDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a directory exists and then delete it
|
||||
* @param {string} dir Directory to delete
|
||||
*/
|
||||
function safeDelete(dir) {
|
||||
if (fs.existsSync(dir)) {
|
||||
fs.rm(dir, {
|
||||
|
8567
package-lock.json
generated
8567
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
27
package.json
27
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "uptime-kuma",
|
||||
"version": "1.19.0-beta.0",
|
||||
"version": "1.19.6",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -31,6 +31,7 @@
|
||||
"build-docker": "npm run build && npm run build-docker-debian && npm run build-docker-alpine",
|
||||
"build-docker-alpine-base": "docker buildx build -f docker/alpine-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base-alpine . --push",
|
||||
"build-docker-debian-base": "docker buildx build -f docker/debian-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base-debian . --push",
|
||||
"build-docker-builder-go": "docker buildx build -f docker/builder-go.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:builder-go . --push",
|
||||
"build-docker-alpine": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:$VERSION-alpine --target release . --push",
|
||||
"build-docker-debian": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:$VERSION -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:$VERSION-debian --target release . --push",
|
||||
"build-docker-nightly": "npm run build && docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push",
|
||||
@ -38,7 +39,7 @@
|
||||
"build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain",
|
||||
"build-docker-pr-test": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64 -t louislam/uptime-kuma:pr-test --target pr-test . --push",
|
||||
"upload-artifacts": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg VERSION --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain",
|
||||
"setup": "git checkout 1.18.5 && npm ci --production && npm run download-dist",
|
||||
"setup": "git checkout 1.19.6 && 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",
|
||||
@ -60,13 +61,17 @@
|
||||
"start-pr-test": "node extra/checkout-pr.js && npm install && npm run dev",
|
||||
"cy:test": "node test/prepare-test-server.js && node server/server.js --port=3002 --data-dir=./data/test/ --e2e",
|
||||
"cy:run": "npx cypress run --browser chrome --headless --config-file ./config/cypress.config.js",
|
||||
"cypress-open": "concurrently -k -r \"node test/prepare-test-server.js && node server/server.js --port=3002 --data-dir=./data/test/\" \"cypress open --config-file ./config/cypress.config.js\""
|
||||
"cy:run:unit": "npx cypress run --browser chrome --headless --config-file ./config/cypress.frontend.config.js",
|
||||
"cypress-open": "concurrently -k -r \"node test/prepare-test-server.js && node server/server.js --port=3002 --data-dir=./data/test/\" \"cypress open --config-file ./config/cypress.config.js\"",
|
||||
"build-healthcheck-armv7": "cross-env GOOS=linux GOARCH=arm GOARM=7 go build -x -o ./extra/healthcheck-armv7 ./extra/healthcheck.go"
|
||||
},
|
||||
"dependencies": {
|
||||
"@grpc/grpc-js": "~1.7.3",
|
||||
"@louislam/ping": "~0.4.2-mod.1",
|
||||
"@louislam/sqlite3": "15.1.2",
|
||||
"args-parser": "~1.3.0",
|
||||
"axios": "~0.27.0",
|
||||
"axios-ntlm": "~1.3.0",
|
||||
"axios-ntlm": "1.3.0",
|
||||
"badge-maker": "~3.3.1",
|
||||
"bcryptjs": "~2.4.3",
|
||||
"bree": "~7.1.5",
|
||||
@ -88,11 +93,13 @@
|
||||
"https-proxy-agent": "~5.0.1",
|
||||
"iconv-lite": "~0.6.3",
|
||||
"jsesc": "~3.0.2",
|
||||
"jsonwebtoken": "~8.5.1",
|
||||
"jsonwebtoken": "~9.0.0",
|
||||
"jwt-decode": "~3.1.2",
|
||||
"limiter": "~2.1.0",
|
||||
"mongodb": "~4.13.0",
|
||||
"mqtt": "~4.3.7",
|
||||
"mssql": "~8.1.4",
|
||||
"mysql2": "~2.3.3",
|
||||
"node-cloudflared-tunnel": "~1.0.9",
|
||||
"node-radius-client": "~1.0.0",
|
||||
"nodemailer": "~6.6.5",
|
||||
@ -102,9 +109,11 @@
|
||||
"pg-connection-string": "~2.5.0",
|
||||
"prom-client": "~13.2.0",
|
||||
"prometheus-api-metrics": "~3.2.1",
|
||||
"redbean-node": "0.1.4",
|
||||
"socket.io": "~4.4.1",
|
||||
"socket.io-client": "~4.4.1",
|
||||
"protobufjs": "~7.1.1",
|
||||
"redbean-node": "~0.2.0",
|
||||
"redis": "~4.5.1",
|
||||
"socket.io": "~4.5.3",
|
||||
"socket.io-client": "~4.5.3",
|
||||
"socks-proxy-agent": "6.1.1",
|
||||
"tar": "~6.1.11",
|
||||
"tcp-ping": "~0.1.1",
|
||||
@ -130,7 +139,7 @@
|
||||
"chart.js": "~3.6.2",
|
||||
"chartjs-adapter-dayjs": "~1.0.0",
|
||||
"concurrently": "^7.1.0",
|
||||
"core-js": "~3.18.3",
|
||||
"core-js": "~3.26.1",
|
||||
"cross-env": "~7.0.3",
|
||||
"cypress": "^10.1.0",
|
||||
"delay": "^5.0.0",
|
||||
|
@ -63,6 +63,12 @@ function myAuthorizer(username, password, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Use basic auth if auth is not disabled
|
||||
* @param {express.Request} req Express request object
|
||||
* @param {express.Response} res Express response object
|
||||
* @param {express.NextFunction} next
|
||||
*/
|
||||
exports.basicAuth = async function (req, res, next) {
|
||||
const middleware = basicAuth({
|
||||
authorizer: myAuthorizer,
|
||||
|
@ -1,6 +1,8 @@
|
||||
const https = require("https");
|
||||
const http = require("http");
|
||||
const CacheableLookup = require("cacheable-lookup");
|
||||
const { Settings } = require("./settings");
|
||||
const { log } = require("../src/util");
|
||||
|
||||
class CacheableDnsHttpAgent {
|
||||
|
||||
@ -9,14 +11,36 @@ class CacheableDnsHttpAgent {
|
||||
static httpAgentList = {};
|
||||
static httpsAgentList = {};
|
||||
|
||||
static enable = false;
|
||||
|
||||
/**
|
||||
* Register cacheable to global agents
|
||||
* Register/Disable cacheable to global agents
|
||||
*/
|
||||
static registerGlobalAgent() {
|
||||
static async update() {
|
||||
log.debug("CacheableDnsHttpAgent", "update");
|
||||
let isEnable = await Settings.get("dnsCache");
|
||||
|
||||
if (isEnable !== this.enable) {
|
||||
log.debug("CacheableDnsHttpAgent", "value changed");
|
||||
|
||||
if (isEnable) {
|
||||
log.debug("CacheableDnsHttpAgent", "enable");
|
||||
this.cacheable.install(http.globalAgent);
|
||||
this.cacheable.install(https.globalAgent);
|
||||
} else {
|
||||
log.debug("CacheableDnsHttpAgent", "disable");
|
||||
this.cacheable.uninstall(http.globalAgent);
|
||||
this.cacheable.uninstall(https.globalAgent);
|
||||
}
|
||||
}
|
||||
|
||||
this.enable = isEnable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach cacheable to HTTP agent
|
||||
* @param {http.Agent} agent Agent to install
|
||||
*/
|
||||
static install(agent) {
|
||||
this.cacheable.install(agent);
|
||||
}
|
||||
@ -26,6 +50,10 @@ class CacheableDnsHttpAgent {
|
||||
* @return {https.Agent}
|
||||
*/
|
||||
static getHttpsAgent(agentOptions) {
|
||||
if (!this.enable) {
|
||||
return new https.Agent(agentOptions);
|
||||
}
|
||||
|
||||
let key = JSON.stringify(agentOptions);
|
||||
if (!(key in this.httpsAgentList)) {
|
||||
this.httpsAgentList[key] = new https.Agent(agentOptions);
|
||||
@ -39,6 +67,10 @@ class CacheableDnsHttpAgent {
|
||||
* @return {https.Agents}
|
||||
*/
|
||||
static getHttpAgent(agentOptions) {
|
||||
if (!this.enable) {
|
||||
return new http.Agent(agentOptions);
|
||||
}
|
||||
|
||||
let key = JSON.stringify(agentOptions);
|
||||
if (!(key in this.httpAgentList)) {
|
||||
this.httpAgentList[key] = new http.Agent(agentOptions);
|
||||
|
@ -25,7 +25,7 @@ exports.startInterval = () => {
|
||||
let checkBeta = await setting("checkBeta");
|
||||
|
||||
if (checkBeta && res.data.beta) {
|
||||
if (compareVersions.compare(res.data.beta, res.data.beta, ">")) {
|
||||
if (compareVersions.compare(res.data.beta, res.data.slow, ">")) {
|
||||
exports.latestVersion = res.data.beta;
|
||||
return;
|
||||
}
|
||||
|
@ -62,6 +62,7 @@ class Database {
|
||||
"patch-add-clickable-status-page-link.sql": true,
|
||||
"patch-add-sqlserver-monitor.sql": true,
|
||||
"patch-add-other-auth.sql": { parents: [ "patch-monitor-basic-auth.sql" ] },
|
||||
"patch-grpc-monitor.sql": true,
|
||||
"patch-add-radius-monitor.sql": true,
|
||||
"patch-monitor-add-resend-interval.sql": true,
|
||||
"patch-maintenance-table2.sql": true,
|
||||
@ -151,9 +152,6 @@ class Database {
|
||||
await R.exec("PRAGMA cache_size = -12000");
|
||||
await R.exec("PRAGMA auto_vacuum = FULL");
|
||||
|
||||
// Avoid error "SQLITE_BUSY: database is locked" by allowing SQLITE to wait up to 5 seconds to do a write
|
||||
await R.exec("PRAGMA busy_timeout = 5000");
|
||||
|
||||
// This ensures that an operating system crash or power failure will not corrupt the database.
|
||||
// FULL synchronous is very safe, but it is also slower.
|
||||
// Read more: https://sqlite.org/pragma.html#pragma_synchronous
|
||||
|
@ -32,6 +32,7 @@ const initBackgroundJobs = function (args) {
|
||||
return bree;
|
||||
};
|
||||
|
||||
/** Stop all background jobs if running */
|
||||
const stopBackgroundJobs = function () {
|
||||
if (bree) {
|
||||
bree.stop();
|
||||
|
@ -25,6 +25,10 @@ const DEFAULT_KEEP_PERIOD = 180;
|
||||
parsedPeriod = DEFAULT_KEEP_PERIOD;
|
||||
}
|
||||
|
||||
if (parsedPeriod < 1) {
|
||||
log(`Data deletion has been disabled as period is less than 1. Period is ${parsedPeriod} days.`);
|
||||
} else {
|
||||
|
||||
log(`Clearing Data older than ${parsedPeriod} days...`);
|
||||
|
||||
try {
|
||||
@ -35,6 +39,7 @@ const DEFAULT_KEEP_PERIOD = 180;
|
||||
} catch (e) {
|
||||
log(`Failed to clear old data: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
exit();
|
||||
})();
|
||||
|
@ -1,4 +1,3 @@
|
||||
const dayjs = require("dayjs");
|
||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||
|
||||
/**
|
||||
|
@ -112,6 +112,11 @@ class Maintenance extends BeanModel {
|
||||
return this.toPublicJSON(timezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of weekdays that the maintenance is active for
|
||||
* Monday=1, Tuesday=2 etc.
|
||||
* @returns {number[]} Array of active weekdays
|
||||
*/
|
||||
getDayOfWeekList() {
|
||||
log.debug("timeslot", "List: " + this.weekdays);
|
||||
return JSON.parse(this.weekdays).sort(function (a, b) {
|
||||
@ -119,12 +124,20 @@ class Maintenance extends BeanModel {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of days in month that maintenance is active for
|
||||
* @returns {number[]} Array of active days in month
|
||||
*/
|
||||
getDayOfMonthList() {
|
||||
return JSON.parse(this.days_of_month).sort(function (a, b) {
|
||||
return a - b;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the start date and time for maintenance
|
||||
* @returns {dayjs.Dayjs} Start date and time
|
||||
*/
|
||||
getStartDateTime() {
|
||||
let startOfTheDay = dayjs.utc(this.start_date).format("HH:mm");
|
||||
log.debug("timeslot", "startOfTheDay: " + startOfTheDay);
|
||||
@ -137,6 +150,10 @@ class Maintenance extends BeanModel {
|
||||
return dayjs.utc(this.start_date).add(startTimeSecond, "second");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the duraction of maintenance in seconds
|
||||
* @returns {number} Duration of maintenance
|
||||
*/
|
||||
getDuration() {
|
||||
let duration = dayjs.utc(this.end_time, "HH:mm").diff(dayjs.utc(this.start_time, "HH:mm"), "second");
|
||||
// Add 24hours if it is across day
|
||||
@ -146,6 +163,12 @@ class Maintenance extends BeanModel {
|
||||
return duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert data from socket to bean
|
||||
* @param {Bean} bean Bean to fill in
|
||||
* @param {Object} obj Data to fill bean with
|
||||
* @returns {Bean} Filled bean
|
||||
*/
|
||||
static jsonToBean(bean, obj) {
|
||||
if (obj.id) {
|
||||
bean.id = obj.id;
|
||||
@ -188,13 +211,13 @@ class Maintenance extends BeanModel {
|
||||
*/
|
||||
static getActiveMaintenanceSQLCondition() {
|
||||
return `
|
||||
|
||||
(
|
||||
(maintenance_timeslot.start_date <= DATETIME('now')
|
||||
AND maintenance_timeslot.end_date >= DATETIME('now')
|
||||
AND maintenance.active = 1)
|
||||
OR
|
||||
(maintenance.strategy = 'manual' AND active = 1)
|
||||
|
||||
)
|
||||
`;
|
||||
}
|
||||
|
||||
@ -204,10 +227,12 @@ class Maintenance extends BeanModel {
|
||||
*/
|
||||
static getActiveAndFutureMaintenanceSQLCondition() {
|
||||
return `
|
||||
(
|
||||
((maintenance_timeslot.end_date >= DATETIME('now')
|
||||
AND maintenance.active = 1)
|
||||
OR
|
||||
(maintenance.strategy = 'manual' AND active = 1))
|
||||
)
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,11 @@ const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||
|
||||
class MaintenanceTimeslot extends BeanModel {
|
||||
|
||||
/**
|
||||
* Return an object that ready to parse to JSON for public
|
||||
* Only show necessary data to public
|
||||
* @returns {Object}
|
||||
*/
|
||||
async toPublicJSON() {
|
||||
const serverTimezoneOffset = UptimeKumaServer.getInstance().getTimezoneOffset();
|
||||
|
||||
@ -21,6 +26,10 @@ class MaintenanceTimeslot extends BeanModel {
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an object that ready to parse to JSON
|
||||
* @returns {Object}
|
||||
*/
|
||||
async toJSON() {
|
||||
return await this.toPublicJSON();
|
||||
}
|
||||
|
@ -2,8 +2,10 @@ const https = require("https");
|
||||
const dayjs = require("dayjs");
|
||||
const axios = require("axios");
|
||||
const { Prometheus } = require("../prometheus");
|
||||
const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, TimeLogger } = require("../../src/util");
|
||||
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mqttAsync, setSetting, httpNtlm, radius } = require("../util-server");
|
||||
const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, TimeLogger, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND } = require("../../src/util");
|
||||
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, mqttAsync, setSetting, httpNtlm, radius, grpcQuery,
|
||||
redisPingAsync, mongodbPing,
|
||||
} = require("../util-server");
|
||||
const { R } = require("redbean-node");
|
||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||
const { Notification } = require("../notification");
|
||||
@ -15,6 +17,7 @@ const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||
const { CacheableDnsHttpAgent } = require("../cacheable-dns-http-agent");
|
||||
const { DockerHost } = require("../docker");
|
||||
const Maintenance = require("./maintenance");
|
||||
const { UptimeCacheList } = require("../uptime-cache-list");
|
||||
|
||||
/**
|
||||
* status:
|
||||
@ -35,7 +38,6 @@ class Monitor extends BeanModel {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
sendUrl: this.sendUrl,
|
||||
maintenance: await Monitor.isUnderMaintenance(this.id),
|
||||
};
|
||||
|
||||
if (this.sendUrl) {
|
||||
@ -89,27 +91,23 @@ class Monitor extends BeanModel {
|
||||
dns_resolve_type: this.dns_resolve_type,
|
||||
dns_resolve_server: this.dns_resolve_server,
|
||||
dns_last_result: this.dns_last_result,
|
||||
pushToken: this.pushToken,
|
||||
docker_container: this.docker_container,
|
||||
docker_host: this.docker_host,
|
||||
proxyId: this.proxy_id,
|
||||
notificationIDList,
|
||||
tags: tags,
|
||||
maintenance: await Monitor.isUnderMaintenance(this.id),
|
||||
mqttUsername: this.mqttUsername,
|
||||
mqttPassword: this.mqttPassword,
|
||||
mqttTopic: this.mqttTopic,
|
||||
mqttSuccessMessage: this.mqttSuccessMessage,
|
||||
databaseConnectionString: this.databaseConnectionString,
|
||||
databaseQuery: this.databaseQuery,
|
||||
authMethod: this.authMethod,
|
||||
authWorkstation: this.authWorkstation,
|
||||
authDomain: this.authDomain,
|
||||
radiusUsername: this.radiusUsername,
|
||||
radiusPassword: this.radiusPassword,
|
||||
grpcUrl: this.grpcUrl,
|
||||
grpcProtobuf: this.grpcProtobuf,
|
||||
grpcMethod: this.grpcMethod,
|
||||
grpcServiceName: this.grpcServiceName,
|
||||
grpcEnableTls: this.getGrpcEnableTls(),
|
||||
radiusCalledStationId: this.radiusCalledStationId,
|
||||
radiusCallingStationId: this.radiusCallingStationId,
|
||||
radiusSecret: this.radiusSecret,
|
||||
};
|
||||
|
||||
if (includeSensitiveData) {
|
||||
@ -117,12 +115,23 @@ class Monitor extends BeanModel {
|
||||
...data,
|
||||
headers: this.headers,
|
||||
body: this.body,
|
||||
grpcBody: this.grpcBody,
|
||||
grpcMetadata: this.grpcMetadata,
|
||||
basic_auth_user: this.basic_auth_user,
|
||||
basic_auth_pass: this.basic_auth_pass,
|
||||
pushToken: this.pushToken,
|
||||
databaseConnectionString: this.databaseConnectionString,
|
||||
radiusUsername: this.radiusUsername,
|
||||
radiusPassword: this.radiusPassword,
|
||||
radiusSecret: this.radiusSecret,
|
||||
mqttUsername: this.mqttUsername,
|
||||
mqttPassword: this.mqttPassword,
|
||||
authWorkstation: this.authWorkstation,
|
||||
authDomain: this.authDomain,
|
||||
};
|
||||
}
|
||||
|
||||
data.includeSensitiveData = includeSensitiveData;
|
||||
return data;
|
||||
}
|
||||
|
||||
@ -167,6 +176,14 @@ class Monitor extends BeanModel {
|
||||
return Boolean(this.upsideDown);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse to boolean
|
||||
* @returns {boolean}
|
||||
*/
|
||||
getGrpcEnableTls() {
|
||||
return Boolean(this.grpcEnableTls);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get accepted status codes
|
||||
* @returns {Object}
|
||||
@ -252,6 +269,7 @@ class Monitor extends BeanModel {
|
||||
|
||||
log.debug("monitor", `[${this.name}] Prepare Options for axios`);
|
||||
|
||||
// Axios Options
|
||||
const options = {
|
||||
url: this.url,
|
||||
method: (this.method || "get").toLowerCase(),
|
||||
@ -290,20 +308,8 @@ class Monitor extends BeanModel {
|
||||
log.debug("monitor", `[${this.name}] Axios Options: ${JSON.stringify(options)}`);
|
||||
log.debug("monitor", `[${this.name}] Axios Request`);
|
||||
|
||||
let res;
|
||||
if (this.auth_method === "ntlm") {
|
||||
options.httpsAgent.keepAlive = true;
|
||||
|
||||
res = await httpNtlm(options, {
|
||||
username: this.basic_auth_user,
|
||||
password: this.basic_auth_pass,
|
||||
domain: this.authDomain,
|
||||
workstation: this.authWorkstation ? this.authWorkstation : undefined
|
||||
});
|
||||
|
||||
} else {
|
||||
res = await axios.request(options);
|
||||
}
|
||||
// Make Request
|
||||
let res = await this.makeAxiosRequest(options);
|
||||
|
||||
bean.msg = `${res.status} - ${res.statusText}`;
|
||||
bean.ping = dayjs().valueOf() - startTime;
|
||||
@ -489,14 +495,18 @@ class Monitor extends BeanModel {
|
||||
|
||||
const options = {
|
||||
url: `/containers/${this.docker_container}/json`,
|
||||
timeout: this.interval * 1000 * 0.8,
|
||||
headers: {
|
||||
"Accept": "*/*",
|
||||
"User-Agent": "Uptime-Kuma/" + version,
|
||||
},
|
||||
httpsAgent: new https.Agent({
|
||||
httpsAgent: CacheableDnsHttpAgent.getHttpsAgent({
|
||||
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
|
||||
rejectUnauthorized: !this.getIgnoreTls(),
|
||||
}),
|
||||
httpAgent: CacheableDnsHttpAgent.getHttpAgent({
|
||||
maxCachedSessions: 0,
|
||||
}),
|
||||
};
|
||||
|
||||
if (dockerHost._dockerType === "socket") {
|
||||
@ -509,7 +519,9 @@ class Monitor extends BeanModel {
|
||||
let res = await axios.request(options);
|
||||
if (res.data.State.Running) {
|
||||
bean.status = UP;
|
||||
bean.msg = "";
|
||||
bean.msg = res.data.State.Status;
|
||||
} else {
|
||||
throw Error("Container State is " + res.data.State.Status);
|
||||
}
|
||||
} else if (this.type === "mqtt") {
|
||||
bean.msg = await mqttAsync(this.hostname, this.mqttTopic, this.mqttSuccessMessage, {
|
||||
@ -527,6 +539,37 @@ class Monitor extends BeanModel {
|
||||
bean.msg = "";
|
||||
bean.status = UP;
|
||||
bean.ping = dayjs().valueOf() - startTime;
|
||||
} else if (this.type === "grpc-keyword") {
|
||||
let startTime = dayjs().valueOf();
|
||||
const options = {
|
||||
grpcUrl: this.grpcUrl,
|
||||
grpcProtobufData: this.grpcProtobuf,
|
||||
grpcServiceName: this.grpcServiceName,
|
||||
grpcEnableTls: this.grpcEnableTls,
|
||||
grpcMethod: this.grpcMethod,
|
||||
grpcBody: this.grpcBody,
|
||||
keyword: this.keyword
|
||||
};
|
||||
const response = await grpcQuery(options);
|
||||
bean.ping = dayjs().valueOf() - startTime;
|
||||
log.debug("monitor:", `gRPC response: ${JSON.stringify(response)}`);
|
||||
let responseData = response.data;
|
||||
if (responseData.length > 50) {
|
||||
responseData = responseData.toString().substring(0, 47) + "...";
|
||||
}
|
||||
if (response.code !== 1) {
|
||||
bean.status = DOWN;
|
||||
bean.msg = `Error in send gRPC ${response.code} ${response.errorMessage}`;
|
||||
} else {
|
||||
if (response.data.toString().includes(this.keyword)) {
|
||||
bean.status = UP;
|
||||
bean.msg = `${responseData}, keyword [${this.keyword}] is found`;
|
||||
} else {
|
||||
log.debug("monitor:", `GRPC response [${response.data}] + ", but keyword [${this.keyword}] is not in [" + ${response.data} + "]"`);
|
||||
bean.status = DOWN;
|
||||
bean.msg = `, but keyword [${this.keyword}] is not in [" + ${responseData} + "]`;
|
||||
}
|
||||
}
|
||||
} else if (this.type === "postgres") {
|
||||
let startTime = dayjs().valueOf();
|
||||
|
||||
@ -535,6 +578,23 @@ class Monitor extends BeanModel {
|
||||
bean.msg = "";
|
||||
bean.status = UP;
|
||||
bean.ping = dayjs().valueOf() - startTime;
|
||||
} else if (this.type === "mysql") {
|
||||
let startTime = dayjs().valueOf();
|
||||
|
||||
await mysqlQuery(this.databaseConnectionString, this.databaseQuery);
|
||||
|
||||
bean.msg = "";
|
||||
bean.status = UP;
|
||||
bean.ping = dayjs().valueOf() - startTime;
|
||||
} else if (this.type === "mongodb") {
|
||||
let startTime = dayjs().valueOf();
|
||||
|
||||
await mongodbPing(this.databaseConnectionString);
|
||||
|
||||
bean.msg = "";
|
||||
bean.status = UP;
|
||||
bean.ping = dayjs().valueOf() - startTime;
|
||||
|
||||
} else if (this.type === "radius") {
|
||||
let startTime = dayjs().valueOf();
|
||||
|
||||
@ -571,6 +631,12 @@ class Monitor extends BeanModel {
|
||||
}
|
||||
}
|
||||
bean.ping = dayjs().valueOf() - startTime;
|
||||
} else if (this.type === "redis") {
|
||||
let startTime = dayjs().valueOf();
|
||||
|
||||
bean.msg = await redisPingAsync(this.databaseConnectionString);
|
||||
bean.status = UP;
|
||||
bean.ping = dayjs().valueOf() - startTime;
|
||||
} else {
|
||||
bean.msg = "Unknown Monitor Type";
|
||||
bean.status = PENDING;
|
||||
@ -655,6 +721,7 @@ class Monitor extends BeanModel {
|
||||
}
|
||||
|
||||
log.debug("monitor", `[${this.name}] Send to socket`);
|
||||
UptimeCacheList.clearCache(this.id);
|
||||
io.to(this.user_id).emit("heartbeat", bean.toJSON());
|
||||
Monitor.sendStats(io, this.id, this.user_id);
|
||||
|
||||
@ -701,6 +768,47 @@ class Monitor extends BeanModel {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a request using axios
|
||||
* @param {Object} options Options for Axios
|
||||
* @param {boolean} finalCall Should this be the final call i.e
|
||||
* don't retry on faliure
|
||||
* @returns {Object} Axios response
|
||||
*/
|
||||
async makeAxiosRequest(options, finalCall = false) {
|
||||
try {
|
||||
let res;
|
||||
if (this.auth_method === "ntlm") {
|
||||
options.httpsAgent.keepAlive = true;
|
||||
|
||||
res = await httpNtlm(options, {
|
||||
username: this.basic_auth_user,
|
||||
password: this.basic_auth_pass,
|
||||
domain: this.authDomain,
|
||||
workstation: this.authWorkstation ? this.authWorkstation : undefined
|
||||
});
|
||||
|
||||
} else {
|
||||
res = await axios.request(options);
|
||||
}
|
||||
|
||||
return res;
|
||||
} catch (e) {
|
||||
// Fix #2253
|
||||
// Read more: https://stackoverflow.com/questions/1759956/curl-error-18-transfer-closed-with-outstanding-read-data-remaining
|
||||
if (!finalCall && typeof e.message === "string" && e.message.includes("maxContentLength size of -1 exceeded")) {
|
||||
log.debug("monitor", "makeAxiosRequest with gzip");
|
||||
options.headers["Accept-Encoding"] = "gzip, deflate";
|
||||
return this.makeAxiosRequest(options, true);
|
||||
} else {
|
||||
if (typeof e.message === "string" && e.message.includes("maxContentLength size of -1 exceeded")) {
|
||||
e.message = "response timeout: incomplete response within a interval";
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Stop monitor */
|
||||
stop() {
|
||||
clearTimeout(this.heartbeatInterval);
|
||||
@ -839,7 +947,15 @@ class Monitor extends BeanModel {
|
||||
* @param {number} duration Hours
|
||||
* @param {number} monitorID ID of monitor to calculate
|
||||
*/
|
||||
static async calcUptime(duration, monitorID) {
|
||||
static async calcUptime(duration, monitorID, forceNoCache = false) {
|
||||
|
||||
if (!forceNoCache) {
|
||||
let cachedUptime = UptimeCacheList.getUptime(monitorID, duration);
|
||||
if (cachedUptime != null) {
|
||||
return cachedUptime;
|
||||
}
|
||||
}
|
||||
|
||||
const timeLogger = new TimeLogger();
|
||||
|
||||
const startTime = R.isoDateTime(dayjs.utc().subtract(duration, "hour"));
|
||||
@ -898,6 +1014,9 @@ class Monitor extends BeanModel {
|
||||
}
|
||||
}
|
||||
|
||||
// Cache
|
||||
UptimeCacheList.addUptime(monitorID, duration, uptime);
|
||||
|
||||
return uptime;
|
||||
}
|
||||
|
||||
@ -997,7 +1116,13 @@ class Monitor extends BeanModel {
|
||||
|
||||
for (let notification of notificationList) {
|
||||
try {
|
||||
await Notification.send(JSON.parse(notification.config), msg, await monitor.toJSON(false), bean.toJSON());
|
||||
// Prevent if the msg is undefined, notifications such as Discord cannot send out.
|
||||
const heartbeatJSON = bean.toJSON();
|
||||
if (!heartbeatJSON["msg"]) {
|
||||
heartbeatJSON["msg"] = "N/A";
|
||||
}
|
||||
|
||||
await Notification.send(JSON.parse(notification.config), msg, await monitor.toJSON(false), heartbeatJSON);
|
||||
} catch (e) {
|
||||
log.error("monitor", "Cannot send notification to " + notification.name);
|
||||
log.error("monitor", e);
|
||||
@ -1130,6 +1255,16 @@ class Monitor extends BeanModel {
|
||||
LIMIT 1`, [ monitorID ]);
|
||||
return maintenance.count !== 0;
|
||||
}
|
||||
|
||||
/** Make sure monitor interval is between bounds */
|
||||
validate() {
|
||||
if (this.interval > MAX_INTERVAL_SECOND) {
|
||||
throw new Error(`Interval cannot be more than ${MAX_INTERVAL_SECOND} seconds`);
|
||||
}
|
||||
if (this.interval < MIN_INTERVAL_SECOND) {
|
||||
throw new Error(`Interval cannot be less than ${MIN_INTERVAL_SECOND} seconds`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Monitor;
|
||||
|
@ -38,7 +38,7 @@ class StatusPage extends BeanModel {
|
||||
*/
|
||||
static async renderHTML(indexHTML, statusPage) {
|
||||
const $ = cheerio.load(indexHTML);
|
||||
const description155 = statusPage.description?.substring(0, 155);
|
||||
const description155 = statusPage.description?.substring(0, 155) ?? "";
|
||||
|
||||
$("title").text(statusPage.title);
|
||||
$("meta[name=description]").attr("content", description155);
|
||||
@ -281,12 +281,14 @@ class StatusPage extends BeanModel {
|
||||
|
||||
let activeCondition = Maintenance.getActiveMaintenanceSQLCondition();
|
||||
let maintenanceBeanList = R.convertToBeans("maintenance", await R.getAll(`
|
||||
SELECT maintenance.*
|
||||
FROM maintenance, maintenance_status_page msp, maintenance_timeslot
|
||||
WHERE msp.maintenance_id = maintenance.id
|
||||
AND maintenance_timeslot.maintenance_id = maintenance.id
|
||||
AND msp.status_page_id = ?
|
||||
AND ${activeCondition}
|
||||
SELECT DISTINCT maintenance.*
|
||||
FROM maintenance
|
||||
JOIN maintenance_status_page
|
||||
ON maintenance_status_page.maintenance_id = maintenance.id
|
||||
AND maintenance_status_page.status_page_id = ?
|
||||
LEFT JOIN maintenance_timeslot
|
||||
ON maintenance_timeslot.maintenance_id = maintenance.id
|
||||
WHERE ${activeCondition}
|
||||
ORDER BY maintenance.end_date
|
||||
`, [ statusPageId ]));
|
||||
|
||||
|
20
server/modules/dayjs/plugin/timezone.d.ts
vendored
Normal file
20
server/modules/dayjs/plugin/timezone.d.ts
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
import { PluginFunc, ConfigType } from 'dayjs'
|
||||
|
||||
declare const plugin: PluginFunc
|
||||
export = plugin
|
||||
|
||||
declare module 'dayjs' {
|
||||
interface Dayjs {
|
||||
tz(timezone?: string, keepLocalTime?: boolean): Dayjs
|
||||
offsetName(type?: 'short' | 'long'): string | undefined
|
||||
}
|
||||
|
||||
interface DayjsTimezone {
|
||||
(date: ConfigType, timezone?: string): Dayjs
|
||||
(date: ConfigType, format: string, timezone?: string): Dayjs
|
||||
guess(): string
|
||||
setDefault(timezone?: string): void
|
||||
}
|
||||
|
||||
const tz: DayjsTimezone
|
||||
}
|
115
server/modules/dayjs/plugin/timezone.js
Normal file
115
server/modules/dayjs/plugin/timezone.js
Normal file
@ -0,0 +1,115 @@
|
||||
/**
|
||||
* Copy from node_modules/dayjs/plugin/timezone.js
|
||||
* Try to fix https://github.com/louislam/uptime-kuma/issues/2318
|
||||
* Source: https://github.com/iamkun/dayjs/tree/dev/src/plugin/utc
|
||||
* License: MIT
|
||||
*/
|
||||
!function (t, e) {
|
||||
// eslint-disable-next-line no-undef
|
||||
typeof exports == "object" && typeof module != "undefined" ? module.exports = e() : typeof define == "function" && define.amd ? define(e) : (t = typeof globalThis != "undefined" ? globalThis : t || self).dayjs_plugin_timezone = e();
|
||||
}(this, (function () {
|
||||
"use strict";
|
||||
let t = {
|
||||
year: 0,
|
||||
month: 1,
|
||||
day: 2,
|
||||
hour: 3,
|
||||
minute: 4,
|
||||
second: 5
|
||||
};
|
||||
let e = {};
|
||||
return function (n, i, o) {
|
||||
let r;
|
||||
let a = function (t, n, i) {
|
||||
void 0 === i && (i = {});
|
||||
let o = new Date(t);
|
||||
let r = function (t, n) {
|
||||
void 0 === n && (n = {});
|
||||
let i = n.timeZoneName || "short";
|
||||
let o = t + "|" + i;
|
||||
let r = e[o];
|
||||
return r || (r = new Intl.DateTimeFormat("en-US", {
|
||||
hour12: !1,
|
||||
timeZone: t,
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
timeZoneName: i
|
||||
}), e[o] = r), r;
|
||||
}(n, i);
|
||||
return r.formatToParts(o);
|
||||
};
|
||||
let u = function (e, n) {
|
||||
let i = a(e, n);
|
||||
let r = [];
|
||||
let u = 0;
|
||||
for (; u < i.length; u += 1) {
|
||||
let f = i[u];
|
||||
let s = f.type;
|
||||
let m = f.value;
|
||||
let c = t[s];
|
||||
c >= 0 && (r[c] = parseInt(m, 10));
|
||||
}
|
||||
let d = r[3];
|
||||
let l = d === 24 ? 0 : d;
|
||||
let v = r[0] + "-" + r[1] + "-" + r[2] + " " + l + ":" + r[4] + ":" + r[5] + ":000";
|
||||
let h = +e;
|
||||
return (o.utc(v).valueOf() - (h -= h % 1e3)) / 6e4;
|
||||
};
|
||||
let f = i.prototype;
|
||||
f.tz = function (t, e) {
|
||||
void 0 === t && (t = r);
|
||||
let n = this.utcOffset();
|
||||
let i = this.toDate();
|
||||
let a = i.toLocaleString("en-US", { timeZone: t }).replace("\u202f", " ");
|
||||
let u = Math.round((i - new Date(a)) / 1e3 / 60);
|
||||
let f = o(a).$set("millisecond", this.$ms).utcOffset(15 * -Math.round(i.getTimezoneOffset() / 15) - u, !0);
|
||||
if (e) {
|
||||
let s = f.utcOffset();
|
||||
f = f.add(n - s, "minute");
|
||||
}
|
||||
return f.$x.$timezone = t, f;
|
||||
}, f.offsetName = function (t) {
|
||||
let e = this.$x.$timezone || o.tz.guess();
|
||||
let n = a(this.valueOf(), e, { timeZoneName: t }).find((function (t) {
|
||||
return t.type.toLowerCase() === "timezonename";
|
||||
}));
|
||||
return n && n.value;
|
||||
};
|
||||
let s = f.startOf;
|
||||
f.startOf = function (t, e) {
|
||||
if (!this.$x || !this.$x.$timezone) {
|
||||
return s.call(this, t, e);
|
||||
}
|
||||
let n = o(this.format("YYYY-MM-DD HH:mm:ss:SSS"));
|
||||
return s.call(n, t, e).tz(this.$x.$timezone, !0);
|
||||
}, o.tz = function (t, e, n) {
|
||||
let i = n && e;
|
||||
let a = n || e || r;
|
||||
let f = u(+o(), a);
|
||||
if (typeof t != "string") {
|
||||
return o(t).tz(a);
|
||||
}
|
||||
let s = function (t, e, n) {
|
||||
let i = t - 60 * e * 1e3;
|
||||
let o = u(i, n);
|
||||
if (e === o) {
|
||||
return [ i, e ];
|
||||
}
|
||||
let r = u(i -= 60 * (o - e) * 1e3, n);
|
||||
return o === r ? [ i, o ] : [ t - 60 * Math.min(o, r) * 1e3, Math.max(o, r) ];
|
||||
}(o.utc(t, i).valueOf(), f, a);
|
||||
let m = s[0];
|
||||
let c = s[1];
|
||||
let d = o(m).utcOffset(c);
|
||||
return d.$x.$timezone = a, d;
|
||||
}, o.tz.guess = function () {
|
||||
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
}, o.tz.setDefault = function (t) {
|
||||
r = t;
|
||||
};
|
||||
};
|
||||
}));
|
@ -64,7 +64,7 @@ class Discord extends NotificationProvider {
|
||||
},
|
||||
{
|
||||
name: "Error",
|
||||
value: heartbeatJSON["msg"],
|
||||
value: heartbeatJSON["msg"] == null ? "N/A" : heartbeatJSON["msg"],
|
||||
},
|
||||
],
|
||||
}],
|
||||
@ -91,7 +91,7 @@ class Discord extends NotificationProvider {
|
||||
},
|
||||
{
|
||||
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
|
||||
value: monitorJSON["type"] === "push" ? "Heartbeat" : address.startsWith("http") ? "[Visit Service](" + address + ")" : address,
|
||||
value: monitorJSON["type"] === "push" ? "Heartbeat" : address,
|
||||
},
|
||||
{
|
||||
name: "Time (UTC)",
|
||||
|
31
server/notification-providers/kook.js
Normal file
31
server/notification-providers/kook.js
Normal file
@ -0,0 +1,31 @@
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
|
||||
class Kook extends NotificationProvider {
|
||||
|
||||
name = "Kook";
|
||||
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
let okMsg = "Sent Successfully.";
|
||||
let url = "https://www.kookapp.cn/api/v3/message/create";
|
||||
let data = {
|
||||
target_id: notification.kookGuildID,
|
||||
content: msg,
|
||||
};
|
||||
let config = {
|
||||
headers: {
|
||||
"Authorization": "Bot " + notification.kookBotToken,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
};
|
||||
try {
|
||||
await axios.post(url, data, config);
|
||||
return okMsg;
|
||||
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Kook;
|
@ -20,6 +20,11 @@ class Ntfy extends NotificationProvider {
|
||||
"priority": notification.ntfyPriority || 4,
|
||||
"title": "Uptime-Kuma",
|
||||
};
|
||||
|
||||
if (notification.ntfyIcon) {
|
||||
data.icon = notification.ntfyIcon;
|
||||
}
|
||||
|
||||
await axios.post(`${notification.ntfyserverurl}`, data, { headers: headers });
|
||||
|
||||
return okMsg;
|
||||
|
@ -8,6 +8,14 @@ class PromoSMS extends NotificationProvider {
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
let okMsg = "Sent Successfully.";
|
||||
|
||||
if (notification.promosmsAllowLongSMS === undefined) {
|
||||
notification.promosmsAllowLongSMS = false;
|
||||
}
|
||||
|
||||
//TODO: Add option for enabling special characters. It will decrese message max length from 160 to 70 chars.
|
||||
//Lets remove non ascii char
|
||||
let cleanMsg = msg.replace(/[^\x00-\x7F]/g, "");
|
||||
|
||||
try {
|
||||
let config = {
|
||||
headers: {
|
||||
@ -18,8 +26,9 @@ class PromoSMS extends NotificationProvider {
|
||||
};
|
||||
let data = {
|
||||
"recipients": [ notification.promosmsPhoneNumber ],
|
||||
//Lets remove non ascii char
|
||||
"text": msg.replace(/[^\x00-\x7F]/g, ""),
|
||||
//Trim message to maximum length of 1 SMS or 4 if we allowed long messages
|
||||
"text": notification.promosmsAllowLongSMS ? cleanMsg.substring(0, 639) : cleanMsg.substring(0, 159),
|
||||
"long-sms": notification.promosmsAllowLongSMS,
|
||||
"type": Number(notification.promosmsSMSType),
|
||||
"sender": notification.promosmsSenderName
|
||||
};
|
||||
|
@ -19,26 +19,26 @@ class Pushbullet extends NotificationProvider {
|
||||
}
|
||||
};
|
||||
if (heartbeatJSON == null) {
|
||||
let testdata = {
|
||||
let data = {
|
||||
"type": "note",
|
||||
"title": "Uptime Kuma Alert",
|
||||
"body": "Testing Successful.",
|
||||
"body": msg,
|
||||
};
|
||||
await axios.post(pushbulletUrl, testdata, config);
|
||||
await axios.post(pushbulletUrl, data, config);
|
||||
} else if (heartbeatJSON["status"] === DOWN) {
|
||||
let downdata = {
|
||||
let downData = {
|
||||
"type": "note",
|
||||
"title": "UptimeKuma Alert: " + monitorJSON["name"],
|
||||
"body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"],
|
||||
};
|
||||
await axios.post(pushbulletUrl, downdata, config);
|
||||
await axios.post(pushbulletUrl, downData, config);
|
||||
} else if (heartbeatJSON["status"] === UP) {
|
||||
let updata = {
|
||||
let upData = {
|
||||
"type": "note",
|
||||
"title": "UptimeKuma Alert: " + monitorJSON["name"],
|
||||
"body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"],
|
||||
};
|
||||
await axios.post(pushbulletUrl, updata, config);
|
||||
await axios.post(pushbulletUrl, upData, config);
|
||||
}
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
|
@ -10,7 +10,7 @@ class Pushover extends NotificationProvider {
|
||||
let pushoverlink = "https://api.pushover.net/1/messages.json";
|
||||
|
||||
let data = {
|
||||
"message": "<b>Uptime Kuma Alert</b>\n\n<b>Message</b>:" + msg,
|
||||
"message": "<b>Message</b>:" + msg,
|
||||
"user": notification.pushoveruserkey,
|
||||
"token": notification.pushoverapptoken,
|
||||
"sound": notification.pushoversounds,
|
||||
|
@ -21,6 +21,12 @@ class ServerChan extends NotificationProvider {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the formatted title for message
|
||||
* @param {?Object} monitorJSON Monitor details (For Up/Down only)
|
||||
* @param {?Object} heartbeatJSON Heartbeat details (For Up/Down only)
|
||||
* @returns {string} Formatted title
|
||||
*/
|
||||
checkStatus(heartbeatJSON, monitorJSON) {
|
||||
let title = "UptimeKuma Message";
|
||||
if (heartbeatJSON != null && heartbeatJSON["status"] === UP) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
const { setSettings, setting } = require("../util-server");
|
||||
const { getMonitorRelativeURL } = require("../../src/util");
|
||||
const { getMonitorRelativeURL, UP } = require("../../src/util");
|
||||
|
||||
class Slack extends NotificationProvider {
|
||||
|
||||
@ -46,7 +46,11 @@ class Slack extends NotificationProvider {
|
||||
"channel": notification.slackchannel,
|
||||
"username": notification.slackusername,
|
||||
"icon_emoji": notification.slackiconemo,
|
||||
"blocks": [{
|
||||
"attachments": [
|
||||
{
|
||||
"color": (heartbeatJSON["status"] === UP) ? "#2eb886" : "#e01e5a",
|
||||
"blocks": [
|
||||
{
|
||||
"type": "header",
|
||||
"text": {
|
||||
"type": "plain_text",
|
||||
@ -63,7 +67,10 @@ class Slack extends NotificationProvider {
|
||||
"type": "mrkdwn",
|
||||
"text": "*Time (UTC)*\n" + time,
|
||||
}],
|
||||
}],
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
if (notification.slackbutton) {
|
||||
@ -74,7 +81,8 @@ class Slack extends NotificationProvider {
|
||||
|
||||
// Button
|
||||
if (baseURL) {
|
||||
data.blocks.push({
|
||||
data.attachments.forEach(element => {
|
||||
element.blocks.push({
|
||||
"type": "actions",
|
||||
"elements": [{
|
||||
"type": "button",
|
||||
@ -86,6 +94,7 @@ class Slack extends NotificationProvider {
|
||||
"url": baseURL + getMonitorRelativeURL(monitorJSON.id),
|
||||
}],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
await axios.post(notification.slackwebhookURL, data);
|
||||
|
71
server/notification-providers/smseagle.js
Normal file
71
server/notification-providers/smseagle.js
Normal file
@ -0,0 +1,71 @@
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
|
||||
class SMSEagle extends NotificationProvider {
|
||||
|
||||
name = "SMSEagle";
|
||||
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
let okMsg = "Sent Successfully.";
|
||||
|
||||
try {
|
||||
let config = {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
};
|
||||
|
||||
let postData;
|
||||
let sendMethod;
|
||||
let recipientType;
|
||||
|
||||
let encoding = (notification.smseagleEncoding) ? "1" : "0";
|
||||
let priority = (notification.smseaglePriority) ? notification.smseaglePriority : "0";
|
||||
|
||||
if (notification.smseagleRecipientType === "smseagle-contact") {
|
||||
recipientType = "contactname";
|
||||
sendMethod = "sms.send_tocontact";
|
||||
}
|
||||
if (notification.smseagleRecipientType === "smseagle-group") {
|
||||
recipientType = "groupname";
|
||||
sendMethod = "sms.send_togroup";
|
||||
}
|
||||
if (notification.smseagleRecipientType === "smseagle-to") {
|
||||
recipientType = "to";
|
||||
sendMethod = "sms.send_sms";
|
||||
}
|
||||
|
||||
let params = {
|
||||
access_token: notification.smseagleToken,
|
||||
[recipientType]: notification.smseagleRecipient,
|
||||
message: msg,
|
||||
responsetype: "extended",
|
||||
unicode: encoding,
|
||||
highpriority: priority
|
||||
};
|
||||
|
||||
postData = {
|
||||
method: sendMethod,
|
||||
params: params
|
||||
};
|
||||
|
||||
let resp = await axios.post(notification.smseagleUrl + "/jsonrpc/sms", postData, config);
|
||||
|
||||
if ((JSON.stringify(resp.data)).indexOf("message_id") === -1) {
|
||||
let error = "";
|
||||
if (resp.data.result && resp.data.result.error_text) {
|
||||
error = `SMSEagle API returned error: ${JSON.stringify(resp.data.result.error_text)}`;
|
||||
} else {
|
||||
error = "SMSEagle API returned an unexpected response";
|
||||
}
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SMSEagle;
|
113
server/notification-providers/splunk.js
Normal file
113
server/notification-providers/splunk.js
Normal file
@ -0,0 +1,113 @@
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
|
||||
const { setting } = require("../util-server");
|
||||
let successMessage = "Sent Successfully.";
|
||||
|
||||
class Splunk extends NotificationProvider {
|
||||
name = "Splunk";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
try {
|
||||
if (heartbeatJSON == null) {
|
||||
const title = "Uptime Kuma Alert";
|
||||
const monitor = {
|
||||
type: "ping",
|
||||
url: "Uptime Kuma Test Button",
|
||||
};
|
||||
return this.postNotification(notification, title, msg, monitor, "trigger");
|
||||
}
|
||||
|
||||
if (heartbeatJSON.status === UP) {
|
||||
const title = "Uptime Kuma Monitor ✅ Up";
|
||||
return this.postNotification(notification, title, heartbeatJSON.msg, monitorJSON, "recovery");
|
||||
}
|
||||
|
||||
if (heartbeatJSON.status === DOWN) {
|
||||
const title = "Uptime Kuma Monitor 🔴 Down";
|
||||
return this.postNotification(notification, title, heartbeatJSON.msg, monitorJSON, "trigger");
|
||||
}
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if result is successful, result code should be in range 2xx
|
||||
* @param {Object} result Axios response object
|
||||
* @throws {Error} The status code is not in range 2xx
|
||||
*/
|
||||
checkResult(result) {
|
||||
if (result.status == null) {
|
||||
throw new Error("Splunk notification failed with invalid response!");
|
||||
}
|
||||
if (result.status < 200 || result.status >= 300) {
|
||||
throw new Error("Splunk notification failed with status code " + result.status);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the message
|
||||
* @param {BeanModel} notification Message title
|
||||
* @param {string} title Message title
|
||||
* @param {string} body Message
|
||||
* @param {Object} monitorInfo Monitor details (For Up/Down only)
|
||||
* @param {?string} eventAction Action event for PagerDuty (trigger, acknowledge, resolve)
|
||||
* @returns {string}
|
||||
*/
|
||||
async postNotification(notification, title, body, monitorInfo, eventAction = "trigger") {
|
||||
|
||||
let monitorUrl;
|
||||
if (monitorInfo.type === "port") {
|
||||
monitorUrl = monitorInfo.hostname;
|
||||
if (monitorInfo.port) {
|
||||
monitorUrl += ":" + monitorInfo.port;
|
||||
}
|
||||
} else if (monitorInfo.hostname != null) {
|
||||
monitorUrl = monitorInfo.hostname;
|
||||
} else {
|
||||
monitorUrl = monitorInfo.url;
|
||||
}
|
||||
|
||||
if (eventAction === "recovery") {
|
||||
if (notification.splunkAutoResolve === "0") {
|
||||
return "No action required";
|
||||
}
|
||||
eventAction = notification.splunkAutoResolve;
|
||||
} else {
|
||||
eventAction = notification.splunkSeverity;
|
||||
}
|
||||
|
||||
const options = {
|
||||
method: "POST",
|
||||
url: notification.splunkRestURL,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data: {
|
||||
message_type: eventAction,
|
||||
state_message: `[${title}] [${monitorUrl}] ${body}`,
|
||||
entity_display_name: "Uptime Kuma Alert: " + monitorInfo.name,
|
||||
routing_key: notification.pagerdutyIntegrationKey,
|
||||
entity_id: "Uptime Kuma/" + monitorInfo.id,
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
this.checkResult(result);
|
||||
if (result.statusText != null) {
|
||||
return "Splunk notification succeed: " + result.statusText;
|
||||
}
|
||||
|
||||
return successMessage;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Splunk;
|
@ -16,20 +16,29 @@ class Webhook extends NotificationProvider {
|
||||
msg,
|
||||
};
|
||||
let finalData;
|
||||
let config = {};
|
||||
let config = {
|
||||
headers: {}
|
||||
};
|
||||
|
||||
if (notification.webhookContentType === "form-data") {
|
||||
finalData = new FormData();
|
||||
finalData.append("data", JSON.stringify(data));
|
||||
|
||||
config = {
|
||||
headers: finalData.getHeaders(),
|
||||
};
|
||||
|
||||
config.headers = finalData.getHeaders();
|
||||
} else {
|
||||
finalData = data;
|
||||
}
|
||||
|
||||
if (notification.webhookAdditionalHeaders) {
|
||||
try {
|
||||
config.headers = {
|
||||
...config.headers,
|
||||
...JSON.parse(notification.webhookAdditionalHeaders)
|
||||
};
|
||||
} catch (err) {
|
||||
throw "Additional Headers is not a valid JSON";
|
||||
}
|
||||
}
|
||||
|
||||
await axios.post(notification.webhookURL, finalData, config);
|
||||
return okMsg;
|
||||
|
||||
|
116
server/notification-providers/zoho-cliq.js
Normal file
116
server/notification-providers/zoho-cliq.js
Normal file
@ -0,0 +1,116 @@
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
const { DOWN, UP } = require("../../src/util");
|
||||
|
||||
class ZohoCliq extends NotificationProvider {
|
||||
|
||||
name = "ZohoCliq";
|
||||
|
||||
/**
|
||||
* Generate the message to send
|
||||
* @param {const} status The status constant
|
||||
* @param {string} monitorName Name of monitor
|
||||
* @returns {string}
|
||||
*/
|
||||
_statusMessageFactory = (status, monitorName) => {
|
||||
if (status === DOWN) {
|
||||
return `🔴 Application [${monitorName}] went down\n`;
|
||||
} else if (status === UP) {
|
||||
return `✅ Application [${monitorName}] is back online\n`;
|
||||
}
|
||||
return "Notification\n";
|
||||
};
|
||||
|
||||
/**
|
||||
* Send the notification
|
||||
* @param {string} webhookUrl URL to send the request to
|
||||
* @param {Array} payload Payload generated by _notificationPayloadFactory
|
||||
*/
|
||||
_sendNotification = async (webhookUrl, payload) => {
|
||||
await axios.post(webhookUrl, { text: payload.join("\n") });
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate payload for notification
|
||||
* @param {const} status The status of the monitor
|
||||
* @param {string} monitorMessage Message to send
|
||||
* @param {string} monitorName Name of monitor affected
|
||||
* @param {string} monitorUrl URL of monitor affected
|
||||
* @returns {Array}
|
||||
*/
|
||||
_notificationPayloadFactory = ({
|
||||
status,
|
||||
monitorMessage,
|
||||
monitorName,
|
||||
monitorUrl,
|
||||
}) => {
|
||||
const payload = [];
|
||||
payload.push("### Uptime Kuma\n");
|
||||
payload.push(this._statusMessageFactory(status, monitorName));
|
||||
payload.push(`*Description:* ${monitorMessage}`);
|
||||
|
||||
if (monitorName) {
|
||||
payload.push(`*Monitor:* ${monitorName}`);
|
||||
}
|
||||
|
||||
if (monitorUrl && monitorUrl !== "https://") {
|
||||
payload.push(`*URL:* [${monitorUrl}](${monitorUrl})`);
|
||||
}
|
||||
|
||||
return payload;
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a general notification
|
||||
* @param {string} webhookUrl URL to send request to
|
||||
* @param {string} msg Message to send
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
_handleGeneralNotification = (webhookUrl, msg) => {
|
||||
const payload = this._notificationPayloadFactory({
|
||||
monitorMessage: msg
|
||||
});
|
||||
|
||||
return this._sendNotification(webhookUrl, payload);
|
||||
};
|
||||
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
let okMsg = "Sent Successfully.";
|
||||
|
||||
try {
|
||||
if (heartbeatJSON == null) {
|
||||
await this._handleGeneralNotification(notification.webhookUrl, msg);
|
||||
return okMsg;
|
||||
}
|
||||
|
||||
let url;
|
||||
switch (monitorJSON["type"]) {
|
||||
case "http":
|
||||
case "keywork":
|
||||
url = monitorJSON["url"];
|
||||
break;
|
||||
case "docker":
|
||||
url = monitorJSON["docker_host"];
|
||||
break;
|
||||
default:
|
||||
url = monitorJSON["hostname"];
|
||||
break;
|
||||
}
|
||||
|
||||
const payload = this._notificationPayloadFactory({
|
||||
monitorMessage: heartbeatJSON.msg,
|
||||
monitorName: monitorJSON.name,
|
||||
monitorUrl: url,
|
||||
status: heartbeatJSON.status
|
||||
});
|
||||
|
||||
await this._sendNotification(notification.webhookUrl, payload);
|
||||
return okMsg;
|
||||
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ZohoCliq;
|
@ -14,6 +14,7 @@ const GoogleChat = require("./notification-providers/google-chat");
|
||||
const Gorush = require("./notification-providers/gorush");
|
||||
const Gotify = require("./notification-providers/gotify");
|
||||
const HomeAssistant = require("./notification-providers/home-assistant");
|
||||
const Kook = require("./notification-providers/kook");
|
||||
const Line = require("./notification-providers/line");
|
||||
const LineNotify = require("./notification-providers/linenotify");
|
||||
const LunaSea = require("./notification-providers/lunasea");
|
||||
@ -32,17 +33,20 @@ const RocketChat = require("./notification-providers/rocket-chat");
|
||||
const SerwerSMS = require("./notification-providers/serwersms");
|
||||
const Signal = require("./notification-providers/signal");
|
||||
const Slack = require("./notification-providers/slack");
|
||||
const SMSEagle = require("./notification-providers/smseagle");
|
||||
const SMTP = require("./notification-providers/smtp");
|
||||
const Squadcast = require("./notification-providers/squadcast");
|
||||
const Stackfield = require("./notification-providers/stackfield");
|
||||
const Teams = require("./notification-providers/teams");
|
||||
const TechulusPush = require("./notification-providers/techulus-push");
|
||||
const Telegram = require("./notification-providers/telegram");
|
||||
const Splunk = require("./notification-providers/splunk");
|
||||
const Webhook = require("./notification-providers/webhook");
|
||||
const WeCom = require("./notification-providers/wecom");
|
||||
const GoAlert = require("./notification-providers/goalert");
|
||||
const SMSManager = require("./notification-providers/smsmanager");
|
||||
const ServerChan = require("./notification-providers/serverchan");
|
||||
const ZohoCliq = require("./notification-providers/zoho-cliq");
|
||||
|
||||
class Notification {
|
||||
|
||||
@ -69,6 +73,7 @@ class Notification {
|
||||
new Gorush(),
|
||||
new Gotify(),
|
||||
new HomeAssistant(),
|
||||
new Kook(),
|
||||
new Line(),
|
||||
new LineNotify(),
|
||||
new LunaSea(),
|
||||
@ -89,15 +94,18 @@ class Notification {
|
||||
new Signal(),
|
||||
new SMSManager(),
|
||||
new Slack(),
|
||||
new SMSEagle(),
|
||||
new SMTP(),
|
||||
new Squadcast(),
|
||||
new Stackfield(),
|
||||
new Teams(),
|
||||
new TechulusPush(),
|
||||
new Telegram(),
|
||||
new Splunk(),
|
||||
new Webhook(),
|
||||
new WeCom(),
|
||||
new GoAlert(),
|
||||
new ZohoCliq()
|
||||
];
|
||||
|
||||
for (let item of list) {
|
||||
|
@ -1,199 +0,0 @@
|
||||
// https://github.com/ben-bradley/ping-lite/blob/master/ping-lite.js
|
||||
// Fixed on Windows
|
||||
const net = require("net");
|
||||
const spawn = require("child_process").spawn;
|
||||
const events = require("events");
|
||||
const fs = require("fs");
|
||||
const util = require("./util-server");
|
||||
|
||||
module.exports = Ping;
|
||||
|
||||
/**
|
||||
* Constructor for ping class
|
||||
* @param {string} host Host to ping
|
||||
* @param {object} [options] Options for the ping command
|
||||
* @param {array|string} [options.args] - Arguments to pass to the ping command
|
||||
*/
|
||||
function Ping(host, options) {
|
||||
if (!host) {
|
||||
throw new Error("You must specify a host to ping!");
|
||||
}
|
||||
|
||||
this._host = host;
|
||||
this._options = options = (options || {});
|
||||
|
||||
events.EventEmitter.call(this);
|
||||
|
||||
const timeout = 10;
|
||||
|
||||
if (util.WIN) {
|
||||
this._bin = "c:/windows/system32/ping.exe";
|
||||
this._args = (options.args) ? options.args : [ "-n", "1", "-w", timeout * 1000, host ];
|
||||
this._regmatch = /[><=]([0-9.]+?)ms/;
|
||||
|
||||
} else if (util.LIN) {
|
||||
this._bin = "/bin/ping";
|
||||
|
||||
const defaultArgs = [ "-n", "-w", timeout, "-c", "1", host ];
|
||||
|
||||
if (net.isIPv6(host) || options.ipv6) {
|
||||
defaultArgs.unshift("-6");
|
||||
}
|
||||
|
||||
this._args = (options.args) ? options.args : defaultArgs;
|
||||
this._regmatch = /=([0-9.]+?) ms/;
|
||||
|
||||
} else if (util.MAC) {
|
||||
|
||||
if (net.isIPv6(host) || options.ipv6) {
|
||||
this._bin = "/sbin/ping6";
|
||||
} else {
|
||||
this._bin = "/sbin/ping";
|
||||
}
|
||||
|
||||
this._args = (options.args) ? options.args : [ "-n", "-t", timeout, "-c", "1", host ];
|
||||
this._regmatch = /=([0-9.]+?) ms/;
|
||||
|
||||
} else if (util.BSD) {
|
||||
this._bin = "/sbin/ping";
|
||||
|
||||
const defaultArgs = [ "-n", "-t", timeout, "-c", "1", host ];
|
||||
|
||||
if (net.isIPv6(host) || options.ipv6) {
|
||||
defaultArgs.unshift("-6");
|
||||
}
|
||||
|
||||
this._args = (options.args) ? options.args : defaultArgs;
|
||||
this._regmatch = /=([0-9.]+?) ms/;
|
||||
|
||||
} else {
|
||||
throw new Error("Could not detect your ping binary.");
|
||||
}
|
||||
|
||||
if (!fs.existsSync(this._bin)) {
|
||||
throw new Error("Could not detect " + this._bin + " on your system");
|
||||
}
|
||||
|
||||
this._i = 0;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
Ping.prototype.__proto__ = events.EventEmitter.prototype;
|
||||
|
||||
/**
|
||||
* Callback for send
|
||||
* @callback pingCB
|
||||
* @param {any} err Any error encountered
|
||||
* @param {number} ms Ping time in ms
|
||||
*/
|
||||
|
||||
/**
|
||||
* Send a ping
|
||||
* @param {pingCB} callback Callback to call with results
|
||||
*/
|
||||
Ping.prototype.send = function (callback) {
|
||||
let self = this;
|
||||
callback = callback || function (err, ms) {
|
||||
if (err) {
|
||||
return self.emit("error", err);
|
||||
}
|
||||
return self.emit("result", ms);
|
||||
};
|
||||
|
||||
let _ended;
|
||||
let _exited;
|
||||
let _errored;
|
||||
|
||||
this._ping = spawn(this._bin, this._args, { windowsHide: true }); // spawn the binary
|
||||
|
||||
this._ping.on("error", function (err) { // handle binary errors
|
||||
_errored = true;
|
||||
callback(err);
|
||||
});
|
||||
|
||||
this._ping.stdout.on("data", function (data) { // log stdout
|
||||
if (util.WIN) {
|
||||
data = convertOutput(data);
|
||||
}
|
||||
this._stdout = (this._stdout || "") + data;
|
||||
});
|
||||
|
||||
this._ping.stdout.on("end", function () {
|
||||
_ended = true;
|
||||
if (_exited && !_errored) {
|
||||
onEnd.call(self._ping);
|
||||
}
|
||||
});
|
||||
|
||||
this._ping.stderr.on("data", function (data) { // log stderr
|
||||
if (util.WIN) {
|
||||
data = convertOutput(data);
|
||||
}
|
||||
this._stderr = (this._stderr || "") + data;
|
||||
});
|
||||
|
||||
this._ping.on("exit", function (code) { // handle complete
|
||||
_exited = true;
|
||||
if (_ended && !_errored) {
|
||||
onEnd.call(self._ping);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {Function} callback
|
||||
*
|
||||
* Generated by Trelent
|
||||
*/
|
||||
function onEnd() {
|
||||
let stdout = this.stdout._stdout;
|
||||
let stderr = this.stderr._stderr;
|
||||
let ms;
|
||||
|
||||
if (stderr) {
|
||||
return callback(new Error(stderr));
|
||||
}
|
||||
|
||||
if (!stdout) {
|
||||
return callback(new Error("No stdout detected"));
|
||||
}
|
||||
|
||||
ms = stdout.match(self._regmatch); // parse out the ##ms response
|
||||
ms = (ms && ms[1]) ? Number(ms[1]) : ms;
|
||||
|
||||
callback(null, ms, stdout);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Ping every interval
|
||||
* @param {pingCB} callback Callback to call with results
|
||||
*/
|
||||
Ping.prototype.start = function (callback) {
|
||||
let self = this;
|
||||
this._i = setInterval(function () {
|
||||
self.send(callback);
|
||||
}, (self._options.interval || 5000));
|
||||
self.send(callback);
|
||||
};
|
||||
|
||||
/** Stop sending pings */
|
||||
Ping.prototype.stop = function () {
|
||||
clearInterval(this._i);
|
||||
};
|
||||
|
||||
/**
|
||||
* Try to convert to UTF-8 for Windows, as the ping's output on Windows is not UTF-8 and could be in other languages
|
||||
* Thank @pemassi
|
||||
* https://github.com/louislam/uptime-kuma/issues/570#issuecomment-941984094
|
||||
* @param {any} data
|
||||
* @returns {string}
|
||||
*/
|
||||
function convertOutput(data) {
|
||||
if (util.WIN) {
|
||||
if (data) {
|
||||
return util.convertToUTF8(data);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
@ -99,6 +99,7 @@ class Prometheus {
|
||||
}
|
||||
}
|
||||
|
||||
/** Remove monitor from prometheus */
|
||||
remove() {
|
||||
try {
|
||||
monitorCertDaysRemaining.remove(this.monitorLabelValues);
|
||||
|
@ -7,7 +7,7 @@ const { UptimeKumaServer } = require("./uptime-kuma-server");
|
||||
|
||||
class Proxy {
|
||||
|
||||
static SUPPORTED_PROXY_PROTOCOLS = [ "http", "https", "socks", "socks5", "socks4" ];
|
||||
static SUPPORTED_PROXY_PROTOCOLS = [ "http", "https", "socks", "socks5", "socks5h", "socks4" ];
|
||||
|
||||
/**
|
||||
* Saves and updates given proxy entity
|
||||
@ -126,6 +126,7 @@ class Proxy {
|
||||
break;
|
||||
case "socks":
|
||||
case "socks5":
|
||||
case "socks5h":
|
||||
case "socks4":
|
||||
agent = new SocksProxyAgent({
|
||||
...httpAgentOptions,
|
||||
|
@ -8,7 +8,7 @@ console.log("Welcome to Uptime Kuma");
|
||||
// As the log function need to use dayjs, it should be very top
|
||||
const dayjs = require("dayjs");
|
||||
dayjs.extend(require("dayjs/plugin/utc"));
|
||||
dayjs.extend(require("dayjs/plugin/timezone"));
|
||||
dayjs.extend(require("./modules/dayjs/plugin/timezone"));
|
||||
dayjs.extend(require("dayjs/plugin/customParseFormat"));
|
||||
|
||||
// Check Node.js Version
|
||||
@ -135,7 +135,9 @@ const { cloudflaredSocketHandler, autoStart: cloudflaredAutoStart, stop: cloudfl
|
||||
const { proxySocketHandler } = require("./socket-handlers/proxy-socket-handler");
|
||||
const { dockerSocketHandler } = require("./socket-handlers/docker-socket-handler");
|
||||
const { maintenanceSocketHandler } = require("./socket-handlers/maintenance-socket-handler");
|
||||
const { generalSocketHandler } = require("./socket-handlers/general-socket-handler");
|
||||
const { Settings } = require("./settings");
|
||||
const { CacheableDnsHttpAgent } = require("./cacheable-dns-http-agent");
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
@ -203,6 +205,7 @@ let needSetup = false;
|
||||
|
||||
if (isDev) {
|
||||
app.post("/test-webhook", async (request, response) => {
|
||||
log.debug("test", request.headers);
|
||||
log.debug("test", request.body);
|
||||
response.send("OK");
|
||||
});
|
||||
@ -631,6 +634,9 @@ let needSetup = false;
|
||||
|
||||
bean.import(monitor);
|
||||
bean.user_id = socket.userID;
|
||||
|
||||
bean.validate();
|
||||
|
||||
await R.store(bean);
|
||||
|
||||
await updateMonitorNotification(bean.id, notificationIDList);
|
||||
@ -706,12 +712,21 @@ let needSetup = false;
|
||||
bean.authMethod = monitor.authMethod;
|
||||
bean.authWorkstation = monitor.authWorkstation;
|
||||
bean.authDomain = monitor.authDomain;
|
||||
bean.grpcUrl = monitor.grpcUrl;
|
||||
bean.grpcProtobuf = monitor.grpcProtobuf;
|
||||
bean.grpcServiceName = monitor.grpcServiceName;
|
||||
bean.grpcMethod = monitor.grpcMethod;
|
||||
bean.grpcBody = monitor.grpcBody;
|
||||
bean.grpcMetadata = monitor.grpcMetadata;
|
||||
bean.grpcEnableTls = monitor.grpcEnableTls;
|
||||
bean.radiusUsername = monitor.radiusUsername;
|
||||
bean.radiusPassword = monitor.radiusPassword;
|
||||
bean.radiusCalledStationId = monitor.radiusCalledStationId;
|
||||
bean.radiusCallingStationId = monitor.radiusCallingStationId;
|
||||
bean.radiusSecret = monitor.radiusSecret;
|
||||
|
||||
bean.validate();
|
||||
|
||||
await R.store(bean);
|
||||
|
||||
await updateMonitorNotification(bean.id, monitor.notificationIDList);
|
||||
@ -926,13 +941,21 @@ let needSetup = false;
|
||||
try {
|
||||
checkLogin(socket);
|
||||
|
||||
let bean = await R.findOne("monitor", " id = ? ", [ tag.id ]);
|
||||
let bean = await R.findOne("tag", " id = ? ", [ tag.id ]);
|
||||
if (bean == null) {
|
||||
callback({
|
||||
ok: false,
|
||||
msg: "Tag not found",
|
||||
});
|
||||
return;
|
||||
}
|
||||
bean.name = tag.name;
|
||||
bean.color = tag.color;
|
||||
await R.store(bean);
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Saved",
|
||||
tag: await bean.toJSON(),
|
||||
});
|
||||
|
||||
@ -1102,6 +1125,8 @@ let needSetup = false;
|
||||
await setSettings("general", data);
|
||||
server.entryPage = data.entryPage;
|
||||
|
||||
await CacheableDnsHttpAgent.update();
|
||||
|
||||
// Also need to apply timezone globally
|
||||
if (data.serverTimezone) {
|
||||
await server.setTimezone(data.serverTimezone);
|
||||
@ -1473,6 +1498,7 @@ let needSetup = false;
|
||||
proxySocketHandler(socket);
|
||||
dockerSocketHandler(socket);
|
||||
maintenanceSocketHandler(socket);
|
||||
generalSocketHandler(socket, server);
|
||||
|
||||
log.debug("server", "added all socket handlers");
|
||||
|
||||
@ -1595,6 +1621,13 @@ async function afterLogin(socket, user) {
|
||||
for (let monitorID in monitorList) {
|
||||
await Monitor.sendStats(io, monitorID, user.id);
|
||||
}
|
||||
|
||||
// Set server timezone from client browser if not set
|
||||
// It should be run once only
|
||||
if (! await Settings.get("initServerTimezone")) {
|
||||
log.debug("server", "emit initServerTimezone");
|
||||
socket.emit("initServerTimezone");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1733,6 +1766,7 @@ async function shutdownFunction(signal) {
|
||||
|
||||
stopBackgroundJobs();
|
||||
await cloudflaredStop();
|
||||
Settings.stopCacheCleaner();
|
||||
}
|
||||
|
||||
/** Final function called before application exits */
|
||||
|
@ -158,6 +158,13 @@ class Settings {
|
||||
delete Settings.cacheList[key];
|
||||
}
|
||||
}
|
||||
|
||||
static stopCacheCleaner() {
|
||||
if (Settings.cacheCleaner) {
|
||||
clearInterval(Settings.cacheCleaner);
|
||||
Settings.cacheCleaner = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
@ -1,6 +1,7 @@
|
||||
const { checkLogin, setSetting, setting, doubleCheckPassword } = require("../util-server");
|
||||
const { CloudflaredTunnel } = require("node-cloudflared-tunnel");
|
||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||
const { log } = require("../../src/util");
|
||||
const io = UptimeKumaServer.getInstance().io;
|
||||
|
||||
const prefix = "cloudflared_";
|
||||
@ -107,7 +108,7 @@ module.exports.autoStart = async (token) => {
|
||||
|
||||
/** Stop cloudflared */
|
||||
module.exports.stop = async () => {
|
||||
console.log("Stop cloudflared");
|
||||
log.info("cloudflared", "Stop cloudflared");
|
||||
if (cloudflared) {
|
||||
cloudflared.stop();
|
||||
}
|
||||
|
20
server/socket-handlers/general-socket-handler.js
Normal file
20
server/socket-handlers/general-socket-handler.js
Normal file
@ -0,0 +1,20 @@
|
||||
const { log } = require("../../src/util");
|
||||
const { Settings } = require("../settings");
|
||||
const { sendInfo } = require("../client");
|
||||
const { checkLogin } = require("../util-server");
|
||||
|
||||
module.exports.generalSocketHandler = (socket, server) => {
|
||||
|
||||
socket.on("initServerTimezone", async (timezone) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
log.debug("generalSocketHandler", "Timezone: " + timezone);
|
||||
await Settings.set("initServerTimezone", true);
|
||||
await server.setTimezone(timezone);
|
||||
await sendInfo(socket);
|
||||
} catch (e) {
|
||||
log.warn("initServerTimezone", e.message);
|
||||
}
|
||||
});
|
||||
|
||||
};
|
@ -244,6 +244,8 @@ module.exports.maintenanceSocketHandler = (socket) => {
|
||||
socket.userID,
|
||||
]);
|
||||
|
||||
apicache.clear();
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Deleted Successfully.",
|
||||
@ -269,6 +271,8 @@ module.exports.maintenanceSocketHandler = (socket) => {
|
||||
maintenanceID,
|
||||
]);
|
||||
|
||||
apicache.clear();
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Paused Successfully.",
|
||||
@ -294,6 +298,8 @@ module.exports.maintenanceSocketHandler = (socket) => {
|
||||
maintenanceID,
|
||||
]);
|
||||
|
||||
apicache.clear();
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Resume Successfully",
|
||||
|
49
server/uptime-cache-list.js
Normal file
49
server/uptime-cache-list.js
Normal file
@ -0,0 +1,49 @@
|
||||
const { log } = require("../src/util");
|
||||
class UptimeCacheList {
|
||||
/**
|
||||
* list[monitorID][duration]
|
||||
*/
|
||||
static list = {};
|
||||
|
||||
/**
|
||||
* Get the uptime for a specific period
|
||||
* @param {number} monitorID
|
||||
* @param {number} duration
|
||||
* @return {number}
|
||||
*/
|
||||
static getUptime(monitorID, duration) {
|
||||
if (UptimeCacheList.list[monitorID] && UptimeCacheList.list[monitorID][duration]) {
|
||||
log.debug("UptimeCacheList", "getUptime: " + monitorID + " " + duration);
|
||||
return UptimeCacheList.list[monitorID][duration];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add uptime for specified monitor
|
||||
* @param {number} monitorID
|
||||
* @param {number} duration
|
||||
* @param {number} uptime Uptime to add
|
||||
*/
|
||||
static addUptime(monitorID, duration, uptime) {
|
||||
log.debug("UptimeCacheList", "addUptime: " + monitorID + " " + duration);
|
||||
if (!UptimeCacheList.list[monitorID]) {
|
||||
UptimeCacheList.list[monitorID] = {};
|
||||
}
|
||||
UptimeCacheList.list[monitorID][duration] = uptime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cache for specified monitor
|
||||
* @param {number} monitorID
|
||||
*/
|
||||
static clearCache(monitorID) {
|
||||
log.debug("UptimeCacheList", "clearCache: " + monitorID);
|
||||
delete UptimeCacheList.list[monitorID];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
UptimeCacheList,
|
||||
};
|
@ -83,12 +83,13 @@ class UptimeKumaServer {
|
||||
}
|
||||
}
|
||||
|
||||
CacheableDnsHttpAgent.registerGlobalAgent();
|
||||
|
||||
this.io = new Server(this.httpServer);
|
||||
}
|
||||
|
||||
/** Initialise app after the database has been set up */
|
||||
async initAfterDatabaseReady() {
|
||||
await CacheableDnsHttpAgent.update();
|
||||
|
||||
process.env.TZ = await this.getTimezone();
|
||||
dayjs.tz.setDefault(process.env.TZ);
|
||||
log.debug("DEBUG", "Timezone: " + process.env.TZ);
|
||||
@ -98,6 +99,11 @@ class UptimeKumaServer {
|
||||
this.generateMaintenanceTimeslotsInterval = setInterval(this.generateMaintenanceTimeslots, 60 * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send list of monitors to client
|
||||
* @param {Socket} socket
|
||||
* @returns {Object} List of monitors
|
||||
*/
|
||||
async sendMonitorList(socket) {
|
||||
let list = await this.getMonitorJSONList(socket.userID);
|
||||
this.io.to(socket.userID).emit("monitorList", list);
|
||||
@ -134,6 +140,11 @@ class UptimeKumaServer {
|
||||
return await this.sendMaintenanceListByUserID(socket.userID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send list of maintenances to user
|
||||
* @param {number} userID
|
||||
* @returns {Object}
|
||||
*/
|
||||
async sendMaintenanceListByUserID(userID) {
|
||||
let list = await this.getMaintenanceJSONList(userID);
|
||||
this.io.to(userID).emit("maintenanceList", list);
|
||||
@ -185,6 +196,11 @@ class UptimeKumaServer {
|
||||
errorLogStream.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the IP of the client connected to the socket
|
||||
* @param {Socket} socket
|
||||
* @returns {string}
|
||||
*/
|
||||
async getClientIP(socket) {
|
||||
let clientIP = socket.client.conn.remoteAddress;
|
||||
|
||||
@ -203,6 +219,12 @@ class UptimeKumaServer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to get the current server timezone
|
||||
* If this fails, fall back to environment variables and then make a
|
||||
* guess.
|
||||
* @returns {string}
|
||||
*/
|
||||
async getTimezone() {
|
||||
let timezone = await Settings.get("serverTimezone");
|
||||
if (timezone) {
|
||||
@ -214,16 +236,25 @@ class UptimeKumaServer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current offset
|
||||
* @returns {string}
|
||||
*/
|
||||
getTimezoneOffset() {
|
||||
return dayjs().format("Z");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current server timezone and environment variables
|
||||
* @param {string} timezone
|
||||
*/
|
||||
async setTimezone(timezone) {
|
||||
await Settings.set("serverTimezone", timezone, "general");
|
||||
process.env.TZ = timezone;
|
||||
dayjs.tz.setDefault(timezone);
|
||||
}
|
||||
|
||||
/** Load the timeslots for maintenance */
|
||||
async generateMaintenanceTimeslots() {
|
||||
|
||||
let list = await R.find("maintenance_timeslot", " generated_next = 0 AND start_date <= DATETIME('now') ");
|
||||
@ -237,6 +268,7 @@ class UptimeKumaServer {
|
||||
|
||||
}
|
||||
|
||||
/** Stop the server */
|
||||
async stop() {
|
||||
clearTimeout(this.generateMaintenanceTimeslotsInterval);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
const tcpp = require("tcp-ping");
|
||||
const Ping = require("./ping-lite");
|
||||
const ping = require("@louislam/ping");
|
||||
const { R } = require("redbean-node");
|
||||
const { log, genSecret } = require("../src/util");
|
||||
const passwordHash = require("./password-hash");
|
||||
@ -13,9 +13,14 @@ const { badgeConstants } = require("./config");
|
||||
const mssql = require("mssql");
|
||||
const { Client } = require("pg");
|
||||
const postgresConParse = require("pg-connection-string").parse;
|
||||
const mysql = require("mysql2");
|
||||
const { MongoClient } = require("mongodb");
|
||||
const { NtlmClient } = require("axios-ntlm");
|
||||
const { Settings } = require("./settings");
|
||||
const grpc = require("@grpc/grpc-js");
|
||||
const protojs = require("protobufjs");
|
||||
const radiusClient = require("node-radius-client");
|
||||
const redis = require("redis");
|
||||
const {
|
||||
dictionaries: {
|
||||
rfc2865: { file, attributes },
|
||||
@ -23,12 +28,7 @@ const {
|
||||
} = require("node-radius-utils");
|
||||
const dayjs = require("dayjs");
|
||||
|
||||
// From ping-lite
|
||||
exports.WIN = /^win/.test(process.platform);
|
||||
exports.LIN = /^linux/.test(process.platform);
|
||||
exports.MAC = /^darwin/.test(process.platform);
|
||||
exports.FBSD = /^freebsd/.test(process.platform);
|
||||
exports.BSD = /bsd$/.test(process.platform);
|
||||
const isWindows = process.platform === /^win/.test(process.platform);
|
||||
|
||||
/**
|
||||
* Init or reset JWT secret
|
||||
@ -102,18 +102,23 @@ exports.ping = async (hostname) => {
|
||||
*/
|
||||
exports.pingAsync = function (hostname, ipv6 = false) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const ping = new Ping(hostname, {
|
||||
ipv6
|
||||
});
|
||||
|
||||
ping.send(function (err, ms, stdout) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else if (ms === null) {
|
||||
reject(new Error(stdout));
|
||||
ping.promise.probe(hostname, {
|
||||
v6: ipv6,
|
||||
min_reply: 1,
|
||||
deadline: 10,
|
||||
}).then((res) => {
|
||||
// If ping failed, it will set field to unknown
|
||||
if (res.alive) {
|
||||
resolve(res.time);
|
||||
} else {
|
||||
resolve(Math.round(ms));
|
||||
if (isWindows) {
|
||||
reject(new Error(exports.convertToUTF8(res.output)));
|
||||
} else {
|
||||
reject(new Error(res.output));
|
||||
}
|
||||
}
|
||||
}).catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
@ -132,7 +137,7 @@ exports.mqttAsync = function (hostname, topic, okMessage, options = {}) {
|
||||
const { port, username, password, interval = 20 } = options;
|
||||
|
||||
// Adds MQTT protocol to the hostname if not already present
|
||||
if (!/^(?:http|mqtt)s?:\/\//.test(hostname)) {
|
||||
if (!/^(?:http|mqtt|ws)s?:\/\//.test(hostname)) {
|
||||
hostname = "mqtt://" + hostname;
|
||||
}
|
||||
|
||||
@ -142,10 +147,11 @@ exports.mqttAsync = function (hostname, topic, okMessage, options = {}) {
|
||||
reject(new Error("Timeout"));
|
||||
}, interval * 1000 * 0.8);
|
||||
|
||||
log.debug("mqtt", "MQTT connecting");
|
||||
const mqttUrl = `${hostname}:${port}`;
|
||||
|
||||
let client = mqtt.connect(hostname, {
|
||||
port,
|
||||
log.debug("mqtt", `MQTT connecting to ${mqttUrl}`);
|
||||
|
||||
let client = mqtt.connect(mqttUrl, {
|
||||
username,
|
||||
password
|
||||
});
|
||||
@ -245,19 +251,19 @@ exports.dnsResolve = function (hostname, resolverServer, resolverPort, rrtype) {
|
||||
* @param {string} query The query to validate the database with
|
||||
* @returns {Promise<(string[]|Object[]|Object)>}
|
||||
*/
|
||||
exports.mssqlQuery = function (connectionString, query) {
|
||||
return new Promise((resolve, reject) => {
|
||||
mssql.connect(connectionString).then(pool => {
|
||||
return pool.request()
|
||||
.query(query);
|
||||
}).then(result => {
|
||||
resolve(result);
|
||||
}).catch(err => {
|
||||
reject(err);
|
||||
}).finally(() => {
|
||||
mssql.close();
|
||||
});
|
||||
});
|
||||
exports.mssqlQuery = async function (connectionString, query) {
|
||||
let pool;
|
||||
try {
|
||||
pool = new mssql.ConnectionPool(connectionString);
|
||||
await pool.connect();
|
||||
await pool.request().query(query);
|
||||
pool.close();
|
||||
} catch (e) {
|
||||
if (pool) {
|
||||
pool.close();
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -277,9 +283,36 @@ exports.postgresQuery = function (connectionString, query) {
|
||||
|
||||
const client = new Client({ connectionString });
|
||||
|
||||
client.connect();
|
||||
client.connect((err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
client.end();
|
||||
} else {
|
||||
// Connected here
|
||||
client.query(query, (err, res) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(res);
|
||||
}
|
||||
client.end();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return client.query(query)
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Run a query on MySQL/MariaDB
|
||||
* @param {string} connectionString The database connection string
|
||||
* @param {string} query The query to validate the database with
|
||||
* @returns {Promise<(string[]|Object[]|Object)>}
|
||||
*/
|
||||
exports.mysqlQuery = function (connectionString, query) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const connection = mysql.createConnection(connectionString);
|
||||
connection.promise().query(query)
|
||||
.then(res => {
|
||||
resolve(res);
|
||||
})
|
||||
@ -287,11 +320,28 @@ exports.postgresQuery = function (connectionString, query) {
|
||||
reject(err);
|
||||
})
|
||||
.finally(() => {
|
||||
client.end();
|
||||
connection.end();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Connect to and Ping a MongoDB database
|
||||
* @param {string} connectionString The database connection string
|
||||
* @returns {Promise<(string[]|Object[]|Object)>}
|
||||
*/
|
||||
exports.mongodbPing = async function (connectionString) {
|
||||
let client = await MongoClient.connect(connectionString);
|
||||
let dbPing = await client.db().command({ ping: 1 });
|
||||
await client.close();
|
||||
|
||||
if (dbPing["ok"] === 1) {
|
||||
return "UP";
|
||||
} else {
|
||||
throw Error("failed");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Query radius server
|
||||
* @param {string} hostname Hostname of radius server
|
||||
@ -329,6 +379,30 @@ exports.radius = function (
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Redis server ping
|
||||
* @param {string} dsn The redis connection string
|
||||
*/
|
||||
exports.redisPingAsync = function (dsn) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const client = redis.createClient({
|
||||
url: dsn,
|
||||
});
|
||||
client.on("error", (err) => {
|
||||
reject(err);
|
||||
});
|
||||
client.connect().then(() => {
|
||||
client.ping().then((res, err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(res);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve value of setting based on key
|
||||
* @param {string} key Key of setting to retrieve
|
||||
@ -445,6 +519,10 @@ const parseCertificateInfo = function (info) {
|
||||
* @returns {Object} Object containing certificate information
|
||||
*/
|
||||
exports.checkCertificate = function (res) {
|
||||
if (!res.request.res.socket) {
|
||||
throw new Error("No socket found");
|
||||
}
|
||||
|
||||
const info = res.request.res.socket.getPeerCertificate(true);
|
||||
const valid = res.request.res.socket.authorized || false;
|
||||
|
||||
@ -720,3 +798,60 @@ module.exports.timeObjectToUTC = (obj, timezone = undefined) => {
|
||||
module.exports.timeObjectToLocal = (obj, timezone = undefined) => {
|
||||
return timeObjectConvertTimezone(obj, timezone, false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create gRPC client stib
|
||||
* @param {Object} options from gRPC client
|
||||
*/
|
||||
module.exports.grpcQuery = async (options) => {
|
||||
const { grpcUrl, grpcProtobufData, grpcServiceName, grpcEnableTls, grpcMethod, grpcBody } = options;
|
||||
const protocObject = protojs.parse(grpcProtobufData);
|
||||
const protoServiceObject = protocObject.root.lookupService(grpcServiceName);
|
||||
const Client = grpc.makeGenericClientConstructor({});
|
||||
const credentials = grpcEnableTls ? grpc.credentials.createSsl() : grpc.credentials.createInsecure();
|
||||
const client = new Client(
|
||||
grpcUrl,
|
||||
credentials
|
||||
);
|
||||
const grpcService = protoServiceObject.create(function (method, requestData, cb) {
|
||||
const fullServiceName = method.fullName;
|
||||
const serviceFQDN = fullServiceName.split(".");
|
||||
const serviceMethod = serviceFQDN.pop();
|
||||
const serviceMethodClientImpl = `/${serviceFQDN.slice(1).join(".")}/${serviceMethod}`;
|
||||
log.debug("monitor", `gRPC method ${serviceMethodClientImpl}`);
|
||||
client.makeUnaryRequest(
|
||||
serviceMethodClientImpl,
|
||||
arg => arg,
|
||||
arg => arg,
|
||||
requestData,
|
||||
cb);
|
||||
}, false, false);
|
||||
return new Promise((resolve, _) => {
|
||||
try {
|
||||
return grpcService[`${grpcMethod}`](JSON.parse(grpcBody), function (err, response) {
|
||||
const responseData = JSON.stringify(response);
|
||||
if (err) {
|
||||
return resolve({
|
||||
code: err.code,
|
||||
errorMessage: err.details,
|
||||
data: ""
|
||||
});
|
||||
} else {
|
||||
log.debug("monitor:", `gRPC response: ${JSON.stringify(response)}`);
|
||||
return resolve({
|
||||
code: 1,
|
||||
errorMessage: "",
|
||||
data: responseData
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
return resolve({
|
||||
code: -1,
|
||||
errorMessage: `Error ${err}. Please review your gRPC configuration option. The service name must not include package name value, and the method name must follow camelCase format`,
|
||||
data: ""
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
|
@ -3,3 +3,7 @@ html[lang='fa'] {
|
||||
font-family: 'IRANSans', 'Iranian Sans','B Nazanin', 'Tahoma', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, segoe ui, Roboto, helvetica neue, Arial, noto sans, sans-serif, apple color emoji, segoe ui emoji, segoe ui symbol, noto color emoji;
|
||||
}
|
||||
}
|
||||
|
||||
ul.multiselect__content {
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
|
@ -3,8 +3,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dayjs from "dayjs";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
/** Value of date time */
|
||||
|
@ -73,7 +73,7 @@ export default {
|
||||
emits: [ "added" ],
|
||||
data() {
|
||||
return {
|
||||
model: null,
|
||||
modal: null,
|
||||
processing: false,
|
||||
id: null,
|
||||
connectionTypes: [ "socket", "tcp" ],
|
||||
@ -91,11 +91,16 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
|
||||
/** Confirm deletion of docker host */
|
||||
deleteConfirm() {
|
||||
this.modal.hide();
|
||||
this.$refs.confirmDelete.show();
|
||||
},
|
||||
|
||||
/**
|
||||
* Show specified docker host
|
||||
* @param {number} dockerHostID
|
||||
*/
|
||||
show(dockerHostID) {
|
||||
if (dockerHostID) {
|
||||
let found = false;
|
||||
@ -126,6 +131,7 @@ export default {
|
||||
this.modal.show();
|
||||
},
|
||||
|
||||
/** Add docker host */
|
||||
submit() {
|
||||
this.processing = true;
|
||||
this.$root.getSocket().emit("addDockerHost", this.dockerHost, this.id, (res) => {
|
||||
@ -144,6 +150,7 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
/** Test the docker host */
|
||||
test() {
|
||||
this.processing = true;
|
||||
this.$root.getSocket().emit("testDockerHost", this.dockerHost, (res) => {
|
||||
@ -152,6 +159,7 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
/** Delete this docker host */
|
||||
deleteDockerHost() {
|
||||
this.processing = true;
|
||||
this.$root.getSocket().emit("deleteDockerHost", this.id, (res) => {
|
||||
|
@ -206,6 +206,16 @@ export default {
|
||||
.search-icon {
|
||||
padding: 10px;
|
||||
color: #c0c0c0;
|
||||
|
||||
// Clear filter button (X)
|
||||
svg[data-icon="times"] {
|
||||
cursor: pointer;
|
||||
transition: all ease-in-out 0.1s;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-input {
|
||||
|
@ -17,6 +17,7 @@
|
||||
<option value="http">HTTP</option>
|
||||
<option value="socks">SOCKS</option>
|
||||
<option value="socks5">SOCKS v5</option>
|
||||
<option value="socks5h">SOCKS v5 (+DNS)</option>
|
||||
<option value="socks4">SOCKS v4</option>
|
||||
</select>
|
||||
</div>
|
||||
|
@ -41,7 +41,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
displayText() {
|
||||
if (this.item.value === "") {
|
||||
if (this.item.value === "" || this.item.value === undefined) {
|
||||
return this.item.name;
|
||||
} else {
|
||||
return `${this.item.name}: ${this.item.value}`;
|
||||
|
376
src/components/TagEditDialog.vue
Normal file
376
src/components/TagEditDialog.vue
Normal file
@ -0,0 +1,376 @@
|
||||
<template>
|
||||
<form @submit.prevent="submit">
|
||||
<div ref="modal" class="modal fade" tabindex="-1" data-bs-backdrop="static">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 id="exampleModalLabel" class="modal-title">
|
||||
{{ $t("Edit Tag") }}
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" />
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label for="tag-name" class="form-label">{{ $t("Name") }}</label>
|
||||
<input id="tag-name" v-model="tag.name" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="tag-color" class="form-label">{{ $t("Color") }}</label>
|
||||
<div class="d-flex">
|
||||
<div class="col-8 pe-1">
|
||||
<vue-multiselect
|
||||
v-model="selectedColor"
|
||||
:options="colorOptions"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
:placeholder="$t('color')"
|
||||
track-by="color"
|
||||
label="name"
|
||||
select-label=""
|
||||
deselect-label=""
|
||||
>
|
||||
<template #option="{ option }">
|
||||
<div
|
||||
class="mx-2 py-1 px-3 rounded d-inline-flex"
|
||||
style="height: 24px; color: white;"
|
||||
:style="{ backgroundColor: option.color + ' !important' }"
|
||||
>
|
||||
<span>{{ option.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #singleLabel="{ option }">
|
||||
<div
|
||||
class="py-1 px-3 rounded d-inline-flex"
|
||||
style="height: 24px; color: white;"
|
||||
:style="{ backgroundColor: option.color + ' !important' }"
|
||||
>
|
||||
<span>{{ option.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</vue-multiselect>
|
||||
</div>
|
||||
<div class="col-4 ps-1">
|
||||
<input id="tag-color-hex" v-model="tag.color" type="text" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="tag-monitors" class="form-label">{{ $tc("Monitor", selectedMonitors.length) }}</label>
|
||||
<div class="tag-monitors-list">
|
||||
<router-link v-for="monitor in selectedMonitors" :key="monitor.id" class="d-flex align-items-center justify-content-between text-decoration-none tag-monitors-list-row py-2 px-3" :to="monitorURL(monitor.id)" @click="modal.hide()">
|
||||
<span>{{ monitor.name }}</span>
|
||||
<button type="button" class="btn-rm-monitor btn btn-outline-danger ms-2 py-1" @click.stop.prevent="removeMonitor(monitor.id)">
|
||||
<font-awesome-icon class="" icon="times" />
|
||||
</button>
|
||||
</router-link>
|
||||
</div>
|
||||
<div v-if="allMonitorList.length > 0" class="pt-3 px-3">
|
||||
<label class="form-label">{{ $t("Add a monitor") }}:</label>
|
||||
<select v-model="selectedAddMonitor" class="form-control">
|
||||
<option v-for="monitor in allMonitorList" :key="monitor.id" :value="monitor">{{ monitor.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button v-if="tag" type="button" class="btn btn-danger" :disabled="processing" @click="deleteConfirm">
|
||||
{{ $t("Delete") }}
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary" :disabled="processing">
|
||||
<div v-if="processing" class="spinner-border spinner-border-sm me-1"></div>
|
||||
{{ $t("Save") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<Confirm ref="confirmDelete" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="deleteTag">
|
||||
{{ $t("confirmDeleteTagMsg") }}
|
||||
</Confirm>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Modal } from "bootstrap";
|
||||
import Confirm from "./Confirm.vue";
|
||||
import VueMultiselect from "vue-multiselect";
|
||||
import { colorOptions } from "../util-frontend";
|
||||
import { useToast } from "vue-toastification";
|
||||
import { getMonitorRelativeURL } from "../util.ts";
|
||||
const toast = useToast();
|
||||
|
||||
export default {
|
||||
components: {
|
||||
VueMultiselect,
|
||||
Confirm,
|
||||
},
|
||||
props: {
|
||||
updated: {
|
||||
type: Function,
|
||||
default: () => {},
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
modal: null,
|
||||
processing: false,
|
||||
selectedColor: {
|
||||
name: null,
|
||||
color: null,
|
||||
},
|
||||
tag: {
|
||||
id: null,
|
||||
name: "",
|
||||
color: "",
|
||||
// Do not set default value here, please scroll to show()
|
||||
},
|
||||
monitors: [],
|
||||
removingMonitor: [],
|
||||
addingMonitor: [],
|
||||
selectedAddMonitor: null,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
colorOptions() {
|
||||
if (!colorOptions(this).find(option => option.color === this.tag.color)) {
|
||||
return colorOptions(this).concat(
|
||||
{
|
||||
name: "custom",
|
||||
color: this.tag.color
|
||||
});
|
||||
} else {
|
||||
return colorOptions(this);
|
||||
}
|
||||
},
|
||||
selectedMonitors() {
|
||||
return this.monitors
|
||||
.concat(Object.values(this.$root.monitorList).filter(monitor => this.addingMonitor.includes(monitor.id)))
|
||||
.filter(monitor => !this.removingMonitor.includes(monitor.id));
|
||||
},
|
||||
allMonitorList() {
|
||||
return Object.values(this.$root.monitorList).filter(monitor => !this.selectedMonitors.includes(monitor));
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
// Set color option to "Custom" when a unknown color is entered
|
||||
"tag.color"(to, from) {
|
||||
if (colorOptions(this).find(x => x.color === to) == null) {
|
||||
this.selectedColor.name = this.$t("Custom");
|
||||
this.selectedColor.color = to;
|
||||
}
|
||||
},
|
||||
selectedColor(to, from) {
|
||||
if (to != null) {
|
||||
this.tag.color = to.color;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Selected a monitor and add to the list.
|
||||
*/
|
||||
selectedAddMonitor(monitor) {
|
||||
if (monitor) {
|
||||
if (this.removingMonitor.includes(monitor.id)) {
|
||||
this.removingMonitor = this.removingMonitor.filter(id => id !== monitor.id);
|
||||
} else {
|
||||
this.addingMonitor.push(monitor.id);
|
||||
}
|
||||
this.selectedAddMonitor = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.modal = new Modal(this.$refs.modal);
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Show confirmation for deleting a tag
|
||||
*/
|
||||
deleteConfirm() {
|
||||
this.$refs.confirmDelete.show();
|
||||
},
|
||||
|
||||
/**
|
||||
* Load tag information for display in the edit dialog
|
||||
* @param {Object} tag tag object to edit
|
||||
* @returns {void}
|
||||
*/
|
||||
show(tag) {
|
||||
if (tag) {
|
||||
this.selectedColor = this.colorOptions.find(x => x.color === tag.color) ?? {
|
||||
name: this.$t("Custom"),
|
||||
color: tag.color
|
||||
};
|
||||
this.tag.id = tag.id;
|
||||
this.tag.name = tag.name;
|
||||
this.tag.color = tag.color;
|
||||
this.monitors = this.monitorsByTag(tag.id);
|
||||
this.removingMonitor = [];
|
||||
this.addingMonitor = [];
|
||||
this.selectedAddMonitor = null;
|
||||
}
|
||||
|
||||
this.modal.show();
|
||||
},
|
||||
|
||||
/**
|
||||
* Submit tag and monitorTag changes to server
|
||||
* @returns {void}
|
||||
*/
|
||||
async submit() {
|
||||
this.processing = true;
|
||||
let editResult = true;
|
||||
|
||||
for (let addId of this.addingMonitor) {
|
||||
await this.addMonitorTagAsync(this.tag.id, addId, "").then((res) => {
|
||||
if (!res.ok) {
|
||||
toast.error(res.msg);
|
||||
editResult = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (let removeId of this.removingMonitor) {
|
||||
this.monitors.find(monitor => monitor.id === removeId)?.tags.forEach(async (monitorTag) => {
|
||||
await this.deleteMonitorTagAsync(this.tag.id, removeId, monitorTag.value).then((res) => {
|
||||
if (!res.ok) {
|
||||
toast.error(res.msg);
|
||||
editResult = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
this.$root.getSocket().emit("editTag", this.tag, (res) => {
|
||||
this.$root.toastRes(res);
|
||||
this.processing = false;
|
||||
|
||||
if (res.ok && editResult) {
|
||||
this.updated();
|
||||
this.modal.hide();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete the editing tag from server
|
||||
* @returns {void}
|
||||
*/
|
||||
deleteTag() {
|
||||
this.processing = true;
|
||||
this.$root.getSocket().emit("deleteTag", this.tag.id, (res) => {
|
||||
this.$root.toastRes(res);
|
||||
this.processing = false;
|
||||
|
||||
if (res.ok) {
|
||||
this.updated();
|
||||
this.modal.hide();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove a monitor from the monitors list locally
|
||||
* @param {number} id id of the tag to remove
|
||||
* @returns {void}
|
||||
*/
|
||||
removeMonitor(id) {
|
||||
if (this.addingMonitor.includes(id)) {
|
||||
this.addingMonitor = this.addingMonitor.filter(x => x !== id);
|
||||
} else {
|
||||
this.removingMonitor.push(id);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get monitors which has a specific tag locally
|
||||
* @param {number} tagId id of the tag to filter
|
||||
* @returns {Object[]} list of monitors which has a specific tag
|
||||
*/
|
||||
monitorsByTag(tagId) {
|
||||
return Object.values(this.$root.monitorList).filter((monitor) => {
|
||||
return monitor.tags.find(monitorTag => monitorTag.tag_id === tagId);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get URL of monitor
|
||||
* @param {number} id ID of monitor
|
||||
* @returns {string} Relative URL of monitor
|
||||
*/
|
||||
monitorURL(id) {
|
||||
return getMonitorRelativeURL(id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a tag to a monitor asynchronously
|
||||
* @param {number} tagId ID of tag to add
|
||||
* @param {number} monitorId ID of monitor to add tag to
|
||||
* @param {string} value Value of tag
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
addMonitorTagAsync(tagId, monitorId, value) {
|
||||
return new Promise((resolve) => {
|
||||
this.$root.getSocket().emit("addMonitorTag", tagId, monitorId, value, resolve);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Delete a tag from a monitor asynchronously
|
||||
* @param {number} tagId ID of tag to remove
|
||||
* @param {number} monitorId ID of monitor to remove tag from
|
||||
* @param {string} value Value of tag
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
deleteMonitorTagAsync(tagId, monitorId, value) {
|
||||
return new Promise((resolve) => {
|
||||
this.$root.getSocket().emit("deleteMonitorTag", tagId, monitorId, value, resolve);
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../assets/vars.scss";
|
||||
|
||||
.dark {
|
||||
.modal-dialog .form-text, .modal-dialog p {
|
||||
color: $dark-font-color;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-rm-monitor {
|
||||
padding-left: 11px;
|
||||
padding-right: 11px;
|
||||
}
|
||||
|
||||
.tag-monitors-list {
|
||||
max-height: 40vh;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.tag-monitors-list .tag-monitors-list-row {
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.125);
|
||||
|
||||
.dark & {
|
||||
border-bottom: 1px solid $dark-border-color;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $highlight-white;
|
||||
}
|
||||
|
||||
.dark &:hover {
|
||||
background-color: $dark-bg2;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
@ -130,6 +130,7 @@
|
||||
import { Modal } from "bootstrap";
|
||||
import VueMultiselect from "vue-multiselect";
|
||||
import { useToast } from "vue-toastification";
|
||||
import { colorOptions } from "../util-frontend";
|
||||
import Tag from "../components/Tag.vue";
|
||||
const toast = useToast();
|
||||
|
||||
@ -176,24 +177,7 @@ export default {
|
||||
return this.preSelectedTags.concat(this.newTags).filter(tag => !this.deleteTags.find(monitorTag => monitorTag.id === tag.id));
|
||||
},
|
||||
colorOptions() {
|
||||
return [
|
||||
{ name: this.$t("Gray"),
|
||||
color: "#4B5563" },
|
||||
{ name: this.$t("Red"),
|
||||
color: "#DC2626" },
|
||||
{ name: this.$t("Orange"),
|
||||
color: "#D97706" },
|
||||
{ name: this.$t("Green"),
|
||||
color: "#059669" },
|
||||
{ name: this.$t("Blue"),
|
||||
color: "#2563EB" },
|
||||
{ name: this.$t("Indigo"),
|
||||
color: "#4F46E5" },
|
||||
{ name: this.$t("Purple"),
|
||||
color: "#7C3AED" },
|
||||
{ name: this.$t("Pink"),
|
||||
color: "#DB2777" },
|
||||
];
|
||||
return colorOptions(this);
|
||||
},
|
||||
validateDraftTag() {
|
||||
let nameInvalid = false;
|
||||
@ -204,7 +188,7 @@ export default {
|
||||
nameInvalid = false;
|
||||
valueInvalid = false;
|
||||
invalid = false;
|
||||
} else if (this.existingTags.filter(tag => tag.name === this.newDraftTag.name).length > 0) {
|
||||
} else if (this.existingTags.filter(tag => tag.name === this.newDraftTag.name).length > 0 && this.newDraftTag.select == null) {
|
||||
// Try to create new tag with existing name
|
||||
nameInvalid = true;
|
||||
invalid = true;
|
||||
|
@ -1,8 +1,10 @@
|
||||
<template>
|
||||
<span :class="className" :title="24 + $t('-hour')">{{ uptime }}</span>
|
||||
<span :class="className" :title="title">{{ uptime }}</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { DOWN, MAINTENANCE, PENDING, UP } from "../util.ts";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
/** Monitor this represents */
|
||||
@ -24,7 +26,6 @@ export default {
|
||||
|
||||
computed: {
|
||||
uptime() {
|
||||
|
||||
if (this.type === "maintenance") {
|
||||
return this.$t("statusMaintenance");
|
||||
}
|
||||
@ -39,19 +40,19 @@ export default {
|
||||
},
|
||||
|
||||
color() {
|
||||
if (this.type === "maintenance" || this.monitor.maintenance) {
|
||||
if (this.lastHeartBeat.status === MAINTENANCE) {
|
||||
return "maintenance";
|
||||
}
|
||||
|
||||
if (this.lastHeartBeat.status === 0) {
|
||||
if (this.lastHeartBeat.status === DOWN) {
|
||||
return "danger";
|
||||
}
|
||||
|
||||
if (this.lastHeartBeat.status === 1) {
|
||||
if (this.lastHeartBeat.status === UP) {
|
||||
return "primary";
|
||||
}
|
||||
|
||||
if (this.lastHeartBeat.status === 2) {
|
||||
if (this.lastHeartBeat.status === PENDING) {
|
||||
return "warning";
|
||||
}
|
||||
|
||||
@ -75,6 +76,14 @@ export default {
|
||||
|
||||
return "";
|
||||
},
|
||||
|
||||
title() {
|
||||
if (this.type === "720") {
|
||||
return `30${this.$t("-day")}`;
|
||||
}
|
||||
|
||||
return `24${this.$t("-hour")}`;
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
36
src/components/notifications/Kook.vue
Normal file
36
src/components/notifications/Kook.vue
Normal file
@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="kook-bot-token" class="form-label">{{ $t("Bot Token") }}</label>
|
||||
<HiddenInput id="kook-bot-token" v-model="$parent.notification.kookBotToken" :required="true" autocomplete="new-password"></HiddenInput>
|
||||
<i18n-t tag="div" keypath="wayToGetKookBotToken" class="form-text">
|
||||
<a href="https://developer.kookapp.cn/bot" target="_blank">https://developer.kookapp.cn/bot</a>
|
||||
</i18n-t>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<div class="form-text">
|
||||
<p style="margin-top: 8px;">
|
||||
{{ $t("wayToGetKookGuildID") }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;">
|
||||
<a href="https://developer.kookapp.cn" target="_blank">https://developer.kookapp.cn</a>
|
||||
</i18n-t>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HiddenInput from "../HiddenInput.vue";
|
||||
export default {
|
||||
components: {
|
||||
HiddenInput,
|
||||
}
|
||||
};
|
||||
</script>
|
@ -27,6 +27,10 @@
|
||||
<HiddenInput id="ntfy-password" v-model="$parent.notification.ntfypassword" autocomplete="new-password"></HiddenInput>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="ntfy-icon" class="form-label">{{ $t("IconUrl") }}</label>
|
||||
<input id="ntfy-icon" v-model="$parent.notification.ntfyIcon" type="text" class="form-control">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -26,6 +26,10 @@
|
||||
<label for="promosms-sender-name" class="form-label">{{ $t("promosmsSMSSender") }}</label>
|
||||
<input id="promosms-sender-name" v-model="$parent.notification.promosmsSenderName" type="text" minlength="3" maxlength="11" class="form-control">
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input id="promosms-allow-long" v-model="$parent.notification.promosmsAllowLongSMS" type="checkbox" class="form-check-input">
|
||||
<label for="promosms-allow-long" class="form-label">{{ $t("promosmsAllowLongSMS") }}</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
40
src/components/notifications/SMSEagle.vue
Normal file
40
src/components/notifications/SMSEagle.vue
Normal file
@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="smseagle-url" class="form-label">{{ $t("smseagleUrl") }}</label>
|
||||
<input id="smseagle-url" v-model="$parent.notification.smseagleUrl" type="text" minlength="7" class="form-control" placeholder="http://127.0.0.1" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="smseagle-token" class="form-label">{{ $t("smseagleToken") }}</label>
|
||||
<HiddenInput id="smseagle-token" v-model="$parent.notification.smseagleToken" :required="true"></HiddenInput>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="smseagle-recipient-type" class="form-label">{{ $t("smseagleRecipientType") }}</label>
|
||||
<select id="smseagle-recipient-type" v-model="$parent.notification.smseagleRecipientType" class="form-select">
|
||||
<option value="smseagle-to" selected>{{ $t("smseagleTo") }}</option>
|
||||
<option value="smseagle-group">{{ $t("smseagleGroup") }}</option>
|
||||
<option value="smseagle-contact">{{ $t("smseagleContact") }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="smseagle-recipient" class="form-label">{{ $t("smseagleRecipient") }}</label>
|
||||
<input id="smseagle-recipient" v-model="$parent.notification.smseagleRecipient" type="text" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="smseagle-priority" class="form-label">{{ $t("smseaglePriority") }}</label>
|
||||
<input id="smseagle-priority" v-model="$parent.notification.smseaglePriority" type="number" class="form-control" min="0" max="9" step="1" placeholder="0">
|
||||
</div>
|
||||
<div class="mb-3 form-check form-switch">
|
||||
<label for="smseagle-encoding" class="form-label">{{ $t("smseagleEncoding") }}</label>
|
||||
<input id="smseagle-encoding" v-model="$parent.notification.smseagleEncoding" type="checkbox" class="form-check-input">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HiddenInput from "../HiddenInput.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HiddenInput,
|
||||
},
|
||||
};
|
||||
</script>
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="smsmanager-key" class="form-label">API Key</label>
|
||||
<label for="smsmanager-key" class="form-label">{{ $t("API Key") }}</label>
|
||||
<div class="form-text">
|
||||
{{ $t("SMSManager API Docs") }}
|
||||
<a href="https://smsmanager.cz/api/http#send" target="_blank">{{ $t("here") }}</a>
|
||||
@ -17,9 +17,9 @@
|
||||
<div class="mb-3">
|
||||
<label for="smsmanager-messageType" class="form-label">{{ $t("Gateway Type") }}</label>
|
||||
<select id="smsmanager-messageType" v-model="$parent.notification.messageType" class="form-select">
|
||||
<option value="economy">Economy</option>
|
||||
<option value="lowcost">Lowcost</option>
|
||||
<option value="high" selected>High</option>
|
||||
<option value="economy">{{ $t("Economy") }}</option>
|
||||
<option value="lowcost">{{ $t("Lowcost") }}</option>
|
||||
<option value="high" selected>{{ $t("High") }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
|
32
src/components/notifications/Splunk.vue
Normal file
32
src/components/notifications/Splunk.vue
Normal file
@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="splunk-rest-url" class="form-label">{{ $t("Splunk Rest URL") }}</label>
|
||||
<HiddenInput id="splunk-rest-url" v-model="$parent.notification.splunkRestURL" :required="true" autocomplete="false"></HiddenInput>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="splunk-severity" class="form-label">{{ $t("Severity") }}</label>
|
||||
<select id="splunk-severity" v-model="$parent.notification.splunkSeverity" class="form-select">
|
||||
<option value="INFO">{{ $t("info") }}</option>
|
||||
<option value="WARNING">{{ $t("warning") }}</option>
|
||||
<option value="CRITICAL" selected="selected">{{ $t("critical") }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="splunk-resolve" class="form-label">{{ $t("Auto resolve or acknowledged") }}</label>
|
||||
<select id="splunk-resolve" v-model="$parent.notification.splunkAutoResolve" class="form-select">
|
||||
<option value="0" selected="selected">{{ $t("do nothing") }}</option>
|
||||
<option value="ACKNOWLEDGEMENT">{{ $t("auto acknowledged") }}</option>
|
||||
<option value="RECOVERY">{{ $t("auto resolve") }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HiddenInput from "../HiddenInput.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HiddenInput,
|
||||
},
|
||||
};
|
||||
</script>
|
@ -42,6 +42,11 @@ export default {
|
||||
HiddenInput,
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Get the URL for telegram updates
|
||||
* @param {string} [mode=masked] Should the token be masked?
|
||||
* @returns {string} formatted URL
|
||||
*/
|
||||
telegramGetUpdatesURL(mode = "masked") {
|
||||
let token = `<${this.$t("YOUR BOT TOKEN HERE")}>`;
|
||||
|
||||
@ -55,6 +60,8 @@ export default {
|
||||
|
||||
return `https://api.telegram.org/bot${token}/getUpdates`;
|
||||
},
|
||||
|
||||
/** Get the telegram chat ID */
|
||||
async autoGetTelegramChatID() {
|
||||
try {
|
||||
let res = await axios.get(this.telegramGetUpdatesURL("withToken"));
|
||||
|
@ -1,22 +1,32 @@
|
||||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="webhook-url" class="form-label">{{ $t("Post URL") }}</label>
|
||||
<input id="webhook-url" v-model="$parent.notification.webhookURL" type="url" pattern="https?://.+" class="form-control" required>
|
||||
<input
|
||||
id="webhook-url"
|
||||
v-model="$parent.notification.webhookURL"
|
||||
type="url"
|
||||
pattern="https?://.+"
|
||||
class="form-control"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="webhook-content-type" class="form-label">{{ $t("Content Type") }}</label>
|
||||
<select id="webhook-content-type" v-model="$parent.notification.webhookContentType" class="form-select" required>
|
||||
<option value="json">
|
||||
application/json
|
||||
</option>
|
||||
<option value="form-data">
|
||||
multipart/form-data
|
||||
</option>
|
||||
<label for="webhook-content-type" class="form-label">{{
|
||||
$t("Content Type")
|
||||
}}</label>
|
||||
<select
|
||||
id="webhook-content-type"
|
||||
v-model="$parent.notification.webhookContentType"
|
||||
class="form-select"
|
||||
required
|
||||
>
|
||||
<option value="json">application/json</option>
|
||||
<option value="form-data">multipart/form-data</option>
|
||||
</select>
|
||||
|
||||
<div class="form-text">
|
||||
<p>{{ $t("webhookJsonDesc", ["\"application/json\""]) }}</p>
|
||||
<p>{{ $t("webhookJsonDesc", ['"application/json"']) }}</p>
|
||||
<i18n-t tag="p" keypath="webhookFormDataDesc">
|
||||
<template #multipart>"multipart/form-data"</template>
|
||||
<template #decodeFunction>
|
||||
@ -25,4 +35,44 @@
|
||||
</i18n-t>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<i18n-t
|
||||
tag="label"
|
||||
class="form-label"
|
||||
for="additionalHeaders"
|
||||
keypath="webhookAdditionalHeadersTitle"
|
||||
>
|
||||
</i18n-t>
|
||||
<textarea
|
||||
id="additionalHeaders"
|
||||
v-model="$parent.notification.webhookAdditionalHeaders"
|
||||
class="form-control"
|
||||
:placeholder="headersPlaceholder"
|
||||
></textarea>
|
||||
<div class="form-text">
|
||||
<i18n-t tag="p" keypath="webhookAdditionalHeadersDesc"> </i18n-t>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
computed: {
|
||||
headersPlaceholder() {
|
||||
return this.$t("Example:", [
|
||||
`
|
||||
{
|
||||
"HeaderName": "HeaderValue"
|
||||
}`,
|
||||
]);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
textarea {
|
||||
min-height: 200px;
|
||||
}
|
||||
</style>
|
||||
|
18
src/components/notifications/ZohoCliq.vue
Normal file
18
src/components/notifications/ZohoCliq.vue
Normal file
@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="zcliq-webhookurl" class="form-label">{{ $t("Webhook URL") }}</label>
|
||||
<input
|
||||
id="zcliq-webhookurl"
|
||||
v-model="$parent.notification.webhookUrl"
|
||||
type="text"
|
||||
class="form-control"
|
||||
required
|
||||
/>
|
||||
<i18n-t tag="div" keypath="wayToGetZohoCliqURL" class="form-text">
|
||||
<a
|
||||
href="https://www.zoho.com/cliq/help/platform/webhook-tokens.html"
|
||||
target="_blank"
|
||||
>{{ $t("here") }}</a>
|
||||
</i18n-t>
|
||||
</div>
|
||||
</template>
|
@ -12,6 +12,7 @@ import GoogleChat from "./GoogleChat.vue";
|
||||
import Gorush from "./Gorush.vue";
|
||||
import Gotify from "./Gotify.vue";
|
||||
import HomeAssistant from "./HomeAssistant.vue";
|
||||
import Kook from "./Kook.vue";
|
||||
import Line from "./Line.vue";
|
||||
import LineNotify from "./LineNotify.vue";
|
||||
import LunaSea from "./LunaSea.vue";
|
||||
@ -33,6 +34,7 @@ import Signal from "./Signal.vue";
|
||||
import SMSManager from "./SMSManager.vue";
|
||||
import Slack from "./Slack.vue";
|
||||
import Squadcast from "./Squadcast.vue";
|
||||
import SMSEagle from "./SMSEagle.vue";
|
||||
import Stackfield from "./Stackfield.vue";
|
||||
import STMP from "./SMTP.vue";
|
||||
import Teams from "./Teams.vue";
|
||||
@ -41,6 +43,8 @@ import Telegram from "./Telegram.vue";
|
||||
import Webhook from "./Webhook.vue";
|
||||
import WeCom from "./WeCom.vue";
|
||||
import GoAlert from "./GoAlert.vue";
|
||||
import ZohoCliq from "./ZohoCliq.vue";
|
||||
import Splunk from "./Splunk.vue";
|
||||
|
||||
/**
|
||||
* Manage all notification form.
|
||||
@ -62,6 +66,7 @@ const NotificationFormList = {
|
||||
"gorush": Gorush,
|
||||
"gotify": Gotify,
|
||||
"HomeAssistant": HomeAssistant,
|
||||
"Kook": Kook,
|
||||
"line": Line,
|
||||
"LineNotify": LineNotify,
|
||||
"lunasea": LunaSea,
|
||||
@ -83,14 +88,17 @@ const NotificationFormList = {
|
||||
"SMSManager": SMSManager,
|
||||
"slack": Slack,
|
||||
"squadcast": Squadcast,
|
||||
"SMSEagle": SMSEagle,
|
||||
"smtp": STMP,
|
||||
"stackfield": Stackfield,
|
||||
"teams": Teams,
|
||||
"telegram": Telegram,
|
||||
"Splunk": Splunk,
|
||||
"webhook": Webhook,
|
||||
"WeCom": WeCom,
|
||||
"GoAlert": GoAlert,
|
||||
"ServerChan": ServerChan,
|
||||
"ZohoCliq": ZohoCliq
|
||||
};
|
||||
|
||||
export default NotificationFormList;
|
||||
|
@ -49,7 +49,7 @@
|
||||
v-model="settings.searchEngineIndex"
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
name="flexRadioDefault"
|
||||
name="searchEngineIndex"
|
||||
:value="true"
|
||||
required
|
||||
/>
|
||||
@ -63,7 +63,7 @@
|
||||
v-model="settings.searchEngineIndex"
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
name="flexRadioDefault"
|
||||
name="searchEngineIndex"
|
||||
:value="false"
|
||||
required
|
||||
/>
|
||||
@ -150,6 +150,46 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DNS Cache -->
|
||||
<div class="mb-4">
|
||||
<label class="form-label">
|
||||
{{ $t("Enable DNS Cache") }}
|
||||
<div class="form-text">
|
||||
⚠️ {{ $t("dnsCacheDescription") }}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<div class="form-check">
|
||||
<input
|
||||
id="dnsCacheEnable"
|
||||
v-model="settings.dnsCache"
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
name="dnsCache"
|
||||
:value="true"
|
||||
required
|
||||
/>
|
||||
<label class="form-check-label" for="dnsCacheEnable">
|
||||
{{ $t("Enable") }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<input
|
||||
id="dnsCacheDisable"
|
||||
v-model="settings.dnsCache"
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
name="dnsCache"
|
||||
:value="false"
|
||||
required
|
||||
/>
|
||||
<label class="form-check-label" for="dnsCacheDisable">
|
||||
{{ $t("Disable") }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Save Button -->
|
||||
<div>
|
||||
<button class="btn btn-primary" type="submit">
|
||||
|
@ -7,6 +7,7 @@
|
||||
settings.keepDataPeriodDays,
|
||||
])
|
||||
}}
|
||||
{{ $t("infiniteRetention") }}
|
||||
</label>
|
||||
<input
|
||||
id="keepDataPeriodDays"
|
||||
@ -14,9 +15,12 @@
|
||||
type="number"
|
||||
class="form-control"
|
||||
required
|
||||
min="1"
|
||||
min="0"
|
||||
step="1"
|
||||
/>
|
||||
<div v-if="settings.keepDataPeriodDays < 0" class="form-text">
|
||||
{{ $t("dataRetentionTimeError") }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-4">
|
||||
<button class="btn btn-primary" type="button" @click="saveSettings()">
|
||||
|
@ -191,6 +191,7 @@ export default {
|
||||
location.reload();
|
||||
},
|
||||
|
||||
/** Show confirmation dialog for disable auth */
|
||||
confirmDisableAuth() {
|
||||
this.$refs.confirmDisableAuth.show();
|
||||
},
|
||||
|
171
src/components/settings/Tags.vue
Normal file
171
src/components/settings/Tags.vue
Normal file
@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="tags-list my-3">
|
||||
<div v-for="(tag, index) in tagsList" :key="tag.id" class="d-flex align-items-center mx-4 py-1 tags-list-row" :disabled="processing" @click="editTag(index)">
|
||||
<div class="col-5 ps-1">
|
||||
<Tag :item="tag" />
|
||||
</div>
|
||||
<div class="col-5 px-1">
|
||||
<div>{{ monitorsByTag(tag.id).length }} {{ $tc("Monitor", monitorsByTag(tag.id).length) }}</div>
|
||||
</div>
|
||||
<div class="col-2 pe-3 d-flex justify-content-end">
|
||||
<button type="button" class="btn ms-2 py-1">
|
||||
<font-awesome-icon class="" icon="edit" />
|
||||
</button>
|
||||
<button type="button" class="btn-rm-tag btn btn-outline-danger ms-2 py-1" :disabled="processing" @click.stop="deleteConfirm(index)">
|
||||
<font-awesome-icon class="" icon="trash" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TagEditDialog ref="tagEditDialog" :updated="tagsUpdated" />
|
||||
<Confirm ref="confirmDelete" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="deleteTag">
|
||||
{{ $t("confirmDeleteTagMsg") }}
|
||||
</Confirm>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useToast } from "vue-toastification";
|
||||
import TagEditDialog from "../../components/TagEditDialog.vue";
|
||||
import Tag from "../Tag.vue";
|
||||
import Confirm from "../Confirm.vue";
|
||||
const toast = useToast();
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Confirm,
|
||||
TagEditDialog,
|
||||
Tag,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
processing: false,
|
||||
tagsList: null,
|
||||
deletingTag: null,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
settings() {
|
||||
return this.$parent.$parent.$parent.settings;
|
||||
},
|
||||
saveSettings() {
|
||||
return this.$parent.$parent.$parent.saveSettings;
|
||||
},
|
||||
settingsLoaded() {
|
||||
return this.$parent.$parent.$parent.settingsLoaded;
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.getExistingTags();
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Reflect tag changes in the UI by fetching data. Callback for the edit tag dialog.
|
||||
* @returns {void}
|
||||
*/
|
||||
tagsUpdated() {
|
||||
this.getExistingTags();
|
||||
this.$root.getMonitorList();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get list of tags from server
|
||||
* @returns {void}
|
||||
*/
|
||||
getExistingTags() {
|
||||
this.processing = true;
|
||||
this.$root.getSocket().emit("getTags", (res) => {
|
||||
this.processing = false;
|
||||
if (res.ok) {
|
||||
this.tagsList = res.tags;
|
||||
} else {
|
||||
toast.error(res.msg);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Show confirmation for deleting a tag
|
||||
* @param {number} index index of the tag to delete in the local tagsList
|
||||
* @returns {void}
|
||||
*/
|
||||
deleteConfirm(index) {
|
||||
this.deletingTag = this.tagsList[index];
|
||||
this.$refs.confirmDelete.show();
|
||||
},
|
||||
|
||||
/**
|
||||
* Show dialog for editing a tag
|
||||
* @param {number} index index of the tag to edit in the local tagsList
|
||||
* @returns {void}
|
||||
*/
|
||||
editTag(index) {
|
||||
this.$refs.tagEditDialog.show(this.tagsList[index]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete the tag "deletingTag" from server
|
||||
* @returns {void}
|
||||
*/
|
||||
deleteTag() {
|
||||
this.processing = true;
|
||||
this.$root.getSocket().emit("deleteTag", this.deletingTag.id, (res) => {
|
||||
this.$root.toastRes(res);
|
||||
this.processing = false;
|
||||
|
||||
if (res.ok) {
|
||||
this.tagsUpdated();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get monitors which has a specific tag locally
|
||||
* @param {number} tagId id of the tag to filter
|
||||
* @returns {Object[]} list of monitors which has a specific tag
|
||||
*/
|
||||
monitorsByTag(tagId) {
|
||||
return Object.values(this.$root.monitorList).filter((monitor) => {
|
||||
return monitor.tags.find(monitorTag => monitorTag.tag_id === tagId);
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../assets/vars.scss";
|
||||
|
||||
.btn-rm-tag {
|
||||
padding-left: 11px;
|
||||
padding-right: 11px;
|
||||
}
|
||||
|
||||
.tags-list .tags-list-row {
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.125);
|
||||
|
||||
.dark & {
|
||||
border-bottom: 1px solid $dark-border-color;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $highlight-white;
|
||||
}
|
||||
|
||||
.dark &:hover {
|
||||
background-color: $dark-bg2;
|
||||
}
|
||||
}
|
||||
|
||||
.tags-list .tags-list-row:last-child {
|
||||
border: none;
|
||||
}
|
||||
|
||||
</style>
|
@ -6,6 +6,7 @@ const languageList = {
|
||||
"zh-HK": "繁體中文 (香港)",
|
||||
"bg-BG": "Български",
|
||||
"de-DE": "Deutsch (Deutschland)",
|
||||
"de-CH": "Deutsch (Schweiz)",
|
||||
"nl-NL": "Nederlands",
|
||||
"nb-NO": "Norsk",
|
||||
"es-ES": "Español",
|
||||
|
@ -44,6 +44,7 @@ import {
|
||||
faWrench,
|
||||
faHeartbeat,
|
||||
faFilter,
|
||||
faInfoCircle,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
library.add(
|
||||
@ -88,6 +89,7 @@ library.add(
|
||||
faWrench,
|
||||
faHeartbeat,
|
||||
faFilter,
|
||||
faInfoCircle,
|
||||
);
|
||||
|
||||
export { FontAwesomeIcon };
|
||||
|
@ -1,13 +1,14 @@
|
||||
# How to translate
|
||||
|
||||
1. Fork this repo.
|
||||
2. Run `npm run update-language-files --language=<code>` where `<code>`
|
||||
2. Run `npm install`
|
||||
3. Run `npm run update-language-files --language=<code>` where `<code>`
|
||||
is a valid ISO language code:
|
||||
http://www.lingoes.net/en/translator/langcode.htm. You can also use
|
||||
this command to check if there are new strings to
|
||||
translate for your language.
|
||||
3. Your language file should be filled in. You can translate now.
|
||||
4. Add it into `languageList` constant.
|
||||
5. Make a [pull request](https://github.com/louislam/uptime-kuma/pulls) when you have done.
|
||||
4. Your language file should be filled in. You can translate now.
|
||||
5. Add it into `languageList` constant.
|
||||
6. Make a [pull request](https://github.com/louislam/uptime-kuma/pulls) when you have done.
|
||||
|
||||
If you do not have programming skills, let me know in [the issues section](https://github.com/louislam/uptime-kuma/issues). I will assist you. 😏
|
||||
|
@ -2,7 +2,7 @@ export default {
|
||||
languageName: "Български",
|
||||
checkEverySecond: "Ще се извършва на всеки {0} секунди",
|
||||
retryCheckEverySecond: "Ще се извършва на всеки {0} секунди",
|
||||
retriesDescription: "Максимакен брой опити преди маркиране на услугата като недостъпна и изпращане на известие",
|
||||
retriesDescription: "Максимален брой опити преди маркиране на услугата като недостъпна и изпращане на известие",
|
||||
ignoreTLSError: "Игнорирай TLS/SSL грешки за HTTPS уеб сайтове",
|
||||
upsideDownModeDescription: "Обръща статуса от достъпен на недостъпен. Ако услугата е достъпна, ще се вижда като НЕДОСТЪПНА.",
|
||||
maxRedirectDescription: "Максимален брой пренасочвания, които да бъдат следвани. Въведете 0 за да изключите пренасочване.",
|
||||
@ -95,7 +95,7 @@ export default {
|
||||
"Repeat New Password": "Повторете новата парола",
|
||||
"Update Password": "Актуализирай паролата",
|
||||
"Disable Auth": "Изключи удостоверяване",
|
||||
"Enable Auth": "Включи удостоверяване",
|
||||
"Enable Auth": "Активирай удостоверяване",
|
||||
"disableauth.message1": "Сигурни ли сте, че желаете да <strong>изключите удостоверяването</strong>?",
|
||||
"disableauth.message2": "Използва се в случаите, когато <strong>има настроен алтернативен метод за удостоверяване</strong> преди Uptime Kuma, например Cloudflare Access, Authelia или друг механизъм за удостоверяване.",
|
||||
"Please use this option carefully!": "Моля, използвайте с повишено внимание.",
|
||||
@ -126,7 +126,7 @@ export default {
|
||||
Import: "Импорт",
|
||||
respTime: "Време за отговор (ms)",
|
||||
notAvailableShort: "Няма",
|
||||
"Default enabled": "Включен по подразбиране",
|
||||
"Default enabled": "Активирано по подразбиране",
|
||||
"Apply on all existing monitors": "Приложи върху всички съществуващи монитори",
|
||||
Create: "Създай",
|
||||
"Clear Data": "Изтрий данни",
|
||||
@ -145,8 +145,8 @@ export default {
|
||||
"Keep both": "Запази двете",
|
||||
"Verify Token": "Провери токен код",
|
||||
"Setup 2FA": "Настройка 2FA",
|
||||
"Enable 2FA": "Включи 2FA",
|
||||
"Disable 2FA": "Изключи 2FA",
|
||||
"Enable 2FA": "Активирай 2FA",
|
||||
"Disable 2FA": "Деактивирай 2FA",
|
||||
"2FA Settings": "Настройка за 2FA",
|
||||
"Two Factor Authentication": "Двуфакторно удостоверяване",
|
||||
Active: "Активно",
|
||||
@ -194,7 +194,7 @@ export default {
|
||||
octopush: "Octopush",
|
||||
promosms: "PromoSMS",
|
||||
lunasea: "LunaSea",
|
||||
apprise: "Apprise (Поддържа 50+ услуги за инвестяване)",
|
||||
apprise: "Apprise (Поддържа 50+ услуги за известяване)",
|
||||
pushbullet: "Pushbullet",
|
||||
line: "Line Messenger",
|
||||
mattermost: "Mattermost",
|
||||
@ -281,19 +281,19 @@ export default {
|
||||
wayToGetLineChannelToken: "Необходимо е първо да посетите {0}, за да създадете (Messaging API) за доставчик и канал, след което може да вземете токен кода за канал и потребителско ID от споменатите по-горе елементи на менюто.",
|
||||
"Icon URL": "URL адрес за иконка",
|
||||
aboutIconURL: "Може да предоставите линк към картинка в поле \"URL Адрес за иконка\" за да отмените картинката на профила по подразбиране. Няма да се използва, ако вече сте настроили емотикон.",
|
||||
aboutMattermostChannelName: "Може да замените канала по подразбиране, към който публикува уеб куката, като въведете името на канала в полето \"Канал име\". Tрябва да бъде активирано в настройките за уеб кука на Mattermost. Например: #other-channel",
|
||||
aboutMattermostChannelName: "Може да замените канала по подразбиране, към който публикува уеб куката, като въведете името на канала в полето \"Канал име\". Трябва да бъде активирано в настройките за уеб кука на Mattermost. Например: #other-channel",
|
||||
matrix: "Matrix",
|
||||
promosmsTypeEco: "SMS ECO - евтин, но бавен. Често е претоварен. Само за получатели от Полша.",
|
||||
promosmsTypeFlash: "SMS FLASH - Съобщението автоматично се показва на устройството на получателя. Само за получатели от Полша.",
|
||||
promosmsTypeFull: "SMS FULL - Високо ниво на SMS услуга. Може да използвате Вашето име като подател (Необходимо е първо да регистрирате името). Надежден метод за съобщения тип тревога.",
|
||||
promosmsTypeSpeed: "SMS SPEED - Най-висок приоритет в системата. Много бърза и надеждна, но същвременно скъпа услуга. (Около два пъти по-висока цена в сравнение с SMS FULL).",
|
||||
promosmsTypeSpeed: "SMS SPEED - Най-висок приоритет в системата. Много бърза и надеждна, но същевременно скъпа услуга. (Около два пъти по-висока цена в сравнение с SMS FULL).",
|
||||
promosmsPhoneNumber: "Телефонен номер (за получатели от Полша, може да пропуснете въвеждането на код за населено място)",
|
||||
promosmsSMSSender: "SMS Подател име: Предварително регистрирано име или някое от имената по подразбиране: InfoSMS, SMS Info, MaxSMS, INFO, SMS",
|
||||
"Feishu WebHookUrl": "Feishu URL адрес за уеб кука",
|
||||
matrixHomeserverURL: "Сървър URL адрес (започва с http(s):// и порт по желание)",
|
||||
"Internal Room Id": "ID на вътрешна стая",
|
||||
matrixDesc1: "Може да намерите \"ID на вътрешна стая\" в разширените настройки на стаята във вашия Matrix клиент. Примерен изглед: !QMdRCpUIfLwsfjxye6:home.server.",
|
||||
matrixDesc2: "Силно препоръчваме да създадете НОВ потребител и да НЕ използвате токен кодът на вашия личен Matrix потребирел, т.к. той позволява пълен достъп до вашия акаунт и всички стаи към които сте се присъединили. Вместо това създайте нов потребител и го поканете само в стаята, където желаете да получавате известията. Токен код за достъп ще получите изпълнявайки {0}",
|
||||
matrixDesc2: "Силно препоръчваме да създадете НОВ потребител и да НЕ използвате токен кодът на вашия личен Matrix потребител, т.к. той позволява пълен достъп до вашия акаунт и всички стаи към които сте се присъединили. Вместо това създайте нов потребител и го поканете само в стаята, където желаете да получавате известията. Токен код за достъп ще получите изпълнявайки {0}",
|
||||
Method: "Метод",
|
||||
Body: "Съобщение",
|
||||
Headers: "Хедъри",
|
||||
@ -316,7 +316,7 @@ export default {
|
||||
Security: "Сигурност",
|
||||
"Steam API Key": "Steam API ключ",
|
||||
"Shrink Database": "Редуцирай базата данни",
|
||||
"Pick a RR-Type...": "Изберете вида на ресурсния запис за мониторитане...",
|
||||
"Pick a RR-Type...": "Изберете вида на ресурсния запис за мониториране...",
|
||||
"Pick Accepted Status Codes...": "Изберете статус кодове, които да се считат за успешен отговор...",
|
||||
Default: "По подразбиране",
|
||||
"HTTP Options": "HTTP Опции",
|
||||
@ -375,12 +375,12 @@ export default {
|
||||
deleteStatusPageMsg: "Сигурни ли сте, че желаете да изтриете тази статус страница?",
|
||||
Proxies: "Прокси",
|
||||
default: "По подразбиране",
|
||||
enabled: "Включено",
|
||||
enabled: "Активирано",
|
||||
setAsDefault: "Зададен по подразбиране",
|
||||
deleteProxyMsg: "Сигурни ли сте, че желаете да изтриете това прокси за всички монитори?",
|
||||
proxyDescription: "За да функционират трябва да бъдат зададени към монитор.",
|
||||
enableProxyDescription: "Това прокси няма да има ефект върху заявките за мониторинг, докато не бъде активирано. Може да контролирате временното деактивиране на проксито от всички монитори чрез статуса на активиране.",
|
||||
setAsDefaultProxyDescription: "Това прокси ще бъде включено по подразбиране за новите монитори. Може да го изключите по отделно за всеки един монитор.",
|
||||
setAsDefaultProxyDescription: "Това прокси ще бъде активно по подразбиране за новите монитори. Може да го изключите по отделно за всеки един монитор.",
|
||||
"Certificate Chain": "Верига на сертификата",
|
||||
Valid: "Валиден",
|
||||
Invalid: "Невалиден",
|
||||
@ -582,4 +582,97 @@ export default {
|
||||
goAlert: "GoAlert",
|
||||
backupOutdatedWarning: "Отпаднало: Тъй като са добавени много функции, тази опция за архивиране не е достатъчно поддържана и не може да генерира или възстанови пълен архив.",
|
||||
backupRecommend: "Моля, архивирайте дяла или папката (./data/) директно вместо това.",
|
||||
Maintenance: "Поддръжка",
|
||||
statusMaintenance: "Поддръжка",
|
||||
"Schedule maintenance": "Планиране на поддръжка",
|
||||
"Affected Monitors": "Засегнати монитори",
|
||||
"Pick Affected Monitors...": "Изберете засегнати монитори...",
|
||||
"Start of maintenance": "Стартирай поддръжка",
|
||||
"All Status Pages": "Всички статус страници",
|
||||
"Select status pages...": "Изберете статус страници...",
|
||||
recurringIntervalMessage: "Изпълнявай ежедневно | Изпълнявай всеки {0} дни",
|
||||
affectedMonitorsDescription: "Изберете монитори, засегнати от текущата поддръжка",
|
||||
affectedStatusPages: "Покажи това съобщение за поддръжка на избрани статус страници",
|
||||
atLeastOneMonitor: "Изберете поне един засегнат монитор",
|
||||
deleteMaintenanceMsg: "Сигурни ли сте, че желаете да изтриете тази поддръжка?",
|
||||
Optional: "По желание",
|
||||
squadcast: "Squadcast",
|
||||
SendKey: "SendKey",
|
||||
"SMSManager API Docs": "SMSManager API Документация ",
|
||||
"Gateway Type": "Тип на шлюза",
|
||||
SMSManager: "SMSManager",
|
||||
"You can divide numbers with": "Може да разделяте числата с",
|
||||
or: "или",
|
||||
recurringInterval: "Интервал",
|
||||
Recurring: "Повтаряне",
|
||||
strategyManual: "Активен/Неактивен ръчно",
|
||||
warningTimezone: "Използва се часовата зона на сървъра",
|
||||
weekdayShortMon: "Пон",
|
||||
weekdayShortTue: "Вт",
|
||||
weekdayShortWed: "Ср",
|
||||
weekdayShortThu: "Чет",
|
||||
weekdayShortFri: "Пет",
|
||||
weekdayShortSat: "Съб",
|
||||
weekdayShortSun: "Нед",
|
||||
dayOfWeek: "Ден",
|
||||
dayOfMonth: "Дата",
|
||||
lastDay: "Последен ден",
|
||||
lastDay1: "Последен ден от месеца",
|
||||
lastDay2: "2-ри последен ден на месеца",
|
||||
lastDay3: "3-ти последен ден на месеца",
|
||||
lastDay4: "4-ти последен ден на месеца",
|
||||
"No Maintenance": "Няма поддръжка",
|
||||
pauseMaintenanceMsg: "Сигурни ли сте, че желаете да направите пауза?",
|
||||
"maintenanceStatus-under-maintenance": "В режим поддръжка",
|
||||
"maintenanceStatus-inactive": "Неактивна",
|
||||
"maintenanceStatus-scheduled": "Планирана",
|
||||
"maintenanceStatus-ended": "Приключена",
|
||||
"maintenanceStatus-unknown": "Неизвестна",
|
||||
"Display Timezone": "Покажи часова зона",
|
||||
"Server Timezone": "Часова зона на сървъра",
|
||||
statusPageMaintenanceEndDate: "Край",
|
||||
enableGRPCTls: "Разреши изпращане на gRPC заявка с TLS връзка",
|
||||
grpcMethodDescription: "Името на метода се форматира в \"cammelCase\", например sayHello, check, и т.н.",
|
||||
smseagle: "SMSEagle",
|
||||
smseagleTo: "Тел. номер(а)",
|
||||
smseagleGroup: "Име на група/и от тел. указател",
|
||||
smseagleContact: "Име(на) от тел. указател",
|
||||
smseagleRecipientType: "Получател тип",
|
||||
smseagleRecipient: "Получател(и) (при повече от един разделете със запетая)",
|
||||
smseagleToken: "API токен за достъп",
|
||||
smseagleUrl: "Вашият SMSEagle URL на устройството",
|
||||
smseagleEncoding: "Изпрати като Unicode",
|
||||
smseaglePriority: "Приоритет на съобщението (0-9, по подразбиране = 0)",
|
||||
IconUrl: "Икона URL адрес",
|
||||
webhookAdditionalHeadersTitle: "Допълнителни хедъри",
|
||||
webhookAdditionalHeadersDesc: "Задава допълнителни хедъри, изпратени с уеб куката.",
|
||||
"Enable DNS Cache": "Активирай DNS кеширане",
|
||||
Enable: "Активирай",
|
||||
Disable: "Деактивирай",
|
||||
dnsCacheDescription: "Възможно е да не работи в IPv6 среда - деактивирайте, ако срещнете проблеми.",
|
||||
"Single Maintenance Window": "Единичен времеви интервал за поддръжка",
|
||||
"Maintenance Time Window of a Day": "Времеви интервал от деня за поддръжка",
|
||||
"Effective Date Range": "Интервал от дни на влизане в сила",
|
||||
"Schedule Maintenance": "Планирай поддръжка",
|
||||
"Date and Time": "Дата и час",
|
||||
"DateTime Range": "Изтрий времеви интервал",
|
||||
Strategy: "Стратегия",
|
||||
"Free Mobile User Identifier": "Free Mobile потребителски идентификатор",
|
||||
"Free Mobile API Key": "Free Mobile API ключ",
|
||||
"Enable TLS": "Активирай TLS",
|
||||
"Proto Service Name": "Proto име на услугата",
|
||||
"Proto Method": "Proto метод",
|
||||
"Proto Content": "Proto съдържание",
|
||||
Economy: "Икономичен",
|
||||
Lowcost: "Евтин",
|
||||
high: "висок",
|
||||
"General Monitor Type": "Общ тип монитор",
|
||||
"Passive Monitor Type": "Пасивет тип монитор",
|
||||
"Specific Monitor Type": "Специфичен тип монитор",
|
||||
ZohoCliq: "ZohoCliq",
|
||||
wayToGetZohoCliqURL: "Можете да научите как се създава URL адрес за уеб кука {0}.",
|
||||
Kook: "Kook",
|
||||
wayToGetKookBotToken: "Създайте приложение и вземете вашия бот токен на {0}",
|
||||
wayToGetKookGuildID: "Превключете в 'Developer Mode' в 'Kook' настройките, след което десен клик върху 'guild' за да вземете неговото 'ID'",
|
||||
"Guild ID": "Guild ID",
|
||||
};
|
||||
|
@ -582,4 +582,45 @@ export default {
|
||||
goAlert: "GoAlert",
|
||||
backupOutdatedWarning: "Zastaralé: V poslední době byla funkčnost aplikace značně rozšířena, nicméně součást pro zálohování nepokrývá všechny možnosti. Z tohoto důvodu není možné vygenerovat úplnou zálohu a zajistit obnovení všech dat.",
|
||||
backupRecommend: "Prosím, zálohujte si ručně celý svazek nebo datovou složku (./data/).",
|
||||
"Optional": "Volitelný",
|
||||
squadcast: "Squadcast",
|
||||
SendKey: "SendKey",
|
||||
"SMSManager API Docs": "SMSManager API Docs ",
|
||||
"Gateway Type": "Gateway Typ",
|
||||
SMSManager: "SMSManager",
|
||||
"You can divide numbers with": "Čísla můžete dělit pomocí",
|
||||
"or": "nebo",
|
||||
recurringInterval: "Interval",
|
||||
"Recurring": "Opakující se",
|
||||
strategyManual: "Aktivní/Neaktivní Ručně",
|
||||
warningTimezone: "Používá se časové pásmo serveru",
|
||||
weekdayShortMon: "Po",
|
||||
weekdayShortTue: "Út",
|
||||
weekdayShortWed: "St",
|
||||
weekdayShortThu: "Čt",
|
||||
weekdayShortFri: "Pá",
|
||||
weekdayShortSat: "So",
|
||||
weekdayShortSun: "Ne",
|
||||
dayOfWeek: "Den v týdnu",
|
||||
dayOfMonth: "Den v měsíci",
|
||||
lastDay: "Poslední den",
|
||||
lastDay1: "1. 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",
|
||||
"No Maintenance": "Žádna údržba",
|
||||
pauseMaintenanceMsg: "Jsi si jistý, že chceš pozastavit údržbu?",
|
||||
"maintenanceStatus-under-maintenance": "Údržba",
|
||||
"maintenanceStatus-inactive": "Neaktivní",
|
||||
"maintenanceStatus-scheduled": "Naplánováno",
|
||||
"maintenanceStatus-ended": "Ukončeno",
|
||||
"maintenanceStatus-unknown": "Neznámý",
|
||||
"Display Timezone": "Zobrazit časové pásmo",
|
||||
"Server Timezone": "Časové pásmo serveru",
|
||||
statusPageMaintenanceEndDate: "Konec",
|
||||
IconUrl: "Adresa URL ikony",
|
||||
"Enable DNS Cache": "Povolit DNS Cache",
|
||||
"Enable": "Povolit",
|
||||
"Disable": "Zakázat",
|
||||
dnsCacheDescription: "V některých prostředích IPv6 nemusí fungovat. Pokud narazíte na nějaké problémy, vypněte jej.",
|
||||
};
|
||||
|
634
src/languages/de-CH.js
Normal file
634
src/languages/de-CH.js
Normal file
@ -0,0 +1,634 @@
|
||||
export default {
|
||||
languageName: "Deutsch (Schweiz)",
|
||||
Settings: "Einstellungen",
|
||||
Dashboard: "Dashboard",
|
||||
"New Update": "Update verfügbar",
|
||||
Language: "Sprache",
|
||||
Appearance: "Erscheinungsbild",
|
||||
Theme: "Erscheinungsbild",
|
||||
General: "Allgemein",
|
||||
Version: "Version",
|
||||
"Check Update On GitHub": "Auf GitHub nach Updates suchen",
|
||||
List: "Liste",
|
||||
Add: "Hinzufügen",
|
||||
"Add New Monitor": "Neuen Monitor hinzufügen",
|
||||
"Quick Stats": "Übersicht",
|
||||
Up: "Aktiv",
|
||||
Down: "Inaktiv",
|
||||
Pending: "Ausstehend",
|
||||
Unknown: "Unbekannt",
|
||||
Pause: "Pausieren",
|
||||
pauseDashboardHome: "Pausiert",
|
||||
Name: "Name",
|
||||
Status: "Status",
|
||||
DateTime: "Datum / Uhrzeit",
|
||||
Message: "Nachricht",
|
||||
"No important events": "Keine wichtigen Ereignisse",
|
||||
Resume: "Fortsetzen",
|
||||
Edit: "Bearbeiten",
|
||||
Delete: "Löschen",
|
||||
Current: "Aktuell",
|
||||
Uptime: "Verfügbarkeit",
|
||||
"Cert Exp.": "Zertifikatsablauf",
|
||||
day: "Tag | Tage",
|
||||
"-day": "-Tage",
|
||||
hour: "Stunde",
|
||||
"-hour": "-Stunden",
|
||||
checkEverySecond: "Überprüfe alle {0} Sekunden",
|
||||
Response: "Antwortzeit",
|
||||
Ping: "Ping",
|
||||
"Monitor Type": "Monitor-Typ",
|
||||
Keyword: "Suchwort",
|
||||
"Friendly Name": "Anzeigename",
|
||||
URL: "URL",
|
||||
Hostname: "Hostname",
|
||||
Port: "Port",
|
||||
"Heartbeat Interval": "Prüfintervall",
|
||||
Retries: "Wiederholungen",
|
||||
retriesDescription: "Maximale Anzahl von 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",
|
||||
upsideDownModeDescription: "Im umgekehrten Modus wird der Dienst als inaktiv angezeigt, wenn er erreichbar ist.",
|
||||
"Max. Redirects": "Max. Weiterleitungen",
|
||||
maxRedirectDescription: "Maximale Anzahl von Weiterleitungen, denen gefolgt werden soll. Auf 0 setzen, um Weiterleitungen zu deaktivieren.",
|
||||
"Accepted Status Codes": "Erlaubte HTTP-Statuscodes",
|
||||
acceptedStatusCodesDescription: "Statuscodes auswählen, die als erfolgreiche Verbindung gelten sollen.",
|
||||
Save: "Speichern",
|
||||
Notifications: "Benachrichtigungen",
|
||||
"Not available, please setup.": "Nicht verfügbar, bitte einrichten.",
|
||||
"Setup Notification": "Benachrichtigung einrichten",
|
||||
Light: "Hell",
|
||||
Dark: "Dunkel",
|
||||
Auto: "Auto",
|
||||
"Theme - Heartbeat Bar": "Erscheinungsbild - Zeitleiste",
|
||||
Normal: "Normal",
|
||||
Bottom: "Unten",
|
||||
None: "Keine",
|
||||
Timezone: "Zeitzone",
|
||||
"Search Engine Visibility": "Sichtbarkeit für Suchmaschinen",
|
||||
"Allow indexing": "Indizierung zulassen",
|
||||
"Discourage search engines from indexing site": "Suchmaschinen darum bitten, die Seite nicht zu indizieren",
|
||||
"Change Password": "Passwort ändern",
|
||||
"Current Password": "Aktuelles Passwort",
|
||||
"New Password": "Neues Passwort",
|
||||
"Repeat New Password": "Neues Passwort wiederholen",
|
||||
passwordNotMatchMsg: "Passwörter stimmen nicht überein.",
|
||||
"Update Password": "Passwort aktualisieren",
|
||||
"Disable Auth": "Authentifizierung deaktivieren",
|
||||
"Enable Auth": "Authentifizierung aktivieren",
|
||||
"disableauth.message1": "Bist du sicher das du die <strong>Authentifizierung deaktivieren</strong> möchtest?",
|
||||
"disableauth.message2": "Dies ist für Szenarien gedacht, <strong>in denen man eine externe Authentifizierung</strong> vor Uptime Kuma geschaltet hat, wie z.B. Cloudflare Access, Authelia oder andere Authentifizierungsmechanismen.",
|
||||
"Please use this option carefully!": "Bitte mit Vorsicht nutzen.",
|
||||
Logout: "Ausloggen",
|
||||
notificationDescription: "Benachrichtigungen müssen einem Monitor zugewiesen werden, damit diese funktionieren.",
|
||||
Leave: "Verlassen",
|
||||
"I understand, please disable": "Ich verstehe, bitte deaktivieren",
|
||||
Confirm: "Bestätigen",
|
||||
Yes: "Ja",
|
||||
No: "Nein",
|
||||
Username: "Benutzername",
|
||||
Password: "Passwort",
|
||||
"Remember me": "Angemeldet bleiben",
|
||||
Login: "Einloggen",
|
||||
"No Monitors, please": "Keine Monitore, bitte",
|
||||
"add one": "hinzufügen",
|
||||
"Notification Type": "Benachrichtigungsdienst",
|
||||
Email: "E-Mail",
|
||||
Test: "Test",
|
||||
"Certificate Info": "Zertifikatsinformation",
|
||||
keywordDescription: "Ein Suchwort in der HTML- oder JSON-Ausgabe finden. Bitte beachte: es wird zwischen Gross-/Kleinschreibung unterschieden.",
|
||||
deleteMonitorMsg: "Bist du sicher, dass du den Monitor löschen möchtest?",
|
||||
deleteNotificationMsg: "Möchtest du diese Benachrichtigung wirklich für alle Monitore löschen?",
|
||||
resolverserverDescription: "Cloudflare ist als der Standardserver festgelegt. Dieser kann jederzeit geändert werden.",
|
||||
"Resolver Server": "Auflösungsserver",
|
||||
rrtypeDescription: "Wähle den RR-Typ aus, welchen du überwachen möchtest.",
|
||||
"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?",
|
||||
"Clear Data": "Lösche Daten",
|
||||
Events: "Ereignisse",
|
||||
Heartbeats: "Statistiken",
|
||||
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",
|
||||
"Resource Record Type": "Ressourcen Record Typ",
|
||||
Export: "Export",
|
||||
Import: "Import",
|
||||
respTime: "Antw.-Zeit (ms)",
|
||||
notAvailableShort: "N/A",
|
||||
"Default enabled": "Standardmässig aktiviert",
|
||||
"Apply on all existing monitors": "Auf alle existierenden Monitore anwenden",
|
||||
enableDefaultNotificationDescription: "Für jeden neuen Monitor wird diese Benachrichtigung standardmässig aktiviert. Die Benachrichtigung kann weiterhin für jeden Monitor separat deaktiviert werden.",
|
||||
Create: "Erstellen",
|
||||
"Auto Get": "Auto Get",
|
||||
backupDescription: "Es können alle Monitore und Benachrichtigungen in einer JSON-Datei gesichert werden.",
|
||||
backupDescription2: "PS: Verlaufs- und Ereignisdaten sind nicht enthalten.",
|
||||
backupDescription3: "Sensible Daten wie Benachrichtigungs-Token sind in der Exportdatei enthalten, bitte bewahre sie sorgfältig auf.",
|
||||
alertNoFile: "Bitte wähle eine Datei zum Importieren aus.",
|
||||
alertWrongFileType: "Bitte wähle eine JSON-Datei aus.",
|
||||
"Clear all statistics": "Lösche alle Statistiken",
|
||||
importHandleDescription: "Wähle 'Vorhandene überspringen' aus, wenn jeder Monitor oder jede Benachrichtigung mit demselben Namen übersprungen werden soll. 'Überschreiben' löscht jeden vorhandenen Monitor sowie Benachrichtigungen.",
|
||||
"Skip existing": "Vorhandene überspringen",
|
||||
Overwrite: "Überschreiben",
|
||||
Options: "Optionen",
|
||||
confirmImportMsg: "Möchtest du das Backup wirklich importieren? Bitte stelle sicher, dass die richtige Import-Option ausgewählt ist.",
|
||||
"Keep both": "Beide behalten",
|
||||
twoFAVerifyLabel: "Bitte trage deinen Token ein, um zu verifizieren, dass 2FA funktioniert",
|
||||
"Verify Token": "Token verifizieren",
|
||||
"Setup 2FA": "2FA einrichten",
|
||||
"Enable 2FA": "2FA aktivieren",
|
||||
"Disable 2FA": "2FA deaktivieren",
|
||||
"2FA Settings": "2FA-Einstellungen",
|
||||
confirmEnableTwoFAMsg: "Bist du sicher, dass du 2FA aktivieren möchtest?",
|
||||
confirmDisableTwoFAMsg: "Bist du sicher, dass du 2FA deaktivieren möchtest?",
|
||||
tokenValidSettingsMsg: "Token gültig! Du kannst jetzt die 2FA-Einstellungen speichern.",
|
||||
"Two Factor Authentication": "Zwei-Faktor-Authentifizierung",
|
||||
Active: "Aktiv",
|
||||
Inactive: "Inaktiv",
|
||||
Token: "Token",
|
||||
"Show URI": "URI anzeigen",
|
||||
Tags: "Tags",
|
||||
"Add New below or Select...": "Einen bestehenden Tag auswählen oder neuen hinzufügen...",
|
||||
"Tag with this name already exist.": "Ein Tag mit diesem Namen existiert bereits.",
|
||||
"Tag with this value already exist.": "Ein Tag mit diesem Wert existiert bereits.",
|
||||
color: "Farbe",
|
||||
"value (optional)": "Wert (optional)",
|
||||
Gray: "Grau",
|
||||
Red: "Rot",
|
||||
Orange: "Orange",
|
||||
Green: "Grün",
|
||||
Blue: "Blau",
|
||||
Indigo: "Indigo",
|
||||
Purple: "Lila",
|
||||
Pink: "Pink",
|
||||
"Search...": "Suchen...",
|
||||
"Heartbeat Retry Interval": "Überprüfungsintervall",
|
||||
"Resend Notification if Down X times consequently": "Benachrichtigung erneut senden, wenn Inaktiv X mal hintereinander",
|
||||
retryCheckEverySecond: "Alle {0} Sekunden neu versuchen",
|
||||
resendEveryXTimes: "Erneut versenden alle {0} mal",
|
||||
resendDisabled: "Erneut versenden deaktiviert",
|
||||
"Import Backup": "Backup importieren",
|
||||
"Export Backup": "Backup exportieren",
|
||||
"Avg. Ping": "Ping ø",
|
||||
"Avg. Response": "Antwortzeit ø",
|
||||
"Entry Page": "Einstiegsseite",
|
||||
statusPageNothing: "Noch ist hier nichts. Bitte füge eine Gruppe oder einen Monitor hinzu.",
|
||||
"No Services": "Keine Dienste",
|
||||
"All Systems Operational": "Alle Systeme betriebsbereit",
|
||||
"Partially Degraded Service": "Teilweise beeinträchtigter Dienst",
|
||||
"Degraded Service": "Eingeschränkter Dienst",
|
||||
"Add Group": "Gruppe hinzufügen",
|
||||
"Add a monitor": "Monitor hinzufügen",
|
||||
"Edit Status Page": "Bearbeite Status-Seite",
|
||||
"Go to Dashboard": "Gehe zum Dashboard",
|
||||
"Status Page": "Status-Seite",
|
||||
"Status Pages": "Status-Seiten",
|
||||
telegram: "Telegram",
|
||||
webhook: "Webhook",
|
||||
smtp: "E-Mail (SMTP)",
|
||||
discord: "Discord",
|
||||
teams: "Microsoft Teams",
|
||||
signal: "Signal",
|
||||
gotify: "Gotify",
|
||||
slack: "Slack",
|
||||
"rocket.chat": "Rocket.chat",
|
||||
pushover: "Pushover",
|
||||
pushy: "Pushy",
|
||||
octopush: "Octopush",
|
||||
promosms: "PromoSMS",
|
||||
lunasea: "LunaSea",
|
||||
apprise: "Apprise (Unterstützung für 50+ Benachrichtigungsdienste)",
|
||||
GoogleChat: "Google Chat (nur Google Workspace)",
|
||||
pushbullet: "Pushbullet",
|
||||
line: "Line Messenger",
|
||||
mattermost: "Mattermost",
|
||||
"Primary Base URL": "Primär URL",
|
||||
"Push URL": "Push URL",
|
||||
needPushEvery: "Du solltest diese URL alle {0} Sekunden aufrufen",
|
||||
pushOptionalParams: "Optionale Parameter: {0}",
|
||||
defaultNotificationName: "Mein {notification} Alarm ({number})",
|
||||
here: "hier",
|
||||
Required: "Erforderlich",
|
||||
"Bot Token": "Bot Token",
|
||||
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.",
|
||||
"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",
|
||||
"Content Type": "Content Type",
|
||||
webhookJsonDesc: "{0} ist gut für alle modernen HTTP-Server, wie z.B. Express.js, geeignet",
|
||||
webhookFormDataDesc: "{multipart} ist gut für PHP. Das JSON muss mit {decodeFunction} verarbeitet werden",
|
||||
secureOptionNone: "Keine / STARTTLS (25, 587)",
|
||||
secureOptionTLS: "TLS (465)",
|
||||
"Ignore TLS Error": "TLS-Fehler ignorieren",
|
||||
"From Email": "Absender E-Mail",
|
||||
emailCustomSubject: "Benutzerdefinierter Betreff",
|
||||
"To Email": "Empfänger E-Mail",
|
||||
smtpCC: "CC",
|
||||
smtpBCC: "BCC",
|
||||
"Discord Webhook URL": "Discord Webhook URL",
|
||||
wayToGetDiscordURL: "Du kannst diese erhalten, indem du zu den Servereinstellungen gehst -> Integrationen -> Neuer Webhook",
|
||||
"Bot Display Name": "Bot-Anzeigename",
|
||||
"Prefix Custom Message": "Benutzerdefinierter Nachrichten Präfix",
|
||||
"Hello @everyone is...": "Hallo {'@'}everyone ist...",
|
||||
"Webhook URL": "Webhook URL",
|
||||
wayToGetTeamsURL: "Wie eine Webhook-URL erstellt werden kann, erfährst du {0}.",
|
||||
Number: "Nummer",
|
||||
Recipients: "Empfänger",
|
||||
needSignalAPI: "Es wird ein Signal Client mit REST-API benötigt.",
|
||||
wayToCheckSignalURL: "Du kannst diese URL aufrufen, um zu sehen, wie du eine einrichtest:",
|
||||
signalImportant: "WICHTIG: Gruppen und Nummern können in Empfängern nicht gemischt werden!",
|
||||
"Application Token": "Anwendungstoken",
|
||||
"Server URL": "Server URL",
|
||||
Priority: "Priorität",
|
||||
"Icon Emoji": "Icon Emoji",
|
||||
"Channel Name": "Kanalname",
|
||||
"Uptime Kuma URL": "Uptime Kuma URL",
|
||||
aboutWebhooks: "Weitere Informationen zu Webhooks auf: {0}",
|
||||
aboutChannelName: "Gebe den Kanalnamen ein in {0} Feld Kanalname, falls du den Webhook-Kanal umgehen möchtest. Ex: #other-channel",
|
||||
aboutKumaURL: "Wenn das Feld für die Uptime Kuma URL leer gelassen wird, wird standardmässig die GitHub Projekt Seite verwendet.",
|
||||
emojiCheatSheet: "Emoji Cheat Sheet: {0}",
|
||||
"User Key": "Benutzerschlüssel",
|
||||
Device: "Gerät",
|
||||
"Message Title": "Nachrichtentitel",
|
||||
"Notification Sound": "Benachrichtigungston",
|
||||
"More info on:": "Mehr Infos auf: {0}",
|
||||
pushoverDesc1: "Notfallpriorität (2) hat standardmässig 30 Sekunden Auszeit zwischen den Versuchen und läuft nach 1 Stunde ab.",
|
||||
pushoverDesc2: "Fülle das Geräte Feld aus, wenn du Benachrichtigungen an verschiedene Geräte senden möchtest.",
|
||||
"SMS Type": "SMS Typ",
|
||||
octopushTypePremium: "Premium (Schnell - zur Benachrichtigung empfohlen)",
|
||||
octopushTypeLowCost: "Kostengünstig (Langsam - manchmal vom Betreiber gesperrt)",
|
||||
checkPrice: "Prüfe {0} Preise:",
|
||||
octopushLegacyHint: "Verwendest du die Legacy-Version von Octopush (2011-2020) oder die neue Version?",
|
||||
"Check octopush prices": "Vergleiche die Oktopush Preise {0}.",
|
||||
octopushPhoneNumber: "Telefonnummer (Internationales Format, z.B : +49612345678) ",
|
||||
octopushSMSSender: "Name des SMS-Absenders : 3-11 alphanumerische Zeichen und Leerzeichen (a-zA-Z0-9)",
|
||||
"LunaSea Device ID": "LunaSea Geräte ID",
|
||||
"Apprise URL": "Apprise URL",
|
||||
"Example:": "Beispiel: {0}",
|
||||
"Read more:": "Weiterlesen: {0}",
|
||||
"Status:": "Status: {0}",
|
||||
"Read more": "Weiterlesen",
|
||||
appriseInstalled: "Apprise ist installiert.",
|
||||
appriseNotInstalled: "Apprise ist nicht installiert. {0}",
|
||||
"Access Token": "Access Token",
|
||||
"Channel access token": "Channel access token",
|
||||
"Line Developers Console": "Line Developers Console",
|
||||
lineDevConsoleTo: "Line Developers Console - {0}",
|
||||
"Basic Settings": "Basic Settings",
|
||||
"User ID": "User ID",
|
||||
"Messaging API": "Messaging API",
|
||||
wayToGetLineChannelToken: "Rufe zuerst {0} auf, erstelle dann einen Provider und Channel (Messaging API). Als nächstes kannst du den Channel access token und die User ID aus den oben genannten Menüpunkten abrufen.",
|
||||
"Icon URL": "Icon URL",
|
||||
aboutIconURL: "Du kannst einen Link zu einem Bild in 'Icon URL' übergeben um das Standardprofilbild zu überschreiben. Wird nicht verwendet, wenn ein Icon Emoji gesetzt ist.",
|
||||
aboutMattermostChannelName: "Du kannst den Standardkanal, auf dem der Webhook gesendet wird überschreiben, indem der Kanalnamen in das Feld 'Channel Name' eingeben wird. Dies muss in den Mattermost Webhook-Einstellungen aktiviert werden. Ex: #other-channel",
|
||||
matrix: "Matrix",
|
||||
promosmsTypeEco: "SMS ECO - billig, aber langsam und oft überladen. Auf polnische Empfänger beschränkt.",
|
||||
promosmsTypeFlash: "SMS FLASH - Die Nachricht wird automatisch auf dem Empfängergerät angezeigt. Auf polnische Empfänger beschränkt.",
|
||||
promosmsTypeFull: "SMS FULL - Premium Stufe von SMS, es kann der Absendernamen verwendet werden (Der Name musst zuerst registriert werden). Zuverlässig für Warnungen.",
|
||||
promosmsTypeSpeed: "SMS SPEED - Höchste Priorität im System. Sehr schnell und zuverlässig, aber teuer (Ungefähr das doppelte von SMS FULL).",
|
||||
promosmsPhoneNumber: "Telefonnummer (für polnische Empfänger können die Vorwahlen übersprungen werden)",
|
||||
promosmsSMSSender: "Name des SMS-Absenders : vorregistrierter Name oder einer der Standardwerte: InfoSMS, SMS Info, MaxSMS, INFO, SMS",
|
||||
"Feishu WebHookUrl": "Feishu Webhook URL",
|
||||
matrixHomeserverURL: "Heimserver URL (mit http(s):// und optionalen Ports)",
|
||||
"Internal Room Id": "Interne Raum-ID",
|
||||
matrixDesc1: "Die interne Raum-ID findest du im erweiterten Bereich der Raumeinstellungen im Matrix-Client. Es sollte aussehen wie z.B. !QMdRCpUIfLwsfjxye6:home.server.",
|
||||
matrixDesc2: "Es wird dringend empfohlen einen neuen Benutzer anzulegen und nicht den Zugriffstoken deines eigenen Matrix-Benutzers zu verwenden. Anderenfalls ermöglicht es vollen Zugriff auf dein Konto und alle Räume, denen du beigetreten bist. Erstelle stattdessen einen neuen Benutzer und lade ihn nur in den Raum ein, in dem du die Benachrichtigung erhalten möchtest. Du kannst den Zugriffstoken erhalten, indem du Folgendes ausführst {0}",
|
||||
Method: "Method",
|
||||
Body: "Body",
|
||||
Headers: "Headers",
|
||||
PushUrl: "Push URL",
|
||||
HeadersInvalidFormat: "Der Header ist kein gültiges JSON: ",
|
||||
BodyInvalidFormat: "Der Body ist kein gültiges JSON: ",
|
||||
"Monitor History": "Monitor Verlauf",
|
||||
clearDataOlderThan: "Bewahre die Aufzeichnungsdaten für {0} Tage auf.",
|
||||
PasswordsDoNotMatch: "Passwörter stimmen nicht überein.",
|
||||
records: "Einträge",
|
||||
"One record": "Ein Eintrag",
|
||||
steamApiKeyDescription: "Um einen Steam Game Server zu überwachen, wird ein Steam Web-API-Schlüssel benötigt. Dieser kann hier registriert werden: ",
|
||||
"Current User": "Aktueller Benutzer",
|
||||
recent: "Letzte",
|
||||
Done: "Fertig",
|
||||
Info: "Info",
|
||||
Security: "Sicherheit",
|
||||
"Steam API Key": "Steam API Key",
|
||||
"Shrink Database": "Datenbank verkleinern",
|
||||
"Pick a RR-Type...": "Wähle ein RR-Typ aus...",
|
||||
"Pick Accepted Status Codes...": "Wähle akzeptierte Statuscodes aus...",
|
||||
Default: "Standard",
|
||||
"HTTP Options": "HTTP Optionen",
|
||||
"Create Incident": "Vorfall erstellen",
|
||||
Title: "Titel",
|
||||
Content: "Inhalt",
|
||||
Style: "Stil",
|
||||
info: "info",
|
||||
warning: "warnung",
|
||||
danger: "gefahr",
|
||||
primary: "primär",
|
||||
light: "hell",
|
||||
dark: "dunkel",
|
||||
Post: "Eintrag",
|
||||
"Please input title and content": "Bitte Titel und Inhalt eingeben",
|
||||
Created: "Erstellt",
|
||||
"Last Updated": "Zuletzt aktualisiert",
|
||||
Unpin: "Loslösen",
|
||||
"Switch to Light Theme": "Zu hellem Thema wechseln",
|
||||
"Switch to Dark Theme": "Zum dunklen Thema wechseln",
|
||||
"Show Tags": "Tags anzeigen",
|
||||
"Hide Tags": "Tags ausblenden",
|
||||
Description: "Beschreibung",
|
||||
"No monitors available.": "Keine Monitore verfügbar.",
|
||||
"Add one": "Hinzufügen",
|
||||
"No Monitors": "Keine Monitore",
|
||||
"Untitled Group": "Gruppe ohne Titel",
|
||||
Services: "Dienste",
|
||||
Discard: "Verwerfen",
|
||||
Cancel: "Abbrechen",
|
||||
"Powered by": "Powered by",
|
||||
shrinkDatabaseDescription: "Löse VACUUM für die SQLite Datenbank aus. Wenn die Datenbank nach 1.10.0 erstellt wurde, ist AUTO_VACUUM bereits aktiviert und diese Aktion ist nicht erforderlich.",
|
||||
serwersms: "SerwerSMS.pl",
|
||||
serwersmsAPIUser: "API Benutzername (inkl. webapi_ prefix)",
|
||||
serwersmsAPIPassword: "API Passwort",
|
||||
serwersmsPhoneNumber: "Telefonnummer",
|
||||
serwersmsSenderName: "Name des SMS-Absenders (über Kundenportal registriert)",
|
||||
stackfield: "Stackfield",
|
||||
clicksendsms: "ClickSend SMS",
|
||||
apiCredentials: "API Zugangsdaten",
|
||||
smtpDkimSettings: "DKIM Einstellungen",
|
||||
smtpDkimDesc: "Details zur Konfiguration sind in der Nodemailer DKIM {0} zu finden.",
|
||||
documentation: "Dokumentation",
|
||||
smtpDkimDomain: "Domain Name",
|
||||
smtpDkimKeySelector: "Schlüssel Auswahl",
|
||||
smtpDkimPrivateKey: "Privater Schlüssel",
|
||||
smtpDkimHashAlgo: "Hash-Algorithmus (Optional)",
|
||||
smtpDkimheaderFieldNames: "Zu validierende Header-Schlüssel (optional)",
|
||||
smtpDkimskipFields: "Zu ignorierende Header Schlüssel (optional)",
|
||||
PushByTechulus: "Push by Techulus",
|
||||
gorush: "Gorush",
|
||||
alerta: "Alerta",
|
||||
alertaApiEndpoint: "API Endpunkt",
|
||||
alertaEnvironment: "Umgebung",
|
||||
alertaApiKey: "API Schlüssel",
|
||||
alertaAlertState: "Alarmstatus",
|
||||
alertaRecoverState: "Wiederherstellungsstatus",
|
||||
deleteStatusPageMsg: "Bist du sicher, dass du diese Status-Seite löschen willst?",
|
||||
Proxies: "Proxies",
|
||||
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.",
|
||||
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",
|
||||
Valid: "Gültig",
|
||||
Invalid: "Ungültig",
|
||||
AccessKeyId: "AccessKey ID",
|
||||
SecretAccessKey: "AccessKey Secret",
|
||||
PhoneNumbers: "Telefonnummern",
|
||||
TemplateCode: "Vorlagencode",
|
||||
SignName: "Signaturname",
|
||||
"Sms template must contain parameters: ": "SMS Vorlage muss folgende Parameter enthalten: ",
|
||||
"Bark Endpoint": "Bark Endpunkt",
|
||||
WebHookUrl: "Webhook URL",
|
||||
SecretKey: "Geheimer Schlüssel",
|
||||
"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",
|
||||
Topic: "Thema",
|
||||
"WeCom Bot Key": "WeCom Bot Schlüssel",
|
||||
"Setup Proxy": "Proxy einrichten",
|
||||
"Proxy Protocol": "Proxy Protokoll",
|
||||
"Proxy Server": "Proxy-Server",
|
||||
"Proxy server has authentication": "Proxy-Server hat Authentifizierung",
|
||||
User: "Benutzer",
|
||||
Installed: "Installiert",
|
||||
"Not installed": "Nicht installiert",
|
||||
Running: "Läuft",
|
||||
"Not running": "Gestoppt",
|
||||
"Remove Token": "Token entfernen",
|
||||
Start: "Start",
|
||||
Stop: "Stop",
|
||||
"Uptime Kuma": "Uptime Kuma",
|
||||
"Add New Status Page": "Neue Status-Seite hinzufügen",
|
||||
Slug: "Slug",
|
||||
"Accept characters:": "Akzeptierte Zeichen:",
|
||||
startOrEndWithOnly: "Nur mit {0} anfangen und enden",
|
||||
"No consecutive dashes": "Keine aufeinanderfolgenden Bindestriche",
|
||||
Next: "Weiter",
|
||||
"The slug is already taken. Please choose another slug.": "Der Slug ist bereits in Verwendung. Bitte wähle einen anderen.",
|
||||
"No Proxy": "Kein Proxy",
|
||||
Authentication: "Authentifizierung",
|
||||
"HTTP Basic Auth": "HTTP Basisauthentifizierung",
|
||||
"New Status Page": "Neue Status-Seite",
|
||||
"Page Not Found": "Seite nicht gefunden",
|
||||
"Reverse Proxy": "Reverse Proxy",
|
||||
Backup: "Sicherung",
|
||||
About: "Über",
|
||||
wayToGetCloudflaredURL: "(Lade Cloudflare von {0} herunter)",
|
||||
cloudflareWebsite: "Cloudflare Website",
|
||||
"Message:": "Nachricht:",
|
||||
"Don't know how to get the token? Please read the guide:": "Du weisst nicht, wie man den Token bekommt? Lies die Anleitung dazu:",
|
||||
"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.": "Die aktuelle Verbindung kann unterbrochen werden, wenn du aktuell über Cloudflare Tunnel verbunden bist. Bist du sicher, dass du es stoppen willst? Gib zur Bestätigung dein aktuelles Passwort ein.",
|
||||
"Other Software": "Andere Software",
|
||||
"For example: nginx, Apache and Traefik.": "Zum Beispiel: nginx, Apache und Traefik.",
|
||||
"Please read": "Bitte lesen",
|
||||
"Subject:": "Betreff:",
|
||||
"Valid To:": "Gültig bis:",
|
||||
"Days Remaining:": "Tage verbleibend:",
|
||||
"Issuer:": "Aussteller:",
|
||||
"Fingerprint:": "Fingerabdruck:",
|
||||
"No status pages": "Keine Status-Seiten",
|
||||
"Domain Name Expiry Notification": "Benachrichtigung bei Ablauf des Domainnamens",
|
||||
Customize: "Anpassen",
|
||||
"Custom Footer": "Eigener Footer",
|
||||
"Custom CSS": "Eigenes CSS",
|
||||
"Footer Text": "Fusszeile",
|
||||
"Show Powered By": "Zeige 'Powered By'",
|
||||
"Date Created": "Erstellt am",
|
||||
"Domain Names": "Domainnamen",
|
||||
signedInDisp: "Angemeldet als {0}",
|
||||
signedInDispDisabled: "Authentifizierung deaktiviert.",
|
||||
dnsPortDescription: "DNS server port. Standard ist 53. Der Port kann jederzeit geändert werden.",
|
||||
topic: "Thema",
|
||||
topicExplanation: "MQTT Thema für den monitor",
|
||||
successMessage: "Erfolgsnachricht",
|
||||
successMessageExplanation: "MQTT Nachricht, die als Erfolg angesehen wird",
|
||||
error: "Fehler",
|
||||
critical: "kritisch",
|
||||
wayToGetPagerDutyKey: "Dieser kann unter Service -> Service Directory -> (Select a service) -> Integrations -> Add integration gefunden werden. Hier muss nach \"Events API V2\" gesucht werden. Mehr informationen {0}",
|
||||
"Integration Key": "Schlüssel der Integration",
|
||||
"Integration URL": "URL der Integration",
|
||||
"Auto resolve or acknowledged": "Automatisch lösen oder bestätigen",
|
||||
"do nothing": "nichts tun",
|
||||
"auto acknowledged": "automatisch bestätigen",
|
||||
"auto resolve": "automatisch lösen",
|
||||
"Bark Group": "Bark Gruppe",
|
||||
"Bark Sound": "Bark Klang",
|
||||
"HTTP Headers": "HTTP Kopfzeilen",
|
||||
"Trust Proxy": "Vertrauenswürdiger Proxy",
|
||||
Proxy: "Proxy",
|
||||
HomeAssistant: "Home Assistant",
|
||||
onebotHttpAddress: "OneBot HTTP Adresse",
|
||||
onebotMessageType: "OneBot Nachrichtentyp",
|
||||
onebotGroupMessage: "Gruppe",
|
||||
onebotPrivateMessage: "Privat",
|
||||
onebotUserOrGroupId: "Gruppe/Nutzer ID",
|
||||
onebotSafetyTips: "Zur Sicherheit ein access token setzen",
|
||||
"PushDeer Key": "PushDeer Schlüssel",
|
||||
RadiusSecret: "Radius Geheimnis",
|
||||
RadiusSecretDescription: "Geteiltes Geheimnis zwischen Client und Server",
|
||||
RadiusCalledStationId: "ID der angesprochenen Station",
|
||||
RadiusCalledStationIdDescription: "Identifikation des angesprochenen Geräts",
|
||||
RadiusCallingStationId: "ID der ansprechenden Station",
|
||||
RadiusCallingStationIdDescription: "Identifikation des ansprechenden Geräts",
|
||||
"Certificate Expiry Notification": "Benachrichtigung ablaufendes Zertifikat",
|
||||
"API Username": "API Nutzername",
|
||||
"API Key": "API Schlüssel",
|
||||
"Recipient Number": "Empfängernummer",
|
||||
"From Name/Number": "Von Name/Nummer",
|
||||
"Leave blank to use a shared sender number.": "Leer lassen um eine geteilte Absendernummer zu nutzen.",
|
||||
"Octopush API Version": "Octopush API Version",
|
||||
"Legacy Octopush-DM": "Legacy Octopush-DM",
|
||||
endpoint: "Endpunkt",
|
||||
octopushAPIKey: "\"API Schlüssel\" der HTTP API Zugangsdaten im control panel",
|
||||
octopushLogin: "\"Login\" der HTTP API Zugangsdaten im control panel",
|
||||
promosmsLogin: "API Login Name",
|
||||
promosmsPassword: "API Password",
|
||||
"pushoversounds pushover": "Pushover (Standard)",
|
||||
"pushoversounds bike": "Fahrrad",
|
||||
"pushoversounds bugle": "Signalhorn",
|
||||
"pushoversounds cashregister": "Kasse",
|
||||
"pushoversounds classical": "Klassisch",
|
||||
"pushoversounds cosmic": "Kosmisch",
|
||||
"pushoversounds falling": "Abfallend",
|
||||
"pushoversounds gamelan": "Gamelan",
|
||||
"pushoversounds incoming": "Eingang",
|
||||
"pushoversounds intermission": "Pause",
|
||||
"pushoversounds magic": "Magisch",
|
||||
"pushoversounds mechanical": "Mechanisch",
|
||||
"pushoversounds pianobar": "Piano Bar",
|
||||
"pushoversounds siren": "Sirene",
|
||||
"pushoversounds spacealarm": "Space Alarm",
|
||||
"pushoversounds tugboat": "Schlepper Horn",
|
||||
"pushoversounds alien": "Ausserirdisch (lang)",
|
||||
"pushoversounds climb": "Ansteigende (lang)",
|
||||
"pushoversounds persistent": "Hartnäckig (lang)",
|
||||
"pushoversounds echo": "Pushover Echo (lang)",
|
||||
"pushoversounds updown": "Auf und Ab (lang)",
|
||||
"pushoversounds vibrate": "Nur vibrieren",
|
||||
"pushoversounds none": "Nichts (Stille)",
|
||||
pushyAPIKey: "Geheimer API Schlüssel",
|
||||
pushyToken: "Gerätetoken",
|
||||
"Show update if available": "Verfügbare Updates anzeigen",
|
||||
"Also check beta release": "Auch nach beta Versionen schauen",
|
||||
"Using a Reverse Proxy?": "Wird ein Reverse Proxy genutzt?",
|
||||
"Check how to config it for WebSocket": "Prüfen, wie er für die Nutzung mit WebSocket konfiguriert wird",
|
||||
"Steam Game Server": "Steam Game Server",
|
||||
"Most likely causes:": "Wahrscheinliche Ursachen:",
|
||||
"The resource is no longer available.": "Die Quelle ist nicht mehr verfügbar.",
|
||||
"There might be a typing error in the address.": "Es gibt einen Tippfehler in der Adresse.",
|
||||
"What you can try:": "Was du versuchen kannst:",
|
||||
"Retype the address.": "Schreibe die Adresse erneut.",
|
||||
"Go back to the previous page.": "Gehe zur vorigen Seite.",
|
||||
"Coming Soon": "Kommt bald",
|
||||
wayToGetClickSendSMSToken: "Du kannst einen API Nutzernamen und Schlüssel unter {0} erhalten.",
|
||||
"Connection String": "Verbindungstext",
|
||||
Query: "Abfrage",
|
||||
settingsCertificateExpiry: "TLS Zertifikatsablauf",
|
||||
certificationExpiryDescription: "HTTPS Monitore senden eine Benachrichtigung, wenn das Zertifikat abläuft in:",
|
||||
"Setup Docker Host": "Docker Host einrichten",
|
||||
"Connection Type": "Verbindungstyp",
|
||||
"Docker Daemon": "Docker Daemon",
|
||||
deleteDockerHostMsg: "Bist du sicher diesen docker host für alle Monitore zu löschen?",
|
||||
socket: "Socket",
|
||||
tcp: "TCP / HTTP",
|
||||
"Docker Container": "Docker Container",
|
||||
"Container Name / ID": "Container Name / ID",
|
||||
"Docker Host": "Docker Host",
|
||||
"Docker Hosts": "Docker Hosts",
|
||||
"ntfy Topic": "ntfy Thema",
|
||||
Domain: "Domain",
|
||||
Workstation: "Workstation",
|
||||
disableCloudflaredNoAuthMsg: "Du bist im nicht-authentifizieren Modus, ein Passwort wird nicht benötigt.",
|
||||
trustProxyDescription: "Vertraue 'X-Forwarded-*' headern. Wenn man die richtige client IP haben möchte und Uptime Kuma hinter einem Proxy wie Nginx or Apache läuft, wollte dies aktiviert werden.",
|
||||
wayToGetLineNotifyToken: "Du kannst hier ein Token erhalten: {0}",
|
||||
Examples: "Beispiele",
|
||||
"Home Assistant URL": "Home Assistant URL",
|
||||
"Long-Lived Access Token": "Lange gültiges Access Token",
|
||||
"Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "Lange gültige Access Token können durch klicken auf den Profilnamen (unten links) und dann einen Klick auf Create Token am Ende erstellt werden. ",
|
||||
"Notification Service": "Benachrichtigungsdienst",
|
||||
"default: notify all devices": "standard: Alle Geräte benachrichtigen",
|
||||
"A list of Notification Services can be found in Home Assistant under \"Developer Tools > Services\" search for \"notification\" to find your device/phone name.": "Eine Liste der Benachrichtigungsdienste kann im Home Assistant unter \"Developer Tools > Services\" gefunden werden, wnen man nach \"notification\" sucht um den Geräte-/Telefonnamen zu finden.",
|
||||
"Automations can optionally be triggered in Home Assistant:": "Automatisierungen können optional im Home Assistant ausgelöst werden:",
|
||||
"Trigger type:": "Auslöser:",
|
||||
"Event type:": "Ereignistyp:",
|
||||
"Event data:": "Ereignis daten:",
|
||||
"Then choose an action, for example switch the scene to where an RGB light is red.": "Dann eine Aktion wählen, zum Beispiel eine Scene wählen in der ein RGB Licht rot ist.",
|
||||
"Frontend Version": "Frontend Version",
|
||||
"Frontend Version do not match backend version!": "Die Frontend Version stimmt nicht mit der backend version überein!",
|
||||
Maintenance: "Wartung",
|
||||
statusMaintenance: "Wartung",
|
||||
"Schedule maintenance": "Geplante Wartung",
|
||||
"Affected Monitors": "Betroffene Monitore",
|
||||
"Pick Affected Monitors...": "Wähle betroffene Monitore...",
|
||||
"Start of maintenance": "Beginn der Wartung",
|
||||
"All Status Pages": "Alle Status Seiten",
|
||||
"Select status pages...": "Wähle Status Seiten...",
|
||||
recurringIntervalMessage: "einmal pro Tag ausgeführt | Wird alle {0} Tage ausgführt",
|
||||
affectedMonitorsDescription: "Wähle alle Monitore die von der Wartung betroffen sind",
|
||||
affectedStatusPages: "Zeige diese Nachricht auf ausgewählten Status Seiten",
|
||||
atLeastOneMonitor: "Wähle mindestens einen Monitor",
|
||||
deleteMaintenanceMsg: "Möchtest du diese Wartung löschen?",
|
||||
"Base URL": "Basis URL",
|
||||
goAlertInfo: "GoAlert ist eine Open-Source Applikation für Rufbereitschaftsplanung, automatische Eskalation und Benachrichtigung (z.B. SMS oder Telefonanrufe). Beauftragen Sie automatisch die richtige Person, auf die richtige Art und Weise und zum richtigen Zeitpunkt. {0}",
|
||||
goAlertIntegrationKeyInfo: "Bekommt einen generischen API Schlüssel in folgenden Format \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\". Normalerweise entspricht dies dem Wert des Token aus der URL.",
|
||||
goAlert: "GoAlert",
|
||||
backupOutdatedWarning: "Veraltet: Eine menge Neuerungen sind eingeflossen und diese Funktion wurde etwas vernachlässigt worden. Es kann kein vollständiges Backup erstellt oder eingespielt werden.",
|
||||
backupRecommend: "Bitte Backup das Volume oder den Ordner (./ data /) selbst.",
|
||||
Optional: "Optional",
|
||||
squadcast: "Squadcast",
|
||||
SendKey: "SendKey",
|
||||
"SMSManager API Docs": "SMSManager API Dokumente",
|
||||
"Gateway Type": "Gateway Type",
|
||||
SMSManager: "SMSManager",
|
||||
"You can divide numbers with": "Du kannst Zahlen teilen mit",
|
||||
or: "oder",
|
||||
recurringInterval: "Intervall",
|
||||
Recurring: "Wiederkehrend",
|
||||
strategyManual: "Active/Inactive Manually",
|
||||
warningTimezone: "Es wird die Zeitzone des Servers genutzt",
|
||||
weekdayShortMon: "Mo",
|
||||
weekdayShortTue: "Di",
|
||||
weekdayShortWed: "Mi",
|
||||
weekdayShortThu: "Do",
|
||||
weekdayShortFri: "Fr",
|
||||
weekdayShortSat: "Sa",
|
||||
weekdayShortSun: "So",
|
||||
dayOfWeek: "Tag der Woche",
|
||||
dayOfMonth: "Tag im Monat",
|
||||
lastDay: "Letzter Tag",
|
||||
lastDay1: "Letzter Tag im Monat",
|
||||
lastDay2: "Vorletzer Tag im Monat",
|
||||
lastDay3: "3. letzter Tag im Monat",
|
||||
lastDay4: "4. letzter Tag im Monat",
|
||||
"No Maintenance": "Keine Wartung",
|
||||
pauseMaintenanceMsg: "Möchtest du wirklich pausieren?",
|
||||
"maintenanceStatus-under-maintenance": "Unter Wartung",
|
||||
"maintenanceStatus-inactive": "Inaktiv",
|
||||
"maintenanceStatus-scheduled": "Geplant",
|
||||
"maintenanceStatus-ended": "Ende",
|
||||
"maintenanceStatus-unknown": "Unbekannt",
|
||||
"Display Timezone": "Zeitzone anzeigen",
|
||||
"Server Timezone": "Server Zeitzone",
|
||||
statusPageMaintenanceEndDate: "Ende",
|
||||
};
|
@ -96,7 +96,7 @@ export default {
|
||||
"Notification Type": "Benachrichtigungsdienst",
|
||||
Email: "E-Mail",
|
||||
Test: "Test",
|
||||
"Certificate Info": "Zertifikatsinfo",
|
||||
"Certificate Info": "Zertifikatsinformation",
|
||||
keywordDescription: "Ein Suchwort in der HTML- oder JSON-Ausgabe finden. Bitte beachte: es wird zwischen Groß-/Kleinschreibung unterschieden.",
|
||||
deleteMonitorMsg: "Bist du sicher, dass du den Monitor löschen möchtest?",
|
||||
deleteNotificationMsg: "Möchtest du diese Benachrichtigung wirklich für alle Monitore löschen?",
|
||||
@ -181,7 +181,7 @@ export default {
|
||||
"Degraded Service": "Eingeschränkter Dienst",
|
||||
"Add Group": "Gruppe hinzufügen",
|
||||
"Add a monitor": "Monitor hinzufügen",
|
||||
"Edit Status Page": "Bearbeite Status-Seite",
|
||||
"Edit Status Page": "Statusseite bearbeiten",
|
||||
"Go to Dashboard": "Gehe zum Dashboard",
|
||||
"Status Page": "Status-Seite",
|
||||
"Status Pages": "Status-Seiten",
|
||||
@ -204,7 +204,7 @@ export default {
|
||||
pushbullet: "Pushbullet",
|
||||
line: "Line Messenger",
|
||||
mattermost: "Mattermost",
|
||||
"Primary Base URL": "Primär URL",
|
||||
"Primary Base URL": "Primäre Basis-URL",
|
||||
"Push URL": "Push URL",
|
||||
needPushEvery: "Du solltest diese URL alle {0} Sekunden aufrufen",
|
||||
pushOptionalParams: "Optionale Parameter: {0}",
|
||||
@ -496,7 +496,7 @@ export default {
|
||||
"API Key": "API Schlüssel",
|
||||
"Recipient Number": "Empfängernummer",
|
||||
"From Name/Number": "Von Name/Nummer",
|
||||
"Leave blank to use a shared sender number.": "Leer lassen um eine geteilte Sendernummer zu nutzen.",
|
||||
"Leave blank to use a shared sender number.": "Leer lassen um eine geteilte Absendernummer zu nutzen.",
|
||||
"Octopush API Version": "Octopush API Version",
|
||||
"Legacy Octopush-DM": "Legacy Octopush-DM",
|
||||
endpoint: "Endpunkt",
|
||||
@ -530,7 +530,7 @@ export default {
|
||||
pushyAPIKey: "Geheimer API Schlüssel",
|
||||
pushyToken: "Gerätetoken",
|
||||
"Show update if available": "Verfügbare Updates anzeigen",
|
||||
"Also check beta release": "Auch nach beta Versionen schauen",
|
||||
"Also check beta release": "Auch nach Beta Versionen schauen",
|
||||
"Using a Reverse Proxy?": "Wird ein Reverse Proxy genutzt?",
|
||||
"Check how to config it for WebSocket": "Prüfen, wie er für die Nutzung mit WebSocket konfiguriert wird",
|
||||
"Steam Game Server": "Steam Game Server",
|
||||
@ -568,12 +568,74 @@ export default {
|
||||
"Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "Lange gültige Access Token können durch klicken auf den Profilnamen (unten links) und dann einen Klick auf Create Token am Ende erstellt werden. ",
|
||||
"Notification Service": "Benachrichtigungsdienst",
|
||||
"default: notify all devices": "standard: Alle Geräte benachrichtigen",
|
||||
"A list of Notification Services can be found in Home Assistant under \"Developer Tools > Services\" search for \"notification\" to find your device/phone name.": "Eine Liste der Benachrichtigungsdiesnte kann im Home Assistant unter \"Developer Tools > Services\" gefunden werden, wnen man nach \"notification\" sucht um den Geräte-/Telefonnamen zu finden.",
|
||||
"A list of Notification Services can be found in Home Assistant under \"Developer Tools > Services\" search for \"notification\" to find your device/phone name.": "Eine Liste der Benachrichtigungsdienste kann im Home Assistant unter \"Developer Tools > Services\" gefunden werden, wnen man nach \"notification\" sucht um den Geräte-/Telefonnamen zu finden.",
|
||||
"Automations can optionally be triggered in Home Assistant:": "Automatisierungen können optional im Home Assistant ausgelöst werden:",
|
||||
"Trigger type:": "Auslösertyp:",
|
||||
"Trigger type:": "Auslöser:",
|
||||
"Event type:": "Ereignistyp:",
|
||||
"Event data:": "Ereignis daten:",
|
||||
"Then choose an action, for example switch the scene to where an RGB light is red.": "Dann eine Aktion wählen, zum Beispiel eine Scene wählen in der ein RGB Licht rot ist.",
|
||||
"Frontend Version": "Frontend Version",
|
||||
"Frontend Version do not match backend version!": "Die Frontend Version stimmt nicht mit der backend version überein!",
|
||||
Maintenance: "Wartung",
|
||||
statusMaintenance: "Wartung",
|
||||
"Schedule maintenance": "Geplante Wartung",
|
||||
"Affected Monitors": "Betroffene Monitore",
|
||||
"Pick Affected Monitors...": "Wähle betroffene Monitore...",
|
||||
"Start of maintenance": "Beginn der Wartung",
|
||||
"All Status Pages": "Alle Status Seiten",
|
||||
"Select status pages...": "Statusseiten auswählen...",
|
||||
recurringIntervalMessage: "Einmal pro Tag ausgeführt | Wird alle {0} Tage ausgführt",
|
||||
affectedMonitorsDescription: "Wähle Monitore aus, die von der aktuellen Wartung betroffen sind",
|
||||
affectedStatusPages: "Diese Wartungsmeldung auf ausgewählten Statusseiten anzeigen",
|
||||
atLeastOneMonitor: "Wähle mindestens einen Monitor",
|
||||
deleteMaintenanceMsg: "Möchtest du diese Wartung löschen?",
|
||||
"Base URL": "Basis URL",
|
||||
goAlertInfo: "GoAlert ist eine Open-Source Applikation für Rufbereitschaftsplanung, automatische Eskalation und Benachrichtigung (z.B. SMS oder Telefonanrufe). Beauftragen Sie automatisch die richtige Person, auf die richtige Art und Weise und zum richtigen Zeitpunkt. {0}",
|
||||
goAlertIntegrationKeyInfo: "Bekommt einen generischen API Schlüssel in folgenden Format \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\". Normalerweise entspricht dies dem Wert des Token aus der URL.",
|
||||
goAlert: "GoAlert",
|
||||
backupOutdatedWarning: "Veraltet: Da viele Funktionen hinzugefügt wurden und diese Sicherungsfunktion nicht mehr gepflegt wird, kann sie keine vollständige Sicherung erstellen oder wiederherstellen.",
|
||||
backupRecommend: "Bitte sichere stattdessen das Volume oder den Datenordner (./data/) direkt.",
|
||||
Optional: "Optional",
|
||||
squadcast: "Squadcast",
|
||||
SendKey: "SendKey",
|
||||
"SMSManager API Docs": "SMSManager API Dokumente",
|
||||
"Gateway Type": "Gateway Type",
|
||||
SMSManager: "SMSManager",
|
||||
"You can divide numbers with": "Du kannst Zahlen teilen mit",
|
||||
or: "oder",
|
||||
recurringInterval: "Intervall",
|
||||
Recurring: "Wiederkehrend",
|
||||
"Single Maintenance Window": "Einzigartiges Wartungsfenster",
|
||||
"Maintenance Time Window of a Day": "Zeitfenster für die Wartung",
|
||||
"Effective Date Range": "Bereich der Wirksamkeitsdaten",
|
||||
strategyManual: "Aktiv/Inaktiv Manuell",
|
||||
warningTimezone: "Es wird die Zeitzone des Servers verwendet",
|
||||
weekdayShortMon: "Mo",
|
||||
weekdayShortTue: "Di",
|
||||
weekdayShortWed: "Mi",
|
||||
weekdayShortThu: "Do",
|
||||
weekdayShortFri: "Fr",
|
||||
weekdayShortSat: "Sa",
|
||||
weekdayShortSun: "So",
|
||||
dayOfWeek: "Tag der Woche",
|
||||
dayOfMonth: "Tag im Monat",
|
||||
lastDay: "Letzter Tag",
|
||||
lastDay1: "Letzter Tag im Monat",
|
||||
lastDay2: "Vorletzer Tag im Monat",
|
||||
lastDay3: "3. letzter Tag im Monat",
|
||||
lastDay4: "4. letzter Tag im Monat",
|
||||
"No Maintenance": "Keine Wartung",
|
||||
"Schedule Maintenance": "Wartung planen",
|
||||
pauseMaintenanceMsg: "Möchtest du wirklich pausieren?",
|
||||
"maintenanceStatus-under-maintenance": "Unter Wartung",
|
||||
"maintenanceStatus-inactive": "Inaktiv",
|
||||
"maintenanceStatus-scheduled": "Geplant",
|
||||
"maintenanceStatus-ended": "Ende",
|
||||
"maintenanceStatus-unknown": "Unbekannt",
|
||||
"Display Timezone": "Zeitzone anzeigen",
|
||||
"Server Timezone": "Server Zeitzone",
|
||||
"Date and Time": "Datum und Zeit",
|
||||
"DateTime Range": "Datums- und Zeitbereich",
|
||||
Strategy: "Strategie",
|
||||
statusPageMaintenanceEndDate: "Ende",
|
||||
};
|
||||
|
@ -194,6 +194,7 @@ export default {
|
||||
here: "εδώ",
|
||||
Required: "Απαιτείται",
|
||||
telegram: "Telegram",
|
||||
"ZohoCliq": "ZohoCliq",
|
||||
"Bot Token": "Διακριτικό Bot",
|
||||
wayToGetTelegramToken: "Μπορείτε να πάρετε ένα διακριτικό από {0}.",
|
||||
"Chat ID": "Chat ID",
|
||||
@ -224,6 +225,7 @@ export default {
|
||||
teams: "Microsoft Teams",
|
||||
"Webhook URL": "Webhook URL",
|
||||
wayToGetTeamsURL: "Μπορείτε να μάθετε πώς να δημιουργείτε μια διεύθυνση URL webhook {0}.",
|
||||
wayToGetZohoCliqURL: "Μπορείτε να μάθετε πώς να δημιουργείτε μια διεύθυνση URL webhook {0}.",
|
||||
signal: "Signal",
|
||||
Number: "Αριθμός",
|
||||
Recipients: "Αποδέκτες",
|
||||
|
@ -8,6 +8,8 @@ export default {
|
||||
ignoreTLSError: "Ignore TLS/SSL error for HTTPS websites",
|
||||
upsideDownModeDescription: "Flip the status upside down. If the service is reachable, it is DOWN.",
|
||||
maxRedirectDescription: "Maximum number of redirects to follow. Set to 0 to disable redirects.",
|
||||
enableGRPCTls: "Allow to send gRPC request with TLS connection",
|
||||
grpcMethodDescription: "Method name is convert to cammelCase format such as sayHello, check, etc.",
|
||||
acceptedStatusCodesDescription: "Select status codes which are considered as a successful response.",
|
||||
Maintenance: "Maintenance",
|
||||
statusMaintenance: "Maintenance",
|
||||
@ -72,6 +74,7 @@ export default {
|
||||
Current: "Current",
|
||||
Uptime: "Uptime",
|
||||
"Cert Exp.": "Cert Exp.",
|
||||
Monitor: "Monitor | Monitors",
|
||||
day: "day | days",
|
||||
"-day": "-day",
|
||||
hour: "hour",
|
||||
@ -188,6 +191,7 @@ export default {
|
||||
Indigo: "Indigo",
|
||||
Purple: "Purple",
|
||||
Pink: "Pink",
|
||||
Custom: "Custom",
|
||||
"Search...": "Search...",
|
||||
"Avg. Ping": "Avg. Ping",
|
||||
"Avg. Response": "Avg. Response",
|
||||
@ -207,6 +211,7 @@ export default {
|
||||
here: "here",
|
||||
Required: "Required",
|
||||
telegram: "Telegram",
|
||||
"ZohoCliq": "ZohoCliq",
|
||||
"Bot Token": "Bot Token",
|
||||
wayToGetTelegramToken: "You can get a token from {0}.",
|
||||
"Chat ID": "Chat ID",
|
||||
@ -219,6 +224,8 @@ export default {
|
||||
"Content Type": "Content Type",
|
||||
webhookJsonDesc: "{0} is good for any modern HTTP servers such as Express.js",
|
||||
webhookFormDataDesc: "{multipart} is good for PHP. The JSON will need to be parsed with {decodeFunction}",
|
||||
webhookAdditionalHeadersTitle: "Additional Headers",
|
||||
webhookAdditionalHeadersDesc: "Sets additional headers sent with the webhook.",
|
||||
smtp: "Email (SMTP)",
|
||||
secureOptionNone: "None / STARTTLS (25, 587)",
|
||||
secureOptionTLS: "TLS (465)",
|
||||
@ -237,6 +244,7 @@ export default {
|
||||
teams: "Microsoft Teams",
|
||||
"Webhook URL": "Webhook URL",
|
||||
wayToGetTeamsURL: "You can learn how to create a webhook URL {0}.",
|
||||
wayToGetZohoCliqURL: "You can learn how to create a webhook URL {0}.",
|
||||
signal: "Signal",
|
||||
Number: "Number",
|
||||
Recipients: "Recipients",
|
||||
@ -266,6 +274,10 @@ export default {
|
||||
apprise: "Apprise (Support 50+ Notification services)",
|
||||
GoogleChat: "Google Chat (Google Workspace only)",
|
||||
pushbullet: "Pushbullet",
|
||||
Kook: "Kook",
|
||||
wayToGetKookBotToken: "Create application and get your bot token at {0}",
|
||||
wayToGetKookGuildID: "Switch on 'Developer Mode' in Kook setting, and right click the guild to get its ID",
|
||||
"Guild ID": "Guild ID",
|
||||
line: "Line Messenger",
|
||||
mattermost: "Mattermost",
|
||||
"User Key": "User Key",
|
||||
@ -310,6 +322,7 @@ export default {
|
||||
promosmsTypeSpeed: "SMS SPEED - Highest priority in system. Very quick and reliable but costly (about twice of SMS FULL price).",
|
||||
promosmsPhoneNumber: "Phone number (for Polish recipient You can skip area codes)",
|
||||
promosmsSMSSender: "SMS Sender Name : Pre-registred name or one of defaults: InfoSMS, SMS Info, MaxSMS, INFO, SMS",
|
||||
promosmsAllowLongSMS: "Allow long SMS",
|
||||
"Feishu WebHookUrl": "Feishu WebHookURL",
|
||||
matrixHomeserverURL: "Homeserver URL (with http(s):// and optionally port)",
|
||||
"Internal Room Id": "Internal Room ID",
|
||||
@ -378,6 +391,16 @@ export default {
|
||||
serwersmsAPIPassword: "API Password",
|
||||
serwersmsPhoneNumber: "Phone number",
|
||||
serwersmsSenderName: "SMS Sender Name (registered via customer portal)",
|
||||
smseagle: "SMSEagle",
|
||||
smseagleTo: "Phone number(s)",
|
||||
smseagleGroup: "Phonebook group name(s)",
|
||||
smseagleContact: "Phonebook contact name(s)",
|
||||
smseagleRecipientType: "Recipient type",
|
||||
smseagleRecipient: "Recipient(s) (multiple must be separated with comma)",
|
||||
smseagleToken: "API Access token",
|
||||
smseagleUrl: "Your SMSEagle device URL",
|
||||
smseagleEncoding: "Send as Unicode",
|
||||
smseaglePriority: "Message priority (0-9, default = 0)",
|
||||
stackfield: "Stackfield",
|
||||
Customize: "Customize",
|
||||
"Custom Footer": "Custom Footer",
|
||||
@ -595,16 +618,16 @@ export default {
|
||||
goAlert: "GoAlert",
|
||||
backupOutdatedWarning: "Deprecated: Since a lot of features added and this backup feature is a bit unmaintained, it cannot generate or restore a complete backup.",
|
||||
backupRecommend: "Please backup the volume or the data folder (./data/) directly instead.",
|
||||
"Optional": "Optional",
|
||||
Optional: "Optional",
|
||||
squadcast: "Squadcast",
|
||||
SendKey: "SendKey",
|
||||
"SMSManager API Docs": "SMSManager API Docs ",
|
||||
"Gateway Type": "Gateway Type",
|
||||
SMSManager: "SMSManager",
|
||||
"You can divide numbers with": "You can divide numbers with",
|
||||
"or": "or",
|
||||
or: "or",
|
||||
recurringInterval: "Interval",
|
||||
"Recurring": "Recurring",
|
||||
Recurring: "Recurring",
|
||||
strategyManual: "Active/Inactive Manually",
|
||||
warningTimezone: "It is using the server's timezone",
|
||||
weekdayShortMon: "Mon",
|
||||
@ -631,4 +654,31 @@ export default {
|
||||
"Display Timezone": "Display Timezone",
|
||||
"Server Timezone": "Server Timezone",
|
||||
statusPageMaintenanceEndDate: "End",
|
||||
IconUrl: "Icon URL",
|
||||
"Enable DNS Cache": "Enable DNS Cache",
|
||||
Enable: "Enable",
|
||||
Disable: "Disable",
|
||||
dnsCacheDescription: "It may be not working in some IPv6 environments, disable it if you encounter any issues.",
|
||||
"Single Maintenance Window": "Single Maintenance Window",
|
||||
"Maintenance Time Window of a Day": "Maintenance Time Window of a Day",
|
||||
"Effective Date Range": "Effective Date Range",
|
||||
"Schedule Maintenance": "Schedule Maintenance",
|
||||
"Date and Time": "Date and Time",
|
||||
"DateTime Range": "DateTime Range",
|
||||
Strategy: "Strategy",
|
||||
"Free Mobile User Identifier": "Free Mobile User Identifier",
|
||||
"Free Mobile API Key": "Free Mobile API Key",
|
||||
"Enable TLS": "Enable TLS",
|
||||
"Proto Service Name": "Proto Service Name",
|
||||
"Proto Method": "Proto Method",
|
||||
"Proto Content": "Proto Content",
|
||||
Economy: "Economy",
|
||||
Lowcost: "Lowcost",
|
||||
high: "high",
|
||||
"General Monitor Type": "General Monitor Type",
|
||||
"Passive Monitor Type": "Passive Monitor Type",
|
||||
"Specific Monitor Type": "Specific Monitor Type",
|
||||
dataRetentionTimeError: "Retention period must be 0 or greater",
|
||||
infiniteRetention: "Set to 0 for infinite retention.",
|
||||
confirmDeleteTagMsg: "Are you sure you want to delete this tag? Monitors associated with this tag will not be deleted.",
|
||||
};
|
||||
|
@ -191,6 +191,7 @@ export default {
|
||||
here: "Hemen",
|
||||
Required: "Beharrezkoa",
|
||||
telegram: "Telegram",
|
||||
"ZohoCliq": "ZohoCliq",
|
||||
"Bot Token": "Bot Tokena",
|
||||
wayToGetTelegramToken: "You can get a token from {0}.",
|
||||
"Chat ID": "Txat IDa",
|
||||
@ -221,6 +222,7 @@ export default {
|
||||
teams: "Microsoft Teams",
|
||||
"Webhook URL": "Webhook URL",
|
||||
wayToGetTeamsURL: "You can learn how to create a webhook URL {0}.",
|
||||
wayToGetZohoCliqURL: "You can learn how to create a webhook URL {0}.",
|
||||
signal: "Signal",
|
||||
Number: "Zenbakia",
|
||||
Recipients: "Recipients",
|
||||
|
@ -1,31 +1,49 @@
|
||||
export default {
|
||||
languageName: "Français",
|
||||
checkEverySecond: "Vérifier toutes les {0} secondes",
|
||||
retryCheckEverySecond: "Réessayer toutes les {0} secondes.",
|
||||
retriesDescription: "Nombre d'essais avant que le service soit déclaré hors-ligne.",
|
||||
retryCheckEverySecond: "Réessayer toutes les {0} secondes",
|
||||
resendEveryXTimes: "Renvoyez toutes les {0} fois",
|
||||
resendDisabled: "Renvoi désactivé",
|
||||
retriesDescription: "Nombre d'essais avant que le service ne soit déclaré hors ligne et qu'une notification soit envoyée.",
|
||||
ignoreTLSError: "Ignorer les erreurs liées au certificat SSL/TLS",
|
||||
upsideDownModeDescription: "Si le service est en ligne, il sera alors noté hors-ligne et vice-versa.",
|
||||
maxRedirectDescription: "Nombre maximal de redirections avant que le service soit noté hors-ligne.",
|
||||
acceptedStatusCodesDescription: "Codes HTTP considérés comme en ligne",
|
||||
upsideDownModeDescription: "Si le service est en ligne, il sera alors noté hors ligne et vice-versa.",
|
||||
maxRedirectDescription: "Nombre maximal de redirections avant que le service ne soit marqué comme hors ligne.",
|
||||
enableGRPCTls: "Autoriser l'envoi d'une requête gRPC avec une connexion TLS",
|
||||
grpcMethodDescription: "Le nom de la méthode est converti au format CamelCase tel que sayHello, check, etc.",
|
||||
acceptedStatusCodesDescription: "Codes HTTP qui considèrent le service comme étant disponible.",
|
||||
Maintenance: "Maintenance",
|
||||
statusMaintenance: "Maintenance",
|
||||
"Schedule maintenance": "Planifier la maintenance",
|
||||
"Affected Monitors": "Sondes concernées",
|
||||
"Pick Affected Monitors...": "Sélectionner les sondes concernées...",
|
||||
"Start of maintenance": "Début de la maintenance",
|
||||
"All Status Pages": "Toutes les pages d'état",
|
||||
"Select status pages...": "Sélectionner les pages d'état...",
|
||||
recurringIntervalMessage: "Exécuter une fois par jour | Exécuter une fois tous les {0} jours",
|
||||
affectedMonitorsDescription: "Sélectionnez les sondes concernées par la maintenance en cours",
|
||||
affectedStatusPages: "Afficher ce message de maintenance sur les pages d'état sélectionnées",
|
||||
atLeastOneMonitor: "Sélectionnez au moins une sonde concernée",
|
||||
passwordNotMatchMsg: "Les mots de passe ne correspondent pas",
|
||||
notificationDescription: "Une fois ajoutée, vous devez l'activer manuellement dans les paramètres de vos hôtes.",
|
||||
keywordDescription: "Le mot clé sera recherché dans la réponse HTML/JSON reçue du site internet.",
|
||||
pauseDashboardHome: "En pause",
|
||||
deleteMonitorMsg: "Êtes-vous sûr de vouloir supprimer cette sonde ?",
|
||||
deleteMonitorMsg: "Êtes-vous sûr de vouloir supprimer cette sonde ?",
|
||||
deleteMaintenanceMsg: "Voulez-vous vraiment supprimer cette maintenance ?",
|
||||
deleteNotificationMsg: "Êtes-vous sûr de vouloir supprimer ce type de notifications ? Une fois désactivée, les services qui l'utilisent ne pourront plus envoyer de notifications.",
|
||||
dnsPortDescription: "Port du serveur DNS. La valeur par défaut est 53. Vous pouvez modifier le port à tout moment.",
|
||||
resolverserverDescription: "Le DNS de Cloudflare est utilisé par défaut, mais vous pouvez le changer si vous le souhaitez.",
|
||||
rrtypeDescription: "Veuillez sélectionner un type d'enregistrement DNS",
|
||||
pauseMonitorMsg: "Êtes-vous sûr de vouloir mettre en pause cette sonde ?",
|
||||
pauseMonitorMsg: "Êtes-vous sûr de vouloir mettre en pause cette sonde ?",
|
||||
enableDefaultNotificationDescription: "Pour chaque nouvelle sonde, cette notification sera activée par défaut. Vous pouvez toujours désactiver la notification séparément pour chaque sonde.",
|
||||
clearEventsMsg: "Êtes-vous sûr de vouloir supprimer tous les événements pour cette sonde ?",
|
||||
clearHeartbeatsMsg: "Êtes-vous sûr de vouloir supprimer toutes les vérifications pour cette sonde ?",
|
||||
clearEventsMsg: "Êtes-vous sûr de vouloir supprimer tous les événements pour cette sonde ?",
|
||||
clearHeartbeatsMsg: "Êtes-vous sûr de vouloir supprimer toutes les vérifications pour cette sonde ?",
|
||||
confirmClearStatisticsMsg: "Êtes-vous sûr de vouloir supprimer toutes les statistiques ?",
|
||||
importHandleDescription: "Choisissez 'Ignorer l'existant' si vous voulez ignorer chaque sonde ou notification portant le même nom. L'option 'Écraser' supprime toutes les sondes et notifications existantes.",
|
||||
importHandleDescription: "Choisissez « Ignorer l'existant » si vous voulez ignorer chaque sonde ou notification portant le même nom. L'option « Écraser » supprime toutes les sondes et notifications existantes.",
|
||||
confirmImportMsg: "Êtes-vous sûr de vouloir importer la sauvegarde ? Veuillez vous assurer que vous avez sélectionné la bonne option d'importation.",
|
||||
twoFAVerifyLabel: "Veuillez saisir votre jeton pour vérifier que le système 2FA fonctionne.",
|
||||
tokenValidSettingsMsg: "Le jeton est valide. Vous pouvez maintenant sauvegarder les paramètres 2FA.",
|
||||
confirmEnableTwoFAMsg: "Êtes-vous sûr de vouloir activer le 2FA ?",
|
||||
confirmDisableTwoFAMsg: "Êtes-vous sûr de vouloir désactiver le 2FA ?",
|
||||
tokenValidSettingsMsg: "Le jeton est valide. Vous pouvez maintenant sauvegarder les paramètres de double authentification (2FA).",
|
||||
confirmEnableTwoFAMsg: "Êtes-vous sûr de vouloir activer la double authentification (2FA) ?",
|
||||
confirmDisableTwoFAMsg: "Êtes-vous sûr de vouloir désactiver la double authentification (2FA) ?",
|
||||
Settings: "Paramètres",
|
||||
Dashboard: "Tableau de bord",
|
||||
"New Update": "Mise à jour disponible",
|
||||
@ -33,8 +51,9 @@ export default {
|
||||
Appearance: "Apparence",
|
||||
Theme: "Thème",
|
||||
General: "Général",
|
||||
"Primary Base URL": "URL principale",
|
||||
Version: "Version",
|
||||
"Check Update On GitHub": "Consulter les mises à jour sur Github",
|
||||
"Check Update On GitHub": "Consulter les mises à jour sur GitHub",
|
||||
List: "Lister",
|
||||
Add: "Ajouter",
|
||||
"Add New Monitor": "Ajouter une nouvelle sonde",
|
||||
@ -43,25 +62,25 @@ export default {
|
||||
Down: "Hors ligne",
|
||||
Pending: "En attente",
|
||||
Unknown: "Inconnu",
|
||||
Pause: "En Pause",
|
||||
Pause: "En pause",
|
||||
Name: "Nom",
|
||||
Status: "État",
|
||||
DateTime: "Heure",
|
||||
Message: "Messages",
|
||||
"No important events": "Pas d'évènements important",
|
||||
"No important events": "Aucun évènement important",
|
||||
Resume: "Reprendre",
|
||||
Edit: "Modifier",
|
||||
Delete: "Supprimer",
|
||||
Current: "Actuellement",
|
||||
Uptime: "Uptime",
|
||||
Uptime: "Disponibilité",
|
||||
"Cert Exp.": "Expiration SSL",
|
||||
day: "jour | jours",
|
||||
"-day": "-jours",
|
||||
hour: "-heure",
|
||||
"-hour": "-heures",
|
||||
"-day": " jours",
|
||||
hour: "heure",
|
||||
"-hour": " heure",
|
||||
Response: "Temps de réponse",
|
||||
Ping: "Ping",
|
||||
"Monitor Type": "Type de Sonde",
|
||||
"Monitor Type": "Type de sonde",
|
||||
Keyword: "Mot-clé",
|
||||
"Friendly Name": "Nom d'affichage",
|
||||
URL: "URL",
|
||||
@ -70,25 +89,29 @@ export default {
|
||||
"Heartbeat Interval": "Intervalle de vérification",
|
||||
Retries: "Essais",
|
||||
"Heartbeat Retry Interval": "Réessayer l'intervalle de vérification",
|
||||
"Resend Notification if Down X times consequently": "Renvoyer une notification si hors ligne X fois",
|
||||
Advanced: "Avancé",
|
||||
"Upside Down Mode": "Mode inversé",
|
||||
"Max. Redirects": "Nombre maximum de redirections",
|
||||
"Accepted Status Codes": "Codes HTTP acceptés",
|
||||
"Push URL": "Push URL",
|
||||
needPushEvery: "Vous devez appeler cette URL toutes les {0} secondes.",
|
||||
pushOptionalParams: "Paramètres facultatifs : {0}",
|
||||
Save: "Sauvegarder",
|
||||
Notifications: "Notifications",
|
||||
"Not available, please setup.": "Pas de système de notification disponible, merci de le configurer",
|
||||
"Not available, please setup.": "Non disponible, merci de le configurer.",
|
||||
"Setup Notification": "Créer une notification",
|
||||
Light: "Clair",
|
||||
Dark: "Sombre",
|
||||
Auto: "Automatique",
|
||||
"Theme - Heartbeat Bar": "Voir les services surveillés",
|
||||
"Theme - Heartbeat Bar": "Thème - barres d'état",
|
||||
Normal: "Normal",
|
||||
Bottom: "En dessous",
|
||||
None: "Aucun",
|
||||
Timezone: "Fuseau Horaire",
|
||||
Timezone: "Fuseau horaire",
|
||||
"Search Engine Visibility": "Visibilité par les moteurs de recherche",
|
||||
"Allow indexing": "Autoriser l'indexation par des moteurs de recherche",
|
||||
"Discourage search engines from indexing site": "Refuser l'indexation par des moteurs de recherche",
|
||||
"Allow indexing": "Autoriser l'indexation",
|
||||
"Discourage search engines from indexing site": "Refuser l'indexation",
|
||||
"Change Password": "Changer le mot de passe",
|
||||
"Current Password": "Mot de passe actuel",
|
||||
"New Password": "Nouveau mot de passe",
|
||||
@ -96,26 +119,29 @@ export default {
|
||||
"Update Password": "Mettre à jour le mot de passe",
|
||||
"Disable Auth": "Désactiver l'authentification",
|
||||
"Enable Auth": "Activer l'authentification",
|
||||
Logout: "Se déconnecter",
|
||||
"disableauth.message1": "Voulez-vous vraiment <strong>désactiver l'authentification</strong> ?",
|
||||
"disableauth.message2": "Cette fonctionnalité est conçue pour les scénarios <strong>où vous avez l'intention d'implémenter une authentification tierce</strong> devant Uptime Kuma, comme Cloudflare Access, Authelia ou d'autres mécanismes d'authentification.",
|
||||
"Please use this option carefully!": "Veuillez utiliser cette option avec précaution !",
|
||||
Logout: "Déconnexion",
|
||||
Leave: "Quitter",
|
||||
"I understand, please disable": "Je comprends, désactivez-le",
|
||||
"I understand, please disable": "Je comprends, désactivez-la",
|
||||
Confirm: "Confirmer",
|
||||
Yes: "Oui",
|
||||
No: "Non",
|
||||
Username: "Nom d'utilisateur",
|
||||
Password: "Mot de passe",
|
||||
"Remember me": "Se souvenir de moi",
|
||||
Login: "Se connecter",
|
||||
Login: "Connexion",
|
||||
"No Monitors, please": "Pas de sondes, veuillez",
|
||||
"add one": "en ajouter une",
|
||||
"Notification Type": "Type de notification",
|
||||
Email: "Email",
|
||||
Email: "Courriel",
|
||||
Test: "Tester",
|
||||
"Certificate Info": "Informations sur le certificat SSL",
|
||||
"Resolver Server": "Serveur DNS utilisé",
|
||||
"Resource Record Type": "Type d'enregistrement DNS recherché",
|
||||
"Last Result": "Dernier résultat",
|
||||
"Create your admin account": "Créez votre compte administrateur",
|
||||
"Create your admin account": "Créer votre compte administrateur",
|
||||
"Repeat Password": "Répéter le mot de passe",
|
||||
"Import Backup": "Importation de la sauvegarde",
|
||||
"Export Backup": "Exportation de la sauvegarde",
|
||||
@ -127,9 +153,9 @@ export default {
|
||||
"Apply on all existing monitors": "Appliquer sur toutes les sondes existantes",
|
||||
Create: "Créer",
|
||||
"Clear Data": "Effacer les données",
|
||||
Events: "Evénements",
|
||||
Events: "Événements",
|
||||
Heartbeats: "Vérifications",
|
||||
"Auto Get": "Récuperer automatiquement",
|
||||
"Auto Get": "Récupérer automatiquement",
|
||||
backupDescription: "Vous pouvez sauvegarder toutes les sondes et toutes les notifications dans un fichier JSON.",
|
||||
backupDescription2: "PS : Les données relatives à l'historique et aux événements ne sont pas incluses.",
|
||||
backupDescription3: "Les données sensibles telles que les jetons de notification sont incluses dans le fichier d'exportation, veuillez les conserver soigneusement.",
|
||||
@ -137,15 +163,15 @@ export default {
|
||||
alertWrongFileType: "Veuillez sélectionner un fichier JSON à importer.",
|
||||
"Clear all statistics": "Effacer toutes les statistiques",
|
||||
"Skip existing": "Sauter l'existant",
|
||||
Overwrite: "Ecraser",
|
||||
Overwrite: "Écraser",
|
||||
Options: "Options",
|
||||
"Keep both": "Garder les deux",
|
||||
"Verify Token": "Vérifier le jeton",
|
||||
"Setup 2FA": "Configurer 2FA",
|
||||
"Enable 2FA": "Activer 2FA",
|
||||
"Disable 2FA": "Désactiver 2FA",
|
||||
"2FA Settings": "Paramètres 2FA",
|
||||
"Two Factor Authentication": "Authentification à deux facteurs",
|
||||
"Setup 2FA": "Configurer la double authentification (2FA)",
|
||||
"Enable 2FA": "Activer la double authentification (2FA)",
|
||||
"Disable 2FA": "Désactiver la double authentification (2FA)",
|
||||
"2FA Settings": "Paramètres de la la double authentification (2FA)",
|
||||
"Two Factor Authentication": "Double authentification",
|
||||
Active: "Actif",
|
||||
Inactive: "Inactif",
|
||||
Token: "Jeton",
|
||||
@ -179,52 +205,49 @@ export default {
|
||||
"Go to Dashboard": "Accéder au tableau de bord",
|
||||
"Status Page": "Page de statut",
|
||||
"Status Pages": "Pages de statut",
|
||||
"New Status Page": "Ajouter page de statut",
|
||||
"Add New Status Page": "Ajouter une page de statut",
|
||||
"No status pages": "Aucune page de statut.",
|
||||
"Accept characters:": "Caractères acceptés:",
|
||||
startOrEndWithOnly: "Commence uniquement par {0}",
|
||||
"No consecutive dashes": "Pas de double tirets",
|
||||
Next: "Continuer",
|
||||
"Setup Proxy": "Configuer Proxy",
|
||||
defaultNotificationName: "Ma notification {notification} numéro ({number})",
|
||||
here: "ici",
|
||||
Required: "Requis",
|
||||
telegram: "Telegram",
|
||||
"Bot Token": "Bot Token",
|
||||
"ZohoCliq": "ZohoCliq",
|
||||
"Bot Token": "Jeton du robot",
|
||||
wayToGetTelegramToken: "Vous pouvez obtenir un token depuis {0}.",
|
||||
"Chat ID": "Chat ID",
|
||||
supportTelegramChatID: "Supporte les messages privés / en groupe / l'ID du salon",
|
||||
wayToGetTelegramChatID: "Vous pouvez obtenir l'ID du chat en envoyant un message avec le bot puis en récupérant l'URL pour voir l'ID du salon :",
|
||||
"YOUR BOT TOKEN HERE": "VOTRE TOKEN BOT ICI",
|
||||
chatIDNotFound: "ID du salon introuvable, envoyez un message via le bot avant",
|
||||
supportTelegramChatID: "Prend en charge les messages privés / messages de groupe / l'ID d'un salon",
|
||||
wayToGetTelegramChatID: "Vous pouvez obtenir le Chat ID en envoyant un message avec le robot puis en récupérant l'URL pour voir l'ID du salon :",
|
||||
"YOUR BOT TOKEN HERE": "VOTRE JETON ROBOT ICI",
|
||||
chatIDNotFound: "ID du salon introuvable, envoyez un message via le robot avant",
|
||||
webhook: "Webhook",
|
||||
"Post URL": "Post URL",
|
||||
"Content Type": "Type de contenu",
|
||||
webhookJsonDesc: "{0} est bien/bon pour tous les serveurs HTTP modernes comme express.js",
|
||||
webhookFormDataDesc: "{multipart} est bien/bon pour du PHP, vous avez juste besoin de mettre le json via/depuis {decodeFunction}",
|
||||
smtp: "Email (SMTP)",
|
||||
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.",
|
||||
smtp: "Courriel (SMTP)",
|
||||
secureOptionNone: "Aucun / STARTTLS (25, 587)",
|
||||
secureOptionTLS: "TLS (465)",
|
||||
"Ignore TLS Error": "Ignorer les erreurs TLS",
|
||||
"From Email": "Depuis l'Email",
|
||||
"To Email": "Vers l'Email",
|
||||
"From Email": "Depuis l'adresse",
|
||||
emailCustomSubject: "Objet personnalisé",
|
||||
"To Email": "Vers l'adresse",
|
||||
smtpCC: "CC",
|
||||
smtpBCC: "BCC",
|
||||
smtpBCC: "CCI",
|
||||
discord: "Discord",
|
||||
"Discord Webhook URL": "Discord Webhook URL",
|
||||
wayToGetDiscordURL: "Vous pouvez l'obtenir en allant dans 'Paramètres du Serveur' -> 'Intégrations' -> 'Créer un Webhook'",
|
||||
"Bot Display Name": "Nom du bot (affiché)",
|
||||
"Prefix Custom Message": "Prefixe du message personnalisé",
|
||||
"Discord Webhook URL": "URL vers le webhook Discord",
|
||||
wayToGetDiscordURL: "Vous pouvez l'obtenir en allant dans « Paramètres du serveur » -> « Intégrations » -> « Créer un Webhook »",
|
||||
"Bot Display Name": "Nom du robot (affiché)",
|
||||
"Prefix Custom Message": "Préfixe du message personnalisé",
|
||||
"Hello @everyone is...": "Bonjour {'@'}everyone il...",
|
||||
teams: "Microsoft Teams",
|
||||
"Webhook URL": "Webhook URL",
|
||||
wayToGetTeamsURL: "Vous pouvez apprendre comment créer un Webhook {0}.",
|
||||
"Webhook URL": "URL vers le webhook",
|
||||
wayToGetTeamsURL: "Vous pouvez apprendre comment créer une URL Webhook {0}.",
|
||||
wayToGetZohoCliqURL: "Vous pouvez apprendre comment créer une URL Webhook {0}.",
|
||||
signal: "Signal",
|
||||
Number: "Numéro",
|
||||
Recipients: "Destinataires",
|
||||
needSignalAPI: "Vous avez besoin d'un client Signal avec l'API REST.",
|
||||
wayToCheckSignalURL: "Vous pouvez regarder l'URL sur comment le mettre en place :",
|
||||
wayToCheckSignalURL: "Vous pouvez regarder l'URL suivante pour savoir comment la mettre en place :",
|
||||
signalImportant: "IMPORTANT : Vous ne pouvez pas mixer les groupes et les numéros en destinataires !",
|
||||
gotify: "Gotify",
|
||||
"Application Token": "Jeton d'application",
|
||||
@ -233,19 +256,26 @@ export default {
|
||||
slack: "Slack",
|
||||
"Icon Emoji": "Icon Emoji",
|
||||
"Channel Name": "Nom du salon",
|
||||
"Uptime Kuma URL": "Uptime Kuma URL",
|
||||
aboutWebhooks: "Plus d'informations sur les Webhooks ici : {0}",
|
||||
aboutChannelName: "Mettez le nom du salon dans {0} dans 'Channel Name' si vous voulez bypass le salon Webhook. Ex : #autre-salon",
|
||||
"Uptime Kuma URL": "URL vers Uptime Kuma",
|
||||
aboutWebhooks: "Plus d'informations sur les webhooks ici : {0}",
|
||||
aboutChannelName: "Mettez le nom du salon dans {0} dans « Nom du salon » si vous voulez contourner le salon webhook. Ex. : #autre-salon",
|
||||
aboutKumaURL: "Si vous laissez l'URL d'Uptime Kuma vierge, elle redirigera vers la page du projet GitHub.",
|
||||
emojiCheatSheet: "Aide emoji : {0}",
|
||||
emojiCheatSheet: "Aide sur les émojis : {0}",
|
||||
"rocket.chat": "Rocket.chat",
|
||||
pushover: "Pushover",
|
||||
pushy: "Pushy",
|
||||
PushByTechulus: "Push by Techulus",
|
||||
octopush: "Octopush",
|
||||
promosms: "PromoSMS",
|
||||
clicksendsms: "ClickSend SMS",
|
||||
lunasea: "LunaSea",
|
||||
apprise: "Apprise (Prend en charge plus de 50 services de notification)",
|
||||
apprise: "Apprise (prend en charge plus de 50 services de notification)",
|
||||
GoogleChat: "Google Chat (Google Workspace uniquement)",
|
||||
pushbullet: "Pushbullet",
|
||||
Kook: "Kook",
|
||||
wayToGetKookBotToken: "Créez une application et récupérer le jeton de robot à l'addresse {0}",
|
||||
wayToGetKookGuildID: "Passez en « mode développeur » dans les paramètres de Kook, et cliquez droit sur le Guild pour obtenir son identifiant",
|
||||
"Guild ID": "Identifiant de Guild",
|
||||
line: "Line Messenger",
|
||||
mattermost: "Mattermost",
|
||||
"User Key": "Clé d'utilisateur",
|
||||
@ -253,91 +283,75 @@ export default {
|
||||
"Message Title": "Titre du message",
|
||||
"Notification Sound": "Son de notification",
|
||||
"More info on:": "Plus d'informations sur : {0}",
|
||||
pushoverDesc1: "Priorité d'urgence (2) a par défaut 30 secondes de délai dépassé entre les tentatives et expierera après 1 heure.",
|
||||
pushoverDesc2: "Si vous voulez envoyer des notifications sur différents Appareils, remplissez le champ 'Device'.",
|
||||
"SMS Type": "SMS Type",
|
||||
octopushTypePremium: "Premium (Rapide - recommandé pour les alertes)",
|
||||
octopushTypeLowCost: "À bas prix (Lent, bloqué de temps en temps par l'opérateur)",
|
||||
"Check octopush prices": "Vérifier les prix d'octopush {0}.",
|
||||
octopushPhoneNumber: "Numéro de téléphone (format int., ex : +33612345678) ",
|
||||
octopushSMSSender: "Nom de l'envoyer : 3-11 caractères alphanumériques avec espace (a-zA-Z0-9)",
|
||||
"LunaSea Device ID": "LunaSea Device ID",
|
||||
"Apprise URL": "Apprise URL",
|
||||
"Example:": "Exemple : {0}",
|
||||
pushoverDesc1: "Priorité d'urgence (2) a un délai par défaut de 30 secondes entre les tentatives et expire après une heure.",
|
||||
pushoverDesc2: "Si vous voulez envoyer des notifications sur différents appareils, remplissez le champ « Appareil ».",
|
||||
"SMS Type": "Type de SMS",
|
||||
octopushTypePremium: "Premium (rapide - recommandé pour les alertes)",
|
||||
octopushTypeLowCost: "Économique (lent, bloqué de temps en temps par l'opérateur)",
|
||||
checkPrice: "Vérification {0} tarifs :",
|
||||
apiCredentials: "Identifiants de l'API",
|
||||
octopushLegacyHint: "Voulez-vous utiliser l'ancienne version d'Octopush (2011-2020) ou la nouvelle version ?",
|
||||
"Check octopush prices": "Vérifier les prix d'Octopush {0}.",
|
||||
octopushPhoneNumber: "Numéro de téléphone (format international, ex. : +33612345678)",
|
||||
octopushSMSSender: "Nom de l'expéditeur : 3-11 caractères alphanumériques avec espace (a-zA-Z0-9)",
|
||||
"LunaSea Device ID": "Identifiant d'appareil LunaSea",
|
||||
"Apprise URL": "URL d'Apprise",
|
||||
"Example:": "Exemple : {0}",
|
||||
"Read more:": "En savoir plus : {0}",
|
||||
"Status:": "Status : {0}",
|
||||
"Status:": "État : {0}",
|
||||
"Read more": "En savoir plus",
|
||||
appriseInstalled: "Apprise est installé.",
|
||||
appriseNotInstalled: "Apprise n'est pas installé. {0}",
|
||||
"Access Token": "Token d'accès",
|
||||
"Channel access token": "Token d'accès au canal",
|
||||
"Line Developers Console": "Ligne console de développeurs",
|
||||
lineDevConsoleTo: "Ligne console de développeurs - {0}",
|
||||
"Access Token": "Jeton d'accès",
|
||||
"Channel access token": "Jeton d'accès au canal",
|
||||
"Line Developers Console": "Console développeurs Line",
|
||||
lineDevConsoleTo: "Console développeurs Line - {0}",
|
||||
"Basic Settings": "Paramètres de base",
|
||||
"User ID": "Identifiant utilisateur",
|
||||
"Messaging API": "Messaging API",
|
||||
wayToGetLineChannelToken: "Premièrement accéder à {0}, créez un Provider et un Salon (Messaging API), puis vous pourrez avoir le Token d'accès du salon ainsi que l'Identifiant utilisateur depuis le même menu.",
|
||||
"Icon URL": "Icon URL",
|
||||
aboutIconURL: "Vous pouvez mettre un lien vers l'image dans \"Icon URL\" pour remplacer l'image de profil par défaut. Ne sera pas utilisé si Icon Emoji est défini.",
|
||||
aboutMattermostChannelName: "Vous pouvez remplacer le salon par défaut que le Webhook utilise en mettant le nom du salon dans le champ \"Channel Name\". Vous aurez besoin de l'activer depuis les paramètres de Mattermost. Ex : #autre-salon",
|
||||
"Messaging API": "Messaging API", // Ne pas traduire, il s'agit du type de salon affiché sur la console développeurs Line
|
||||
wayToGetLineChannelToken: "Premièrement accédez à {0}, créez un <i>provider</i> et définissez un type de salon à « Messaging API ». Vous pourrez alors avoir puis vous pourrez avoir le jeton d'accès du salon et l'identifiant utilisateur demandés.",
|
||||
"Icon URL": "URL vers l'icône",
|
||||
aboutIconURL: "Vous pouvez mettre un lien vers une image dans « URL vers l'icône » pour remplacer l'image de profil par défaut. Elle ne sera utilisé que si « Icône émoji » n'est pas défini.",
|
||||
aboutMattermostChannelName: "Vous pouvez remplacer le salon par défaut que le webhook utilise en mettant le nom du salon dans le champ « Nom du salon ». Vous aurez besoin de l'activer depuis les paramètres de Mattermost. Ex. : #autre-salon",
|
||||
matrix: "Matrix",
|
||||
promosmsTypeEco: "SMS ECO - Pas cher mais lent et souvent surchargé. Limité uniquement aux déstinataires Polonais.",
|
||||
promosmsTypeFlash: "SMS FLASH - Le message sera automatiquement affiché sur l'appareil du destinataire. Limité uniquement aux déstinataires Polonais.",
|
||||
promosmsTypeFull: "SMS FULL - Version Premium des SMS, Vous pouvez mettre le nom de l'expéditeur (Vous devez vous enregistrer avant). Fiable pour les alertes.",
|
||||
promosmsTypeSpeed: "SMS SPEED - La plus haute des priorités dans le système. Très rapide et fiable mais cher (environ le double du prix d'un SMS FULL).",
|
||||
promosmsPhoneNumber: "Numéro de téléphone (Poiur les déstinataires Polonais, vous pouvez enlever les codes interna.)",
|
||||
promosmsSMSSender: "SMS Expéditeur : Nom pré-enregistré ou l'un de base : InfoSMS, SMS Info, MaxSMS, INFO, SMS",
|
||||
"Primary Base URL": "URL principale",
|
||||
emailCustomSubject: "Sujet personalisé",
|
||||
clicksendsms: "ClickSend SMS",
|
||||
checkPrice: "Vérification {0} tarifs :",
|
||||
apiCredentials: "Crédentials de l'API",
|
||||
octopushLegacyHint: "Vous utilisez l'ancienne version d'Octopush (2011-2020) ou la nouvelle version ?",
|
||||
promosmsTypeEco: "SMS ECO - Bon marché mais lent et souvent surchargé. Limité uniquement aux destinataires polonais.",
|
||||
promosmsTypeFlash: "SMS FLASH - Le message sera automatiquement affiché sur l'appareil du destinataire. Limité uniquement aux destinataires Polonais.",
|
||||
promosmsTypeFull: "SMS FULL - Version premium des SMS. Vous pouvez mettre le nom de l'expéditeur (vous devez l'enregistrer au préalable). Fiable pour les alertes.",
|
||||
promosmsTypeSpeed: "SMS SPEED - Priorité élevée pour le système. Très rapide et fiable mais coûteux (environ le double du prix d'un SMS FULL).",
|
||||
promosmsPhoneNumber: "Numéro de téléphone (pour les destinataires polonais, vous pouvez ignorer l'indicatif international)",
|
||||
promosmsSMSSender: "Nom de l'expéditeur du SMS : Nom pré-enregistré ou l'un de base : InfoSMS, SMS Info, MaxSMS, INFO, SMS",
|
||||
"Feishu WebHookUrl": "Feishu WebHookURL",
|
||||
matrixHomeserverURL: "L'URL du serveur (avec http(s):// et le port de manière facultatif)",
|
||||
matrixHomeserverURL: "L'URL du serveur (avec http(s):// et le port de manière facultative)",
|
||||
"Internal Room Id": "ID de la salle interne",
|
||||
matrixDesc1: "Vous pouvez trouver l'ID de salle interne en regardant dans la section avancée des paramètres dans le client Matrix. C'est censé ressembler à !QMdRCpUIfLwsfjxye6:home.server.",
|
||||
matrixDesc2: "Il est fortement recommandé de créer un nouvel utilisateur et de ne pas utiliser le jeton d'accès de votre propre utilisateur Matrix, car il vous donnera un accès complet à votre compte et à toutes les salles que vous avez rejointes. Au lieu de cela, créez un nouvel utilisateur et invitez-le uniquement dans la salle dans laquelle vous souhaitez recevoir la notification. Vous pouvez obtenir le jeton d'accès en exécutant {0}",
|
||||
matrixDesc2: "Il est fortement recommandé de créer un nouvel utilisateur et de ne pas utiliser le jeton d'accès de votre propre utilisateur Matrix, car il vous donnera un accès complet à votre compte et à toutes les salles que vous avez rejointes. Pour cela, créez un nouvel utilisateur et invitez-le uniquement dans la salle dans laquelle vous souhaitez recevoir la notification. Vous pouvez obtenir le jeton d'accès en exécutant {0}",
|
||||
Method: "Méthode",
|
||||
Body: "Le corps",
|
||||
Body: "Corps",
|
||||
Headers: "En-têtes",
|
||||
PushUrl: "Push URL",
|
||||
HeadersInvalidFormat: "Les en-têtes de la requête ne sont pas dans un format JSON valide: ",
|
||||
BodyInvalidFormat: "Le corps de la requête n'est pas dans un format JSON valide: ",
|
||||
PushUrl: "URL Push",
|
||||
HeadersInvalidFormat: "Les en-têtes de la requête ne sont pas dans un format JSON valide : ",
|
||||
BodyInvalidFormat: "Le corps de la requête n'est pas dans un format JSON valide : ",
|
||||
"Monitor History": "Historique de la sonde",
|
||||
clearDataOlderThan: "Garder l'historique des données de la sonde durant {0} jours.",
|
||||
clearDataOlderThan: "Conserver l'historique des données de la sonde durant {0} jours.",
|
||||
PasswordsDoNotMatch: "Les mots de passe ne correspondent pas.",
|
||||
records: "Enregistrements",
|
||||
records: "enregistrements",
|
||||
"One record": "Un enregistrement",
|
||||
steamApiKeyDescription: "Pour surveiller un serveur Steam, vous avez besoin d'une clé Steam Web-API. Vous pouvez enregistrer votre clé ici : ",
|
||||
steamApiKeyDescription: "Pour surveiller un serveur Steam, vous avez besoin d'une clé Steam Web-API. Vous pouvez enregistrer votre clé ici : ",
|
||||
"Current User": "Utilisateur actuel",
|
||||
topic: "Topic",
|
||||
topicExplanation: "Topic MQTT à surveiller",
|
||||
successMessage: "Message de réussite",
|
||||
successMessageExplanation: "Message MQTT qui sera considéré comme un succès",
|
||||
recent: "Récent",
|
||||
alertaApiEndpoint: "API Endpoint",
|
||||
alertaEnvironment: "Environement",
|
||||
alertaApiKey: "Clé de l'API",
|
||||
alertaAlertState: "État de l'Alerte",
|
||||
alertaRecoverState: "État de récupération",
|
||||
resendEveryXTimes: "Renvoyez toutes les {0} fois",
|
||||
resendDisabled: "Renvoi désactivé",
|
||||
dnsPortDescription: "Port du serveur DNS. La valeur par défaut est 53. Vous pouvez modifier le port à tout moment.",
|
||||
"Resend Notification if Down X times consequently": "Renvoyer la notification a partir d'un certain temps",
|
||||
"Push URL": "Push URL",
|
||||
needPushEvery: "Vous devez appeler cette URL toutes les {0} secondes.",
|
||||
pushOptionalParams: "parametres optionnels: {0}",
|
||||
"disableauth.message1": "Voulez-vous vraiment <strong>désactiver l'authentification</strong>?",
|
||||
"disableauth.message2": "Il est conçu pour les scénarios <strong>où vous avez l'intention d'implémenter une authentification tierce</strong> devant Uptime Kuma, comme Cloudflare Access, Authelia ou d'autres mécanismes d'authentification.",
|
||||
"Please use this option carefully!": "Veuillez utiliser cette option avec précaution !",
|
||||
PushByTechulus: "Pousser par Techulus",
|
||||
GoogleChat: "Google Chat (Google Workspace uniquement)",
|
||||
Done: "Fait",
|
||||
Info: "Info",
|
||||
Security: "Sécurité",
|
||||
"Steam API Key": "Clé API Steam",
|
||||
"Steam API Key": "Clé d'API Steam",
|
||||
"Shrink Database": "Réduire la base de données",
|
||||
"Pick a RR-Type...": "Pick a RR-Type...",
|
||||
"Pick Accepted Status Codes...": "Pick Accepted Status Codes...",
|
||||
"Pick a RR-Type...": "Choisissez un type d'enregistrement...",
|
||||
"Pick Accepted Status Codes...": "Choisissez les codes de statut acceptés...",
|
||||
Default: "Défaut",
|
||||
"HTTP Options": "HTTP Options",
|
||||
"HTTP Options": "Options HTTP",
|
||||
"Create Incident": "Créer un incident",
|
||||
Title: "Titre",
|
||||
Content: "Contenu",
|
||||
@ -351,151 +365,160 @@ export default {
|
||||
light: "Blanc",
|
||||
dark: "Noir",
|
||||
Post: "Post",
|
||||
"Please input title and content": "Veuillez entrer le titre et le contenu",
|
||||
Created: "Created",
|
||||
"Please input title and content": "Veuillez saisir le titre et le contenu",
|
||||
Created: "Créé",
|
||||
"Last Updated": "Dernière mise à jour",
|
||||
Unpin: "Détacher",
|
||||
Unpin: "Retirer",
|
||||
"Switch to Light Theme": "Passer au thème clair",
|
||||
"Switch to Dark Theme": "Passer au thème sombre",
|
||||
"Show Tags": "Voir les étiquettes",
|
||||
"Show Tags": "Afficher les étiquettes",
|
||||
"Hide Tags": "Masquer les étiquettes",
|
||||
Description: "Description",
|
||||
"No monitors available.": "Aucun moniteur disponible.",
|
||||
"Add one": "En rajouter un",
|
||||
"No Monitors": "Aucun moniteur",
|
||||
"No monitors available.": "Aucune sonde disponible.",
|
||||
"Add one": "En rajouter une",
|
||||
"No Monitors": "Aucune sonde",
|
||||
"Untitled Group": "Groupe sans titre",
|
||||
Services: "Services",
|
||||
Discard: "Annuler",
|
||||
Discard: "Abandonner",
|
||||
Cancel: "Annuler",
|
||||
shrinkDatabaseDescription: "Déclencher la base de données VACUUM pour SQLite. Si votre base de données est créée après 1.10.0, AUTO_VACUUM est déjà activé et cette action n'est pas nécessaire.",
|
||||
"Powered by": "Propulsé par",
|
||||
shrinkDatabaseDescription: "Déclenche la commande VACUUM pour SQLite. Si votre base de données a été créée après la version 1.10.0, AUTO_VACUUM est déjà activé et cette action n'est pas nécessaire.",
|
||||
serwersms: "SerwerSMS.pl",
|
||||
serwersmsAPIUser: "Nom d'utilisateur de l'API (incl. webapi_ prefix)",
|
||||
serwersmsAPIPassword: "Mot de passe API",
|
||||
serwersmsPhoneNumber: "Numéro de téléphone",
|
||||
serwersmsSenderName: "Nom de l'expéditeur du SMS (enregistré via le portail client)",
|
||||
smseagle: "SMSEagle",
|
||||
smseagleTo: "Numéro(s) de téléphone",
|
||||
smseagleGroup: "Nom(s) de groupe(s) de répertoire",
|
||||
smseagleContact: "Nom(s) de contact du répertoire",
|
||||
smseagleRecipientType: "Type de destinataire",
|
||||
smseagleRecipient: "Destinataire(s) (les multiples doivent être séparés par une virgule)",
|
||||
smseagleToken: "Jeton d'accès à l'API",
|
||||
smseagleUrl: "L'URL de votre appareil SMSEagle",
|
||||
smseagleEncoding: "Envoyer en Unicode",
|
||||
smseaglePriority: "Priorité des messages (0-9, par défaut = 0)",
|
||||
stackfield: "Stackfield",
|
||||
Customize: "Personnaliser",
|
||||
"Custom Footer": "Pied de page personnalisé",
|
||||
"Custom CSS": "CSS personnalisé",
|
||||
deleteStatusPageMsg: "Voulez-vous vraiment supprimer cette page d'état ?",
|
||||
Proxies: "Proxies",
|
||||
default: "Défaut",
|
||||
enabled: "Activé",
|
||||
setAsDefault: "Définir par défaut",
|
||||
deleteProxyMsg: "Voulez-vous vraiment supprimer ce proxy pour tous les moniteurs ?",
|
||||
proxyDescription: "Les proxys doivent être affectés à un moniteur pour fonctionner.",
|
||||
enableProxyDescription: "Ce proxy n'aura pas d'effet sur les demandes de moniteur tant qu'il n'est pas activé. Vous pouvez contrôler la désactivation temporaire du proxy de tous les moniteurs en fonction de l'état d'activation.",
|
||||
setAsDefaultProxyDescription: "Ce proxy sera activé par défaut pour les nouveaux moniteurs. Vous pouvez toujours désactiver le proxy séparément pour chaque moniteur.",
|
||||
Valid: "Valide",
|
||||
Invalid: "Non valide",
|
||||
User: "Utilisateur",
|
||||
Installed: "Installé",
|
||||
"Not installed": "Pas installé",
|
||||
"Remove Token": "Supprimer le jeton",
|
||||
Slug: "Chemin",
|
||||
"The slug is already taken. Please choose another slug.": "Le chemin est déjà pris. Veuillez choisir un autre chemin.",
|
||||
Authentication: "Authentication",
|
||||
"Page Not Found": "Page non trouvée",
|
||||
Backup: "Sauvegarde",
|
||||
About: "À propos de",
|
||||
"Footer Text": "Texte de pied de page",
|
||||
"Domain Names": "Noms de domaine",
|
||||
signedInDisp: "Connecté en tant que {0}",
|
||||
signedInDispDisabled: "Authentification désactivée.",
|
||||
"Show update if available": "Afficher la mise à jour si disponible",
|
||||
"Also check beta release": "Vérifiez également la version bêta",
|
||||
"Steam Game Server": "Serveur de jeu Steam",
|
||||
"Most likely causes:": "Causes les plus probables:",
|
||||
"The resource is no longer available.": "La ressource n'est plus disponible.",
|
||||
"There might be a typing error in the address.": "Il se peut qu'il y ait une erreur de frappe dans l'adresse.",
|
||||
"What you can try:": "Ce que vous pouvez essayer:",
|
||||
"Retype the address.": "Retapez l'adresse.",
|
||||
"Go back to the previous page.": "Retournez à la page précédente.",
|
||||
"Coming Soon": "À venir",
|
||||
settingsCertificateExpiry: "Expiration du certificat TLS",
|
||||
certificationExpiryDescription: "Les moniteurs HTTPS déclenchent une notification lorsque le certificat TLS expire dans:",
|
||||
"Setup Docker Host": "Configurer l'hôte Docker",
|
||||
"Connection Type": "Type de connexion",
|
||||
deleteDockerHostMsg: "Voulez-vous vraiment supprimer cet hôte Docker pour tous les moniteurs ?",
|
||||
"Container Name / ID": "Nom / ID du conteneur",
|
||||
"Docker Host": "Hôte Docker",
|
||||
"Docker Hosts": "Hôtes Docker",
|
||||
Domain: "Domaine",
|
||||
trustProxyDescription: "Faire confiance aux en-têtes 'X-Forwarded-*'. Si vous souhaitez obtenir la bonne adresse IP client et que votre Uptime Kuma est en retard, comme Nginx ou Apache, vous devez l'activer.",
|
||||
wayToGetLineNotifyToken: "Vous pouvez obtenir un jeton d'accès auprès de {0}",
|
||||
Examples: "Exemples",
|
||||
"Home Assistant URL": "Home Assistant URL",
|
||||
"Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "Un jeton d'accès de longue durée peut être créé en cliquant sur le nom de votre profil (en bas à gauche) et en faisant défiler vers le bas, puis cliquez sur Créer un jeton. ",
|
||||
"Notification Service": "Service de notifications",
|
||||
"default: notify all devices": "par défaut: notifier tous les appareils",
|
||||
"A list of Notification Services can be found in Home Assistant under \"Developer Tools > Services\" search for \"notification\" to find your device/phone name.": "Une liste des services de notification peut être trouvée dans Home Assistant sous \"Outils de développement > Services\" recherchez \"notification\" pour trouver le nom de votre appareil/téléphone.",
|
||||
"Automations can optionally be triggered in Home Assistant:": "Les automatisations peuvent éventuellement être déclenchées dans Home Assistant:",
|
||||
"Trigger type:": "Type de déclencheur:",
|
||||
"Event type:": "Type d'événement:",
|
||||
"Event data:": "Données d'événement:",
|
||||
topic: "Topic",
|
||||
topicExplanation: "MQTT sujet à surveiller",
|
||||
successMessage: "Message de réussite",
|
||||
successMessageExplanation: "MQTT message qui sera considéré comme un succès",
|
||||
"Powered by": "Propulsé par",
|
||||
serwersms: "SerwerSMS.pl",
|
||||
stackfield: "Stackfield",
|
||||
smtpDkimSettings: "Paramètres DKIM",
|
||||
smtpDkimDesc: "Veuillez vous référer au Nodemailer DKIM {0} pour l'utilisation.",
|
||||
documentation: "Documentation",
|
||||
documentation: "documentation",
|
||||
smtpDkimDomain: "Nom de domaine",
|
||||
smtpDkimKeySelector: "Sélecteur de clé",
|
||||
smtpDkimPrivateKey: "Clé privée",
|
||||
smtpDkimHashAlgo: "Algorithme de hachage (facultatif)",
|
||||
smtpDkimheaderFieldNames: "Clés d'en-tête à signer (facultatif)",
|
||||
smtpDkimskipFields: "Clés d'en-tête à ne pas signer (facultatif)",
|
||||
wayToGetPagerDutyKey: "Vous pouvez l'obtenir en allant dans Service -> Annuaire des services -> (Sélectionner un service) -> Intégrations -> Ajouter une intégration. Ici, vous pouvez rechercher \"Events API V2\". Plus d'infos {0}",
|
||||
wayToGetPagerDutyKey: "Vous pouvez l'obtenir en allant dans Service -> Annuaire des services -> (sélectionner un service) -> Intégrations -> Ajouter une intégration. Ici, vous pouvez rechercher \"Events API V2\". Plus d'infos {0}",
|
||||
"Integration Key": "Clé d'intégration",
|
||||
"Integration URL": "URL d'intégration",
|
||||
"Auto resolve or acknowledged": "Résolution automatique ou accusé de réception",
|
||||
"do nothing": "ne fais rien",
|
||||
"auto acknowledged": "accusé de réception automatique",
|
||||
"auto resolve": "résolution automatique",
|
||||
gorush: "Gorush",
|
||||
alerta: "Alerta",
|
||||
alertaApiEndpoint: "API Endpoint",
|
||||
alertaEnvironment: "Environnement",
|
||||
alertaApiKey: "Clé de l'API",
|
||||
alertaAlertState: "État de l'alerte",
|
||||
alertaRecoverState: "État de récupération",
|
||||
deleteStatusPageMsg: "Voulez-vous vraiment supprimer cette page d'état ?",
|
||||
Proxies: "Proxies",
|
||||
default: "Défaut",
|
||||
enabled: "Activé",
|
||||
setAsDefault: "Définir par défaut",
|
||||
deleteProxyMsg: "Voulez-vous vraiment supprimer ce proxy pour toutes les sondes ?",
|
||||
proxyDescription: "Les proxies doivent être affectés à une sonde pour fonctionner.",
|
||||
enableProxyDescription: "Ce proxy n'aura pas d'effet sur les demandes de sonde tant qu'il n'est pas activé. Vous pouvez contrôler la désactivation temporaire du proxy de toutes les sondes en fonction de l'état d'activation.",
|
||||
setAsDefaultProxyDescription: "Ce proxy sera activé par défaut pour les nouvelles sondes. Vous pouvez toujours désactiver le proxy séparément pour chaque sonde.",
|
||||
"Certificate Chain": "Chaîne de certificats",
|
||||
Valid: "Valide",
|
||||
Invalid: "Non valide",
|
||||
AccessKeyId: "ID de clé d'accès",
|
||||
SecretAccessKey: "Clé secrète d'accès",
|
||||
PhoneNumbers: "Les numéros de téléphone",
|
||||
PhoneNumbers: "Numéros de téléphone",
|
||||
TemplateCode: "Modèle de code",
|
||||
SignName: "Signature",
|
||||
"Sms template must contain parameters: ": "Le modèle de SMS doit contenir des paramètres : ",
|
||||
"Sms template must contain parameters: ": "Le modèle de SMS doit contenir des paramètres : ",
|
||||
"Bark Endpoint": "Endpoint Bark",
|
||||
"Bark Group": "Groupe Bark",
|
||||
"Bark Sound": "Son Bark",
|
||||
WebHookUrl: "WebHookUrl",
|
||||
SecretKey: "Clé secrète",
|
||||
"For safety, must use secret key": "Pour la sécurité, doit utiliser la clé secrète",
|
||||
"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",
|
||||
Topic: "Topic",
|
||||
"Proxy server has authentication": "Le serveur proxy a une authentification",
|
||||
"WeCom Bot Key": "Clé de robot WeCom",
|
||||
"Setup Proxy": "Configurer le proxy",
|
||||
"Proxy Protocol": "Protocole proxy",
|
||||
"Proxy Server": "Serveur proxy",
|
||||
"Proxy server has authentication": "Une authentification est nécessaire pour le serveur proxy",
|
||||
User: "Utilisateur",
|
||||
Installed: "Installé",
|
||||
"Not installed": "Non installé",
|
||||
Running: "Fonctionne",
|
||||
"Not running": "Ne fonctionne pas",
|
||||
Start: "Start",
|
||||
Stop: "Stop",
|
||||
"Remove Token": "Supprimer le jeton",
|
||||
Start: "Démarrer",
|
||||
Stop: "Arrêter",
|
||||
"Uptime Kuma": "Uptime Kuma",
|
||||
"No Proxy": "Pas de Proxy",
|
||||
"Add New Status Page": "Ajouter une page de statut",
|
||||
Slug: "Chemin",
|
||||
"Accept characters:": "Caractères acceptés : ",
|
||||
startOrEndWithOnly: "Commence uniquement par {0}",
|
||||
"No consecutive dashes": "Pas de double tirets",
|
||||
Next: "Continuer",
|
||||
"The slug is already taken. Please choose another slug.": "Un chemin existe déjà. Veuillez en choisir un autre.",
|
||||
"No Proxy": "Pas de proxy",
|
||||
Authentication: "Authentification",
|
||||
"HTTP Basic Auth": "Authentification de base HTTP",
|
||||
"New Status Page": "Nouvelle page de statut",
|
||||
"Page Not Found": "Page non trouvée",
|
||||
"Reverse Proxy": "Proxy inverse",
|
||||
wayToGetCloudflaredURL: "(Télécharger cloudflared depuis {0})",
|
||||
cloudflareWebsite: "le site Cloudflare ",
|
||||
"Message:": "Message:",
|
||||
"Don't know how to get the token? Please read the guide:": "Vous ne savez pas comment obtenir le jeton ? Veuillez lire le 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.": "La connexion actuelle peut être perdue si vous vous connectez actuellement via Cloudflare Tunnel. Êtes-vous sûr de vouloir l'arrêter ? Tapez votre mot de passe actuel pour le confirmer.",
|
||||
Backup: "Sauvegarde",
|
||||
About: "À propos",
|
||||
wayToGetCloudflaredURL: "(télécharger cloudflared depuis {0})",
|
||||
cloudflareWebsite: "Site web de Cloudflare",
|
||||
"Message:": "Message : ",
|
||||
"Don't know how to get the token? Please read the guide:": "Vous ne savez pas comment obtenir le jeton ? Lisez le 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.": "La connexion actuelle peut être perdue si vous vous connectez actuellement via un tunnel Cloudflare. Êtes-vous sûr de vouloir l'arrêter ? Tapez votre mot de passe actuel pour le confirmer.",
|
||||
"HTTP Headers": "En-têtes HTTP",
|
||||
"Trust Proxy": "Proxy de confiance",
|
||||
"Other Software": "Autres logiciels",
|
||||
"For example: nginx, Apache and Traefik.": "Par exemple : nginx, Apache et Traefik.",
|
||||
"Please read": "S'il vous plaît Lisez",
|
||||
"Valid To:": "Valable pour:",
|
||||
"Days Remaining:": "Jours restant:",
|
||||
"Domain Name Expiry Notification": "Notification d'expiration de nom de domaine",
|
||||
"For example: nginx, Apache and Traefik.": "Par exemple : nginx, Apache et Traefik.",
|
||||
"Please read": "Veuillez lire",
|
||||
"Subject:": "Objet : ",
|
||||
"Valid To:": "Valable jusqu'au : ",
|
||||
"Days Remaining:": "Jours restants : ",
|
||||
"Issuer:": "Émetteur : ",
|
||||
"Fingerprint:": "Empreinte : ",
|
||||
"No status pages": "Aucune page de statut.",
|
||||
"Domain Name Expiry Notification": "Notification d'expiration du nom de domaine",
|
||||
Proxy: "Proxy",
|
||||
"Date Created": "Date de création",
|
||||
HomeAssistant: "Home Assistant",
|
||||
onebotHttpAddress: "Adresse HTTP OneBot",
|
||||
onebotMessageType: "Type de message OneBot",
|
||||
onebotGroupMessage: "Groupe",
|
||||
onebotPrivateMessage: "Privé",
|
||||
onebotUserOrGroupId: "ID de groupe/utilisateur",
|
||||
onebotSafetyTips: "Pour des raisons de sécurité, vous devez définir un jeton d'accès",
|
||||
"PushDeer Key": "Clé PushDeer",
|
||||
"Show Powered By": "Afficher \"Propulsé par\"",
|
||||
"Footer Text": "Texte de pied de page",
|
||||
"Show Powered By": "Afficher « Propulsé par »",
|
||||
"Domain Names": "Noms de domaine",
|
||||
signedInDisp: "Connecté en tant que {0}",
|
||||
signedInDispDisabled: "Authentification désactivée.",
|
||||
RadiusSecret: "Radius Secret",
|
||||
RadiusSecretDescription: "Secret partagé entre le client et le serveur",
|
||||
RadiusCalledStationId: "Identifiant de la station appelée",
|
||||
RadiusCalledStationIdDescription: "Identifiant de l'appareil appelé",
|
||||
@ -503,32 +526,155 @@ export default {
|
||||
RadiusCallingStationIdDescription: "Identifiant de l'appareil appelant",
|
||||
"Certificate Expiry Notification": "Notification d'expiration du certificat",
|
||||
"API Username": "Nom d'utilisateur de l'API",
|
||||
"API Key": "clé API",
|
||||
"API Key": "Clé API",
|
||||
"Recipient Number": "Numéro du destinataire",
|
||||
"From Name/Number": "De Nom/Numéro",
|
||||
"From Name/Number": "De nom/numéro",
|
||||
"Leave blank to use a shared sender number.": "Laisser vide pour utiliser un numéro d'expéditeur partagé.",
|
||||
"Octopush API Version": "Version de l'API Octopush",
|
||||
"Legacy Octopush-DM": "Ancien Octopush-DM",
|
||||
endpoint: "endpoint",
|
||||
octopushAPIKey: "\"Clé API\" à partir des informations d'identification de l'API HTTP dans le panneau de configuration",
|
||||
octopushLogin: "\"Connexion\" à partir des informations d'identification de l'API HTTP dans le panneau de configuration",
|
||||
octopushLogin: "\"Identifiant\" à partir des informations d'identification de l'API HTTP dans le panneau de configuration",
|
||||
promosmsLogin: "Nom de connexion API",
|
||||
promosmsPassword: "Mot de passe API",
|
||||
"pushoversounds pushover": "Pushover (par défaut)",
|
||||
"pushoversounds bike": "Vélo",
|
||||
"pushoversounds bugle": "Clairon",
|
||||
"pushoversounds cashregister": "Caisse enregistreuse",
|
||||
"pushoversounds classical": "Classique",
|
||||
"pushoversounds cosmic": "Cosmique",
|
||||
"pushoversounds falling": "Chute",
|
||||
"pushoversounds gamelan": "Gamelan",
|
||||
"pushoversounds incoming": "Arrivée",
|
||||
"pushoversounds intermission": "Intermission",
|
||||
"pushoversounds magic": "Magique",
|
||||
"pushoversounds mechanical": "Mécanique",
|
||||
"pushoversounds pianobar": "Piano-bar",
|
||||
"pushoversounds siren": "Sirène",
|
||||
"pushoversounds spacealarm": "Alarme spatiale",
|
||||
"pushoversounds tugboat": "Remorqueur",
|
||||
"pushoversounds alien": "Alarme alienne (version longue)",
|
||||
"pushoversounds climb": "Escalade (version longue)",
|
||||
"pushoversounds persistent": "Persistent (version longue)",
|
||||
"pushoversounds echo": "Pushover Echo (version longue)",
|
||||
"pushoversounds updown": "Up Down (version longue)",
|
||||
"pushoversounds vibrate": "Vibration seulement",
|
||||
"pushoversounds none": "Aucun (silencieux)",
|
||||
pushyAPIKey: "Clé API secrète",
|
||||
pushyToken: "Jeton d'appareil",
|
||||
"Show update if available": "Afficher la mise à jour si disponible",
|
||||
"Also check beta release": "Vérifiez également la version bêta",
|
||||
"Using a Reverse Proxy?": "Utiliser un proxy inverse ?",
|
||||
"Check how to config it for WebSocket": "Vérifiez comment le configurer pour WebSocket",
|
||||
"Check how to config it for WebSocket": "Vérifier comment le configurer pour WebSocket",
|
||||
"Steam Game Server": "Serveur de jeu Steam",
|
||||
"Most likely causes:": "Causes les plus probables : ",
|
||||
"The resource is no longer available.": "La ressource n'est plus disponible.",
|
||||
"There might be a typing error in the address.": "Il se peut qu'il y ait une erreur de frappe dans l'adresse.",
|
||||
"What you can try:": "Ce que vous pouvez essayer :",
|
||||
"Retype the address.": "Retaper l'adresse.",
|
||||
"Go back to the previous page.": "Retourner à la page précédente.",
|
||||
"Coming Soon": "Prochainement",
|
||||
wayToGetClickSendSMSToken: "Vous pouvez obtenir le nom d'utilisateur API et la clé API à partir de {0} .",
|
||||
"Connection String": "Chaîne de connexion",
|
||||
Query: "Requête",
|
||||
settingsCertificateExpiry: "Expiration du certificat TLS",
|
||||
certificationExpiryDescription: "Les sondes HTTPS émettent une notification lorsque le certificat TLS expire dans :",
|
||||
"Setup Docker Host": "Configurer l'hôte Docker",
|
||||
"Connection Type": "Type de connexion",
|
||||
"Docker Daemon": "Deamon Docker",
|
||||
deleteDockerHostMsg: "Voulez-vous vraiment supprimer cet hôte Docker pour toutes les sondes ?",
|
||||
socket: "Socket",
|
||||
tcp: "TCP / HTTP",
|
||||
"Docker Container": "Conteneur Docker",
|
||||
"Container Name / ID": "Nom / ID du conteneur",
|
||||
"Docker Host": "Hôte Docker",
|
||||
"Docker Hosts": "Hôtes Docker",
|
||||
"ntfy Topic": "Topic ntfy",
|
||||
Domain: "Domaine",
|
||||
Workstation: "Poste de travail",
|
||||
disableCloudflaredNoAuthMsg: "Vous êtes en mode No Auth, un mot de passe n'est pas nécessaire.",
|
||||
trustProxyDescription: "Faire confiance aux en-têtes 'X-Forwarded-*'. Si vous souhaitez obtenir la bonne adresse IP client et que votre Uptime Kuma se situe derrière (nginx ou Apache) vous devez l'activer.",
|
||||
wayToGetLineNotifyToken: "Vous pouvez obtenir un jeton d'accès auprès de {0}",
|
||||
Examples: "Exemples",
|
||||
"Home Assistant URL": "URL vers Home Assistant",
|
||||
"Long-Lived Access Token": "Jeton d'accès de longue durée",
|
||||
"Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "Un jeton d'accès de longue durée peut être créé en cliquant sur le nom de votre profil (en bas à gauche) et en faisant défiler vers le bas, puis cliquez sur Créer un jeton. ",
|
||||
"Notification Service": "Service de notifications",
|
||||
"default: notify all devices": "par défaut: notifier tous les appareils",
|
||||
"A list of Notification Services can be found in Home Assistant under \"Developer Tools > Services\" search for \"notification\" to find your device/phone name.": "Une liste des services de notification peut être trouvée dans Home Assistant sous \"Outils de développement > Services\" recherchez \"notification\" pour trouver le nom de votre appareil/téléphone.",
|
||||
"Automations can optionally be triggered in Home Assistant:": "Les automatisations peuvent éventuellement être déclenchées dans Home Assistant : ",
|
||||
"Trigger type:": "Type de déclencheur : ",
|
||||
"Event type:": "Type d'événement : ",
|
||||
"Event data:": "Données d'événement : ",
|
||||
"Then choose an action, for example switch the scene to where an RGB light is red.": "Ensuite, choisissez une action, par exemple basculer la scène là où une lumière RVB est rouge.",
|
||||
"Frontend Version": "Frontend Version",
|
||||
"Frontend Version do not match backend version!": "La version frontale ne correspond pas à la version principale !",
|
||||
"Frontend Version": "Version frontend",
|
||||
"Frontend Version do not match backend version!": "La version frontend ne correspond pas à la version backend !",
|
||||
"Base URL": "URL de base",
|
||||
goAlertInfo: "GoAlert est une application open source pour la planification des appels, les escalades automatisées et les notifications (comme les SMS ou les appels vocaux). Engagez automatiquement la bonne personne, de la bonne manière et au bon moment ! {0}",
|
||||
goAlertInfo: "GoAlert est une application open source pour la planification des appels, les escalades automatisées et les notifications (comme les SMS ou les appels vocaux). Impliquez automatiquement la bonne personne, de la bonne manière et au bon moment ! {0}",
|
||||
goAlertIntegrationKeyInfo: "Obtenez la clé d'intégration d'API générique pour le service dans ce format \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\" généralement la valeur du paramètre de jeton de l'URL copiée.",
|
||||
goAlert: "GoAlert",
|
||||
backupOutdatedWarning: "Obsolète : étant donné que de nombreuses fonctionnalités ont été ajoutées et que cette fonctionnalité de sauvegarde est un peu non maintenue, elle ne peut pas générer ou restaurer une sauvegarde complète.",
|
||||
backupOutdatedWarning: "Obsolète : étant donné que de nombreuses fonctionnalités ont été ajoutées et que cette fonctionnalité de sauvegarde est non maintenue, elle ne peut pas générer ou restaurer une sauvegarde complète.",
|
||||
backupRecommend: "Veuillez sauvegarder le volume ou le dossier de données (./data/) directement à la place.",
|
||||
Optional: "Optionnel",
|
||||
squadcast: "Squadcast",
|
||||
SendKey: "SendKey",
|
||||
"SMSManager API Docs": "Documentations de l'API SMSManager ",
|
||||
"Gateway Type": "Type de passerelle",
|
||||
SMSManager: "SMSManager",
|
||||
"You can divide numbers with": "Vous pouvez diviser des nombres avec",
|
||||
or: "ou",
|
||||
recurringInterval: "Intervalle",
|
||||
Recurring: "Récurrent",
|
||||
strategyManual: "Activer/désactiver manuellement",
|
||||
warningTimezone: "Utilisation du fuseau horaire du serveur",
|
||||
weekdayShortMon: "Lun",
|
||||
weekdayShortTue: "Mar",
|
||||
weekdayShortWed: "Mer",
|
||||
weekdayShortThu: "Jeu",
|
||||
weekdayShortFri: "Ven",
|
||||
weekdayShortSat: "Sam",
|
||||
weekdayShortSun: "Dim",
|
||||
dayOfWeek: "Jour de la semaine",
|
||||
dayOfMonth: "Jour du mois",
|
||||
lastDay: "Dernier jour",
|
||||
lastDay1: "Dernier jour du mois",
|
||||
lastDay2: "Avant-dernier jour du mois",
|
||||
lastDay3: "3ème dernier jour du mois",
|
||||
lastDay4: "4ème dernier jour du mois",
|
||||
"No Maintenance": "Aucune maintenance",
|
||||
pauseMaintenanceMsg: "Voulez-vous vraiment mettre en pause ?",
|
||||
"maintenanceStatus-under-maintenance": "En maintenance",
|
||||
"maintenanceStatus-inactive": "Inactif",
|
||||
"maintenanceStatus-scheduled": "Programmé",
|
||||
"maintenanceStatus-ended": "Terminé",
|
||||
"maintenanceStatus-unknown": "Inconnue",
|
||||
"Display Timezone": "Afficher le fuseau horaire",
|
||||
"Server Timezone": "Fuseau horaire du serveur",
|
||||
statusPageMaintenanceEndDate: "Fin",
|
||||
IconUrl: "URL vers l'icône",
|
||||
"Enable DNS Cache": "Activer le cache DNS",
|
||||
Enable: "Activer",
|
||||
Disable: "Désactiver",
|
||||
dnsCacheDescription: "Il peut ne pas fonctionner dans certains environnements IPv6, désactivez-le si vous rencontrez des problèmes.",
|
||||
"Single Maintenance Window": "Créneau de maintenance unique",
|
||||
"Maintenance Time Window of a Day": "Créneau de la maintenance",
|
||||
"Effective Date Range": "Plage de dates d'effet",
|
||||
"Schedule Maintenance": "Créer une maintenance",
|
||||
"Date and Time": "Date et heure",
|
||||
"DateTime Range": "Plage de dates et d'heures",
|
||||
Strategy: "Stratégie",
|
||||
"Free Mobile User Identifier": "Identifiant d'utilisateur Free Mobile",
|
||||
"Free Mobile API Key": "Clé d'API Free Mobile",
|
||||
"Enable TLS": "Activer le TLS",
|
||||
"Proto Service Name": "Nom du service proto",
|
||||
"Proto Method": "Méthode Proto",
|
||||
"Proto Content": "Contenu proto",
|
||||
"Economy": "Économique",
|
||||
"Lowcost": "Faible coût",
|
||||
"high": "Haute",
|
||||
"General Monitor Type": "Type de sonde générale",
|
||||
"Passive Monitor Type": "Type de sonde passive",
|
||||
"Specific Monitor Type": "Type de sonde spécifique",
|
||||
dataRetentionTimeError: "La durée de conservation doit être supérieure ou égale à 0",
|
||||
infiniteRetention: "Définissez la valeur à 0 pour une durée de conservation infinie.",
|
||||
};
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user