mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-09-10 12:56:13 +08:00
Compare commits
287 Commits
1.21.0
...
1.22.0-bet
Author | SHA1 | Date | |
---|---|---|---|
|
71c34694b7 | ||
|
2128ed5ce3 | ||
|
09ab6a015b | ||
|
ec858eb67a | ||
|
c4c3fc81b2 | ||
|
8aa577529f | ||
|
cdd5067b17 | ||
|
5fdb01308a | ||
|
eb1a1d0ac7 | ||
|
a1fc283b3c | ||
|
419b684433 | ||
|
8bc139d8c1 | ||
|
91dfd8dfaa | ||
|
1f405cf2a0 | ||
|
cf61077dd8 | ||
|
4396e0d4d8 | ||
|
240db1d173 | ||
|
ef06b5376d | ||
|
186d733134 | ||
|
225ba61e22 | ||
|
11b32ce553 | ||
|
3707919025 | ||
|
d10e378fb1 | ||
|
370328c3b8 | ||
|
1d8a82ae3e | ||
|
7da336e975 | ||
|
b8c12cca2a | ||
|
88fcfcc6fc | ||
|
fcb22f7d05 | ||
|
5be41990bc | ||
|
09149f50e0 | ||
|
8f60274582 | ||
|
7dadac3ebe | ||
|
28c29f755d | ||
|
b426840b5b | ||
|
4f2d39d5fc | ||
|
4012fc6964 | ||
|
d1b52bc098 | ||
|
c9a32f9dbb | ||
|
b884f82de6 | ||
|
65928a26c7 | ||
|
abe00efa7f | ||
|
0c364fc288 | ||
|
0d21529037 | ||
|
fea8ef8367 | ||
|
37031fb9a7 | ||
|
58ec53fb1d | ||
|
57190b58c6 | ||
|
6c2948d2de | ||
|
68f389868c | ||
|
af5d7cbb0b | ||
|
56f448bfe5 | ||
|
2b46da0f47 | ||
|
9bd76c2795 | ||
|
4b3a2ee71b | ||
|
1634df5a39 | ||
|
039fdb0730 | ||
|
20af2d9d95 | ||
|
04806ba4f3 | ||
|
3ff910a8f8 | ||
|
343a1d3344 | ||
|
f1c184c30c | ||
|
f3c09f2bbd | ||
|
85eb084305 | ||
|
0735f12d19 | ||
|
8ed2b59410 | ||
|
0b8dddba24 | ||
|
2114295381 | ||
|
bc95875aa0 | ||
|
c1efe0f26d | ||
|
a0d0d5b015 | ||
|
8d05d80a5f | ||
|
36942de329 | ||
|
771ca09331 | ||
|
3cb287a40e | ||
|
83a59bd984 | ||
|
446b5fa9e4 | ||
|
0d1b5321ad | ||
|
1e1cc86a10 | ||
|
9dc02bb8e2 | ||
|
bb15fa0179 | ||
|
966066b897 | ||
|
8d24891b8e | ||
|
ba7de3fd37 | ||
|
80c8fd7372 | ||
|
a27386bb92 | ||
|
ce70b3fc62 | ||
|
06fba5b55a | ||
|
f2c294e9e5 | ||
|
332e54937e | ||
|
a1adc30a89 | ||
|
6ce882ad4a | ||
|
e392d12585 | ||
|
253214ad2b | ||
|
33de7bdb1c | ||
|
7f5d0e5490 | ||
|
1a344c1371 | ||
|
28b0f8fc00 | ||
|
0eaaa8b6fa | ||
|
5cd506e340 | ||
|
f0beccf6bf | ||
|
72c16c3aa2 | ||
|
aa8454b73f | ||
|
d23cb0b382 | ||
|
9975050872 | ||
|
f8c2909576 | ||
|
fcfe13e52d | ||
|
9f51115a19 | ||
|
4057ca6e72 | ||
|
8a3bce44ef | ||
|
dfe6f52f6a | ||
|
333a631389 | ||
|
eaa948579b | ||
|
74dd07c3ca | ||
|
f75cf3a186 | ||
|
a3e31b22bc | ||
|
078d1f96a5 | ||
|
8207f16396 | ||
|
ba82abe5f3 | ||
|
eb9c748071 | ||
|
3579520575 | ||
|
030faddd1c | ||
|
0e516a42e5 | ||
|
680dccefea | ||
|
8c9423f4de | ||
|
f433f33418 | ||
|
d4a31cf02a | ||
|
a7588adc52 | ||
|
6356b1e50a | ||
|
af6e01ee3a | ||
|
11f4cb8725 | ||
|
1bf97e701d | ||
|
4c1ac5e870 | ||
|
9e320dc5fb | ||
|
2f3f929fbd | ||
|
b776e88b26 | ||
|
49741bbef2 | ||
|
1f7f1f70bf | ||
|
be7d3f6142 | ||
|
7706c29564 | ||
|
9dd1b1ca0f | ||
|
21ad715e6a | ||
|
23af66f618 | ||
|
03aa685d3f | ||
|
84c1baf706 | ||
|
23808efe2a | ||
|
1db25a329f | ||
|
e314d517ad | ||
|
84d1cb73b6 | ||
|
ddd3d3bc92 | ||
|
190e85d2c8 | ||
|
d8511fa201 | ||
|
e76d29dee5 | ||
|
fb38048159 | ||
|
80f1959871 | ||
|
4ddc3b5f5e | ||
|
d173a3c663 | ||
|
45ef7b2f69 | ||
|
6b078b83bd | ||
|
22f730499f | ||
|
1be74e2720 | ||
|
32f84b5e4e | ||
|
97c7ad9cc7 | ||
|
8f449ab738 | ||
|
dbfaddafca | ||
|
511038b45a | ||
|
e8d48561fc | ||
|
aeb2feacd3 | ||
|
c01055efb3 | ||
|
17ae47d091 | ||
|
de0d1edfd4 | ||
|
0f5a243450 | ||
|
b975c24531 | ||
|
524cf7c607 | ||
|
a6acd065bb | ||
|
227cec86a8 | ||
|
ba52e1c885 | ||
|
02291730fe | ||
|
dcc065c86f | ||
|
501dc29e6d | ||
|
d2b09ef042 | ||
|
f608590526 | ||
|
a7f21bffec | ||
|
ebd42444d1 | ||
|
f0f7645c57 | ||
|
3ada6fa99b | ||
|
4fb10a4e3f | ||
|
1aac15bccc | ||
|
cbcd2a1027 | ||
|
17f038767d | ||
|
edd1c6b662 | ||
|
29be8d3ddb | ||
|
2ac1abd424 | ||
|
738a494dcb | ||
|
e575d41f7d | ||
|
8ee4b844fd | ||
|
4ae437dd61 | ||
|
6cb296b07a | ||
|
644c6a872f | ||
|
8c69c18f6d | ||
|
c1a1160767 | ||
|
a0ebd88849 | ||
|
2e9413cf33 | ||
|
278b52ec34 | ||
|
caa757a27c | ||
|
3ce117a943 | ||
|
cefe484b47 | ||
|
a700892709 | ||
|
13d721ccf8 | ||
|
6c66bff518 | ||
|
bea51d048b | ||
|
2e1a0fe4d5 | ||
|
27b0895722 | ||
|
e687698851 | ||
|
fc4312ca1a | ||
|
fbdeb30ce7 | ||
|
41bda4e1d7 | ||
|
4869e6531c | ||
|
302b9cf644 | ||
|
3c3a192943 | ||
|
b64c835cee | ||
|
5266e713e6 | ||
|
86579d245f | ||
|
b6169408be | ||
|
4f05912276 | ||
|
bf525371d9 | ||
|
89bfc3bf33 | ||
|
a2014278b8 | ||
|
70572af1af | ||
|
b31c23a43b | ||
|
f4ee5271af | ||
|
7330db3563 | ||
|
097567e5f0 | ||
|
35f300c8eb | ||
|
4c9d7ac8ca | ||
|
ca52047bf5 | ||
|
d9558833fc | ||
|
776a482a1d | ||
|
d2527d7254 | ||
|
6dfca0c163 | ||
|
df47609671 | ||
|
e63f7562f8 | ||
|
8921ed0cff | ||
|
35a56dd9e0 | ||
|
442f54de84 | ||
|
cf59832d51 | ||
|
8f259e1756 | ||
|
29b2809279 | ||
|
16f2701f61 | ||
|
3bbf269da0 | ||
|
56d716cee4 | ||
|
391692a708 | ||
|
f32fcb204f | ||
|
e8814e8479 | ||
|
193a273557 | ||
|
150607cc93 | ||
|
cbbd3e20ad | ||
|
beb22f743d | ||
|
6fc34e44d9 | ||
|
7b4f90ce92 | ||
|
db6b863445 | ||
|
186ca30508 | ||
|
896e33815d | ||
|
0be8b111e2 | ||
|
cef0a0faf4 | ||
|
dfb95dfdcb | ||
|
e10ba9ed7e | ||
|
9446c2d102 | ||
|
2c581ade90 | ||
|
f286386f59 | ||
|
9286dcb6ce | ||
|
a6894d36f2 | ||
|
66573934f6 | ||
|
c444d78706 | ||
|
661fa87134 | ||
|
d48eb24046 | ||
|
aee4c22dee | ||
|
9a46b50989 | ||
|
faf3488b1e | ||
|
f3ac351d75 | ||
|
aba515e172 | ||
|
97bd306a09 | ||
|
645fd94bba | ||
|
71f00b3690 | ||
|
a21a47de93 | ||
|
f6d0f28b3a | ||
|
9404efd86d |
6
.github/ISSUE_TEMPLATE/ask-for-help.yaml
vendored
6
.github/ISSUE_TEMPLATE/ask-for-help.yaml
vendored
@@ -26,6 +26,12 @@ body:
|
||||
label: "📝 Describe your problem"
|
||||
description: "Please walk us through it step by step."
|
||||
placeholder: "Describe what are you asking for..."
|
||||
- type: textarea
|
||||
id: error-msg
|
||||
validations:
|
||||
required: false
|
||||
attributes:
|
||||
label: "📝 Error Message(s) or Log"
|
||||
- type: input
|
||||
id: uptime-kuma-version
|
||||
attributes:
|
||||
|
4
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
4
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@@ -61,8 +61,8 @@ body:
|
||||
id: operating-system
|
||||
attributes:
|
||||
label: "💻 Operating System and Arch"
|
||||
description: "Which OS is your server/device running on?"
|
||||
placeholder: "Ex. Ubuntu 20.04 x86"
|
||||
description: "Which OS is your server/device running on? (For Replit, please do not report this bug)"
|
||||
placeholder: "Ex. Ubuntu 20.04 x64 "
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
|
1
.github/config/exclude.txt
vendored
Normal file
1
.github/config/exclude.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
# This is a .gitignore style file for 'GrantBirki/json-yaml-validate' Action workflow
|
2
.github/workflows/auto-test.yml
vendored
2
.github/workflows/auto-test.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
node: [ 14, 16, 18, 19 ]
|
||||
node: [ 14, 16, 18, 20 ]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
|
26
.github/workflows/json-yaml-validate.yml
vendored
Normal file
26
.github/workflows/json-yaml-validate.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: json-yaml-validate
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write # enable write permissions for pull request comments
|
||||
|
||||
jobs:
|
||||
json-yaml-validate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: json-yaml-validate
|
||||
id: json-yaml-validate
|
||||
uses: GrantBirki/json-yaml-validate@v1.3.0
|
||||
with:
|
||||
comment: "true" # enable comment mode
|
||||
exclude_file: ".github/config/exclude.txt" # gitignore style file for exclusions
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -23,3 +23,6 @@ cypress/screenshots
|
||||
|
||||
extra/exe-builder/bin
|
||||
extra/exe-builder/obj
|
||||
|
||||
.vs
|
||||
.vscode
|
||||
|
23
README.md
23
README.md
@@ -49,8 +49,12 @@ Uptime Kuma is now running on http://localhost:3001
|
||||
|
||||
### 💪🏻 Non-Docker
|
||||
|
||||
Required Tools:
|
||||
- [Node.js](https://nodejs.org/en/download/) >= 14
|
||||
Requirements:
|
||||
- Platform
|
||||
- ✅ Major Linux distros such as Debian, Ubuntu, CentOS, Fedora and ArchLinux etc.
|
||||
- ✅ Windows 10 (x64), Windows Server 2012 R2 (x64) or higher
|
||||
- ❌ Replit / Heroku
|
||||
- [Node.js](https://nodejs.org/en/download/) 14 / 16 / 18 (20 is not supported)
|
||||
- [npm](https://docs.npmjs.com/cli/) >= 7
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
- [pm2](https://pm2.keymetrics.io/) - For running Uptime Kuma in the background
|
||||
@@ -87,6 +91,10 @@ pm2 monit
|
||||
pm2 save && pm2 startup
|
||||
```
|
||||
|
||||
### Windows Portable (x64)
|
||||
|
||||
https://github.com/louislam/uptime-kuma/releases/download/1.21.0/uptime-kuma-win64-portable-1.0.0.zip
|
||||
|
||||
### Advanced Installation
|
||||
|
||||
If you need more options or need to browse via a reverse proxy, please read:
|
||||
@@ -144,17 +152,18 @@ Telegram Notification Sample:
|
||||
|
||||
If you love this project, please consider giving me a ⭐.
|
||||
|
||||
## 🗣️ Discussion
|
||||
## 🗣️ Discussion / Ask for Help
|
||||
|
||||
### Issues Page
|
||||
⚠️ For any general or technical questions, please don't send me an email, as I am unable to provide support in that manner. I will not response if you asked such questions.
|
||||
|
||||
You can discuss or ask for help in [issues](https://github.com/louislam/uptime-kuma/issues).
|
||||
I recommend using Google, GitHub Issues, or Uptime Kuma's Subreddit for finding answers to your question. If you cannot find the information you need, feel free to ask:
|
||||
|
||||
### Subreddit
|
||||
- [GitHub Issues](https://github.com/louislam/uptime-kuma/issues)
|
||||
- [Subreddit r/Uptime kuma](https://www.reddit.com/r/UptimeKuma/)
|
||||
|
||||
My Reddit account: [u/louislamlam](https://reddit.com/u/louislamlam).
|
||||
You can mention me if you ask a question on Reddit.
|
||||
[r/Uptime kuma](https://www.reddit.com/r/UptimeKuma/)
|
||||
|
||||
|
||||
## Contribute
|
||||
|
||||
|
6
db/patch-add-parent-monitor.sql
Normal file
6
db/patch-add-parent-monitor.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
ALTER TABLE monitor
|
||||
ADD parent INTEGER REFERENCES [monitor] ([id]) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
COMMIT
|
11
db/patch-maintenance-cron.sql
Normal file
11
db/patch-maintenance-cron.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
DROP TABLE maintenance_timeslot;
|
||||
|
||||
-- 999 characters. https://stackoverflow.com/questions/46134830/maximum-length-for-cron-job
|
||||
ALTER TABLE maintenance ADD cron TEXT;
|
||||
ALTER TABLE maintenance ADD timezone VARCHAR(255);
|
||||
ALTER TABLE maintenance ADD duration INTEGER;
|
||||
|
||||
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 git && \
|
||||
pip3 --no-cache-dir install apprise==1.3.0 && \
|
||||
pip3 --no-cache-dir install apprise==1.4.0 && \
|
||||
rm -rf /root/.cache
|
||||
|
@@ -8,21 +8,21 @@ WORKDIR /app
|
||||
# Install Curl
|
||||
# Install Apprise, add sqlite3 cli for debugging in the future, iputils-ping for ping, util-linux for setpriv
|
||||
# Stupid python3 and python3-pip actually install a lot of useless things into Debian, specify --no-install-recommends to skip them, make the base even smaller than alpine!
|
||||
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 git && \
|
||||
pip3 --no-cache-dir install apprise==1.3.0 && \
|
||||
RUN apt-get update && \
|
||||
apt-get --yes --no-install-recommends install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \
|
||||
sqlite3 iputils-ping util-linux dumb-init git curl ca-certificates && \
|
||||
pip3 --no-cache-dir install apprise==1.4.0 && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
apt --yes autoremove
|
||||
|
||||
# Install cloudflared
|
||||
# dpkg --add-architecture arm: cloudflared do not provide armhf, this is workaround. Read more: https://github.com/cloudflare/cloudflared/issues/583
|
||||
COPY extra/download-cloudflared.js ./extra/download-cloudflared.js
|
||||
RUN node ./extra/download-cloudflared.js $TARGETPLATFORM && \
|
||||
dpkg --add-architecture arm && \
|
||||
apt update && \
|
||||
apt --yes --no-install-recommends install ./cloudflared.deb && \
|
||||
RUN set -eux && \
|
||||
mkdir -p --mode=0755 /usr/share/keyrings && \
|
||||
curl --fail --show-error --silent --location --insecure https://pkg.cloudflare.com/cloudflare-main.gpg --output /usr/share/keyrings/cloudflare-main.gpg && \
|
||||
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared buster main' | tee /etc/apt/sources.list.d/cloudflared.list && \
|
||||
apt-get update && \
|
||||
apt-get install --yes --no-install-recommends cloudflared && \
|
||||
cloudflared version && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
rm -f cloudflared.deb && \
|
||||
apt --yes autoremove
|
||||
|
||||
|
@@ -1,48 +0,0 @@
|
||||
//
|
||||
|
||||
const http = require("https"); // or 'https' for https:// URLs
|
||||
const fs = require("fs");
|
||||
|
||||
const platform = process.argv[2];
|
||||
|
||||
if (!platform) {
|
||||
console.error("No platform??");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let arch = null;
|
||||
|
||||
if (platform === "linux/amd64") {
|
||||
arch = "amd64";
|
||||
} else if (platform === "linux/arm64") {
|
||||
arch = "arm64";
|
||||
} else if (platform === "linux/arm/v7") {
|
||||
arch = "arm";
|
||||
} else {
|
||||
console.error("Invalid platform?? " + platform);
|
||||
}
|
||||
|
||||
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) {
|
||||
console.log("Redirect to " + res.headers.location);
|
||||
get(res.headers.location);
|
||||
} else if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||
res.pipe(file);
|
||||
|
||||
res.on("end", function () {
|
||||
console.log("Downloaded");
|
||||
});
|
||||
} else {
|
||||
console.error(res.statusCode);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
}
|
@@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
|
19369
package-lock.json
generated
19369
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
28
package.json
28
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "uptime-kuma",
|
||||
"version": "1.21.0",
|
||||
"version": "1.22.0-beta.0",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -39,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.21.0 && npm ci --production && npm run download-dist",
|
||||
"setup": "git checkout 1.21.3 && 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",
|
||||
@@ -64,19 +64,18 @@
|
||||
"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",
|
||||
"depoly-demo-server": "node extra/deploy-demo-server.js",
|
||||
"deploy-demo-server": "node extra/deploy-demo-server.js",
|
||||
"sort-contributors": "node extra/sort-contributors.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@grpc/grpc-js": "~1.7.3",
|
||||
"@louislam/ping": "~0.4.4-mod.0",
|
||||
"@louislam/sqlite3": "15.1.2",
|
||||
"@louislam/sqlite3": "15.1.6",
|
||||
"args-parser": "~1.3.0",
|
||||
"axios": "~0.27.0",
|
||||
"axios-ntlm": "1.3.0",
|
||||
"badge-maker": "~3.3.1",
|
||||
"bcryptjs": "~2.4.3",
|
||||
"bree": "~7.1.5",
|
||||
"cacheable-lookup": "~6.0.4",
|
||||
"chardet": "~1.4.0",
|
||||
"check-password-strength": "^2.0.5",
|
||||
@@ -85,6 +84,7 @@
|
||||
"command-exists": "~1.2.9",
|
||||
"compare-versions": "~3.6.0",
|
||||
"compression": "~1.7.4",
|
||||
"croner": "^6.0.3",
|
||||
"dayjs": "~1.11.5",
|
||||
"dotenv": "~16.0.3",
|
||||
"express": "~4.17.3",
|
||||
@@ -118,8 +118,8 @@
|
||||
"qs": "~6.10.4",
|
||||
"redbean-node": "~0.2.0",
|
||||
"redis": "~4.5.1",
|
||||
"socket.io": "~4.5.3",
|
||||
"socket.io-client": "~4.5.3",
|
||||
"socket.io": "~4.6.1",
|
||||
"socket.io-client": "~4.6.1",
|
||||
"socks-proxy-agent": "6.1.1",
|
||||
"tar": "~6.1.11",
|
||||
"tcp-ping": "~0.1.1",
|
||||
@@ -142,10 +142,11 @@
|
||||
"aedes": "^0.46.3",
|
||||
"babel-plugin-rewire": "~1.2.0",
|
||||
"bootstrap": "5.1.3",
|
||||
"chart.js": "~3.6.2",
|
||||
"chartjs-adapter-dayjs": "~1.0.0",
|
||||
"chart.js": "~4.2.1",
|
||||
"chartjs-adapter-dayjs-4": "~1.0.4",
|
||||
"concurrently": "^7.1.0",
|
||||
"core-js": "~3.26.1",
|
||||
"cronstrue": "~2.24.0",
|
||||
"cross-env": "~7.0.3",
|
||||
"cypress": "^10.1.0",
|
||||
"delay": "^5.0.0",
|
||||
@@ -170,10 +171,10 @@
|
||||
"timezones-list": "~3.0.1",
|
||||
"typescript": "~4.4.4",
|
||||
"v-pagination-3": "~0.1.7",
|
||||
"vite": "~3.1.0",
|
||||
"vite": "~3.2.7",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vue": "next",
|
||||
"vue-chart-3": "3.0.9",
|
||||
"vue": "~3.2.47",
|
||||
"vue-chartjs": "~5.2.0",
|
||||
"vue-confirm-dialog": "~1.0.2",
|
||||
"vue-contenteditable": "~3.0.4",
|
||||
"vue-i18n": "~9.2.2",
|
||||
@@ -184,6 +185,7 @@
|
||||
"vue-router": "~4.0.14",
|
||||
"vue-toastification": "~2.0.0-rc.5",
|
||||
"vuedraggable": "~4.1.0",
|
||||
"wait-on": "^6.0.1"
|
||||
"wait-on": "^6.0.1",
|
||||
"whatwg-url": "~12.0.1"
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@ const fs = require("fs");
|
||||
const { R } = require("redbean-node");
|
||||
const { setSetting, setting } = require("./util-server");
|
||||
const { log, sleep } = require("../src/util");
|
||||
const dayjs = require("dayjs");
|
||||
const knex = require("knex");
|
||||
const { PluginsManager } = require("./plugins-manager");
|
||||
|
||||
@@ -30,11 +29,6 @@ class Database {
|
||||
*/
|
||||
static patched = false;
|
||||
|
||||
/**
|
||||
* For Backup only
|
||||
*/
|
||||
static backupPath = null;
|
||||
|
||||
/**
|
||||
* Add patch filename in key
|
||||
* Values:
|
||||
@@ -74,6 +68,8 @@ class Database {
|
||||
"patch-add-description-monitor.sql": true,
|
||||
"patch-api-key-table.sql": true,
|
||||
"patch-monitor-tls.sql": true,
|
||||
"patch-maintenance-cron.sql": true,
|
||||
"patch-add-parent-monitor.sql": true,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -198,15 +194,7 @@ class Database {
|
||||
} else {
|
||||
log.info("db", "Database patch is needed");
|
||||
|
||||
try {
|
||||
this.backup(version);
|
||||
} catch (e) {
|
||||
log.error("db", e);
|
||||
log.error("db", "Unable to create a backup before patching the database. Please make sure you have enough space and permission.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Try catch anything here, if gone wrong, restore the backup
|
||||
// Try catch anything here
|
||||
try {
|
||||
for (let i = version + 1; i <= this.latestVersion; i++) {
|
||||
const sqlFile = `./db/patch${i}.sql`;
|
||||
@@ -222,7 +210,6 @@ class Database {
|
||||
log.error("db", "Start Uptime-Kuma failed due to issue patching the database");
|
||||
log.error("db", "Please submit a bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues");
|
||||
|
||||
this.restore();
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -264,8 +251,6 @@ class Database {
|
||||
log.error("db", "Start Uptime-Kuma failed due to issue patching the database");
|
||||
log.error("db", "Please submit the bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues");
|
||||
|
||||
this.restore();
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -367,8 +352,6 @@ class Database {
|
||||
}
|
||||
}
|
||||
|
||||
this.backup(dayjs().format("YYYYMMDDHHmmss"));
|
||||
|
||||
log.info("db", sqlFilename + " is patching");
|
||||
this.patched = true;
|
||||
await this.importSQLFile("./db/" + sqlFilename);
|
||||
@@ -434,6 +417,9 @@ class Database {
|
||||
|
||||
log.info("db", "Closing the database");
|
||||
|
||||
// Flush WAL to main database
|
||||
await R.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
||||
|
||||
while (true) {
|
||||
Database.noReject = true;
|
||||
await R.close();
|
||||
@@ -450,100 +436,6 @@ class Database {
|
||||
process.removeListener("unhandledRejection", listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* One backup one time in this process.
|
||||
* Reset this.backupPath if you want to backup again
|
||||
* @param {string} version Version code of backup
|
||||
*/
|
||||
static backup(version) {
|
||||
if (! this.backupPath) {
|
||||
log.info("db", "Backing up the database");
|
||||
this.backupPath = this.dataDir + "kuma.db.bak" + version;
|
||||
fs.copyFileSync(Database.path, this.backupPath);
|
||||
|
||||
const shmPath = Database.path + "-shm";
|
||||
if (fs.existsSync(shmPath)) {
|
||||
this.backupShmPath = shmPath + ".bak" + version;
|
||||
fs.copyFileSync(shmPath, this.backupShmPath);
|
||||
}
|
||||
|
||||
const walPath = Database.path + "-wal";
|
||||
if (fs.existsSync(walPath)) {
|
||||
this.backupWalPath = walPath + ".bak" + version;
|
||||
fs.copyFileSync(walPath, this.backupWalPath);
|
||||
}
|
||||
|
||||
// Double confirm if all files actually backup
|
||||
if (!fs.existsSync(this.backupPath)) {
|
||||
throw new Error("Backup failed! " + this.backupPath);
|
||||
}
|
||||
|
||||
if (fs.existsSync(shmPath)) {
|
||||
if (!fs.existsSync(this.backupShmPath)) {
|
||||
throw new Error("Backup failed! " + this.backupShmPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (fs.existsSync(walPath)) {
|
||||
if (!fs.existsSync(this.backupWalPath)) {
|
||||
throw new Error("Backup failed! " + this.backupWalPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Restore from most recent backup */
|
||||
static restore() {
|
||||
if (this.backupPath) {
|
||||
log.error("db", "Patching the database failed!!! Restoring the backup");
|
||||
|
||||
const shmPath = Database.path + "-shm";
|
||||
const walPath = Database.path + "-wal";
|
||||
|
||||
// Make sure we have a backup to restore before deleting old db
|
||||
if (
|
||||
!fs.existsSync(this.backupPath)
|
||||
&& !fs.existsSync(shmPath)
|
||||
&& !fs.existsSync(walPath)
|
||||
) {
|
||||
log.error("db", "Backup file not found! Leaving database in failed state.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Delete patch failed db
|
||||
try {
|
||||
if (fs.existsSync(Database.path)) {
|
||||
fs.unlinkSync(Database.path);
|
||||
}
|
||||
|
||||
if (fs.existsSync(shmPath)) {
|
||||
fs.unlinkSync(shmPath);
|
||||
}
|
||||
|
||||
if (fs.existsSync(walPath)) {
|
||||
fs.unlinkSync(walPath);
|
||||
}
|
||||
} catch (e) {
|
||||
log.error("db", "Restore failed; you may need to restore the backup manually");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Restore backup
|
||||
fs.copyFileSync(this.backupPath, Database.path);
|
||||
|
||||
if (this.backupShmPath) {
|
||||
fs.copyFileSync(this.backupShmPath, shmPath);
|
||||
}
|
||||
|
||||
if (this.backupWalPath) {
|
||||
fs.copyFileSync(this.backupWalPath, walPath);
|
||||
}
|
||||
|
||||
} else {
|
||||
log.info("db", "Nothing to restore");
|
||||
}
|
||||
}
|
||||
|
||||
/** Get the size of the database */
|
||||
static getSize() {
|
||||
log.debug("db", "Database.getSize()");
|
||||
|
@@ -1,41 +1,44 @@
|
||||
const path = require("path");
|
||||
const Bree = require("bree");
|
||||
const { SHARE_ENV } = require("worker_threads");
|
||||
const { log } = require("../src/util");
|
||||
let bree;
|
||||
const { UptimeKumaServer } = require("./uptime-kuma-server");
|
||||
const { clearOldData } = require("./jobs/clear-old-data");
|
||||
const Cron = require("croner");
|
||||
|
||||
const jobs = [
|
||||
{
|
||||
name: "clear-old-data",
|
||||
interval: "at 03:14",
|
||||
interval: "14 03 * * *",
|
||||
jobFunc: clearOldData,
|
||||
croner: null,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Initialize background jobs
|
||||
* @param {Object} args Arguments to pass to workers
|
||||
* @returns {Bree}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const initBackgroundJobs = function (args) {
|
||||
bree = new Bree({
|
||||
root: path.resolve("server", "jobs"),
|
||||
jobs,
|
||||
worker: {
|
||||
env: SHARE_ENV,
|
||||
workerData: args,
|
||||
},
|
||||
workerMessageHandler: (message) => {
|
||||
log.info("jobs", message);
|
||||
}
|
||||
});
|
||||
const initBackgroundJobs = async function () {
|
||||
const timezone = await UptimeKumaServer.getInstance().getTimezone();
|
||||
|
||||
for (const job of jobs) {
|
||||
const cornerJob = new Cron(
|
||||
job.interval,
|
||||
{
|
||||
name: job.name,
|
||||
timezone,
|
||||
},
|
||||
job.jobFunc,
|
||||
);
|
||||
job.croner = cornerJob;
|
||||
}
|
||||
|
||||
bree.start();
|
||||
return bree;
|
||||
};
|
||||
|
||||
/** Stop all background jobs if running */
|
||||
const stopBackgroundJobs = function () {
|
||||
if (bree) {
|
||||
bree.stop();
|
||||
for (const job of jobs) {
|
||||
if (job.croner) {
|
||||
job.croner.stop();
|
||||
job.croner = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -1,12 +1,15 @@
|
||||
const { log, exit, connectDb } = require("./util-worker");
|
||||
const { R } = require("redbean-node");
|
||||
const { log } = require("../../src/util");
|
||||
const { setSetting, setting } = require("../util-server");
|
||||
|
||||
const DEFAULT_KEEP_PERIOD = 180;
|
||||
|
||||
(async () => {
|
||||
await connectDb();
|
||||
/**
|
||||
* Clears old data from the heartbeat table of the database.
|
||||
* @return {Promise<void>} A promise that resolves when the data has been cleared.
|
||||
*/
|
||||
|
||||
const clearOldData = async () => {
|
||||
let period = await setting("keepDataPeriodDays");
|
||||
|
||||
// Set Default Period
|
||||
@@ -20,16 +23,16 @@ const DEFAULT_KEEP_PERIOD = 180;
|
||||
try {
|
||||
parsedPeriod = parseInt(period);
|
||||
} catch (_) {
|
||||
log("Failed to parse setting, resetting to default..");
|
||||
log.warn("clearOldData", "Failed to parse setting, resetting to default..");
|
||||
await setSetting("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general");
|
||||
parsedPeriod = DEFAULT_KEEP_PERIOD;
|
||||
}
|
||||
|
||||
if (parsedPeriod < 1) {
|
||||
log(`Data deletion has been disabled as period is less than 1. Period is ${parsedPeriod} days.`);
|
||||
log.info("clearOldData", `Data deletion has been disabled as period is less than 1. Period is ${parsedPeriod} days.`);
|
||||
} else {
|
||||
|
||||
log(`Clearing Data older than ${parsedPeriod} days...`);
|
||||
log.debug("clearOldData", `Clearing Data older than ${parsedPeriod} days...`);
|
||||
|
||||
try {
|
||||
await R.exec(
|
||||
@@ -37,9 +40,11 @@ const DEFAULT_KEEP_PERIOD = 180;
|
||||
[ parsedPeriod ]
|
||||
);
|
||||
} catch (e) {
|
||||
log(`Failed to clear old data: ${e.message}`);
|
||||
log.error("clearOldData", `Failed to clear old data: ${e.message}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exit();
|
||||
})();
|
||||
module.exports = {
|
||||
clearOldData,
|
||||
};
|
||||
|
@@ -1,50 +0,0 @@
|
||||
const { parentPort, workerData } = require("worker_threads");
|
||||
const Database = require("../database");
|
||||
const path = require("path");
|
||||
|
||||
/**
|
||||
* Send message to parent process for logging
|
||||
* since worker_thread does not have access to stdout, this is used
|
||||
* instead of console.log()
|
||||
* @param {any} any The message to log
|
||||
*/
|
||||
const log = function (any) {
|
||||
if (parentPort) {
|
||||
parentPort.postMessage(any);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Exit the worker process
|
||||
* @param {number} error The status code to exit
|
||||
*/
|
||||
const exit = function (error) {
|
||||
if (error && error !== 0) {
|
||||
process.exit(error);
|
||||
} else {
|
||||
if (parentPort) {
|
||||
parentPort.postMessage("done");
|
||||
} else {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** Connects to the database */
|
||||
const connectDb = async function () {
|
||||
const dbPath = path.join(
|
||||
process.env.DATA_DIR || workerData["data-dir"] || "./data/"
|
||||
);
|
||||
|
||||
Database.init({
|
||||
"data-dir": dbPath,
|
||||
});
|
||||
|
||||
await Database.connect();
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
log,
|
||||
exit,
|
||||
connectDb,
|
||||
};
|
@@ -1,8 +1,10 @@
|
||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||
const { parseTimeObject, parseTimeFromTimeObject, utcToLocal, localToUTC, log } = require("../../src/util");
|
||||
const { timeObjectToUTC, timeObjectToLocal } = require("../util-server");
|
||||
const { parseTimeObject, parseTimeFromTimeObject, log } = require("../../src/util");
|
||||
const { R } = require("redbean-node");
|
||||
const dayjs = require("dayjs");
|
||||
const Cron = require("croner");
|
||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||
const apicache = require("../modules/apicache");
|
||||
|
||||
class Maintenance extends BeanModel {
|
||||
|
||||
@@ -15,16 +17,19 @@ class Maintenance extends BeanModel {
|
||||
|
||||
let dateRange = [];
|
||||
if (this.start_date) {
|
||||
dateRange.push(utcToLocal(this.start_date));
|
||||
if (this.end_date) {
|
||||
dateRange.push(utcToLocal(this.end_date));
|
||||
}
|
||||
dateRange.push(this.start_date);
|
||||
} else {
|
||||
dateRange.push(null);
|
||||
}
|
||||
|
||||
if (this.end_date) {
|
||||
dateRange.push(this.end_date);
|
||||
}
|
||||
|
||||
let timeRange = [];
|
||||
let startTime = timeObjectToLocal(parseTimeObject(this.start_time));
|
||||
let startTime = parseTimeObject(this.start_time);
|
||||
timeRange.push(startTime);
|
||||
let endTime = timeObjectToLocal(parseTimeObject(this.end_time));
|
||||
let endTime = parseTimeObject(this.end_time);
|
||||
timeRange.push(endTime);
|
||||
|
||||
let obj = {
|
||||
@@ -39,12 +44,44 @@ class Maintenance extends BeanModel {
|
||||
weekdays: (this.weekdays) ? JSON.parse(this.weekdays) : [],
|
||||
daysOfMonth: (this.days_of_month) ? JSON.parse(this.days_of_month) : [],
|
||||
timeslotList: [],
|
||||
cron: this.cron,
|
||||
duration: this.duration,
|
||||
durationMinutes: parseInt(this.duration / 60),
|
||||
timezone: await this.getTimezone(), // Only valid timezone
|
||||
timezoneOption: this.timezone, // Mainly for dropdown menu, because there is a option "SAME_AS_SERVER"
|
||||
timezoneOffset: await this.getTimezoneOffset(),
|
||||
status: await this.getStatus(),
|
||||
};
|
||||
|
||||
const timeslotList = await this.getTimeslotList();
|
||||
if (this.strategy === "manual") {
|
||||
// Do nothing, no timeslots
|
||||
} else if (this.strategy === "single") {
|
||||
obj.timeslotList.push({
|
||||
startDate: this.start_date,
|
||||
endDate: this.end_date,
|
||||
});
|
||||
} else {
|
||||
// Should be cron or recurring here
|
||||
if (this.beanMeta.job) {
|
||||
let runningTimeslot = this.getRunningTimeslot();
|
||||
|
||||
for (let timeslot of timeslotList) {
|
||||
obj.timeslotList.push(await timeslot.toPublicJSON());
|
||||
if (runningTimeslot) {
|
||||
obj.timeslotList.push(runningTimeslot);
|
||||
}
|
||||
|
||||
let nextRunDate = this.beanMeta.job.nextRun();
|
||||
if (nextRunDate) {
|
||||
let startDateDayjs = dayjs(nextRunDate);
|
||||
|
||||
let startDate = startDateDayjs.toISOString();
|
||||
let endDate = startDateDayjs.add(this.duration, "second").toISOString();
|
||||
|
||||
obj.timeslotList.push({
|
||||
startDate,
|
||||
endDate,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!Array.isArray(obj.weekdays)) {
|
||||
@@ -55,54 +92,9 @@ class Maintenance extends BeanModel {
|
||||
obj.daysOfMonth = [];
|
||||
}
|
||||
|
||||
// Maintenance Status
|
||||
if (!obj.active) {
|
||||
obj.status = "inactive";
|
||||
} else if (obj.strategy === "manual") {
|
||||
obj.status = "under-maintenance";
|
||||
} else if (obj.timeslotList.length > 0) {
|
||||
let currentTimestamp = dayjs().unix();
|
||||
|
||||
for (let timeslot of obj.timeslotList) {
|
||||
if (dayjs.utc(timeslot.startDate).unix() <= currentTimestamp && dayjs.utc(timeslot.endDate).unix() >= currentTimestamp) {
|
||||
log.debug("timeslot", "Timeslot ID: " + timeslot.id);
|
||||
log.debug("timeslot", "currentTimestamp:" + currentTimestamp);
|
||||
log.debug("timeslot", "timeslot.start_date:" + dayjs.utc(timeslot.startDate).unix());
|
||||
log.debug("timeslot", "timeslot.end_date:" + dayjs.utc(timeslot.endDate).unix());
|
||||
|
||||
obj.status = "under-maintenance";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!obj.status) {
|
||||
obj.status = "scheduled";
|
||||
}
|
||||
} else if (obj.timeslotList.length === 0) {
|
||||
obj.status = "ended";
|
||||
} else {
|
||||
obj.status = "unknown";
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only get future or current timeslots only
|
||||
* @returns {Promise<[]>}
|
||||
*/
|
||||
async getTimeslotList() {
|
||||
return R.convertToBeans("maintenance_timeslot", await R.getAll(`
|
||||
SELECT maintenance_timeslot.*
|
||||
FROM maintenance_timeslot, maintenance
|
||||
WHERE maintenance_timeslot.maintenance_id = maintenance.id
|
||||
AND maintenance.id = ?
|
||||
AND ${Maintenance.getActiveAndFutureMaintenanceSQLCondition()}
|
||||
`, [
|
||||
this.id
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an object that ready to parse to JSON
|
||||
* @param {string} timezone If not specified, the timeRange will be in UTC
|
||||
@@ -126,7 +118,7 @@ class Maintenance extends BeanModel {
|
||||
|
||||
/**
|
||||
* Get a list of days in month that maintenance is active for
|
||||
* @returns {number[]} Array of active days in month
|
||||
* @returns {number[]|string[]} Array of active days in month
|
||||
*/
|
||||
getDayOfMonthList() {
|
||||
return JSON.parse(this.days_of_month).sort(function (a, b) {
|
||||
@@ -135,26 +127,10 @@ class Maintenance extends BeanModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
// Start Time
|
||||
let startTimeSecond = dayjs.utc(this.start_time, "HH:mm").diff(dayjs.utc(startOfTheDay, "HH:mm"), "second");
|
||||
log.debug("timeslot", "startTime: " + startTimeSecond);
|
||||
|
||||
// Bake StartDate + StartTime = Start DateTime
|
||||
return dayjs.utc(this.start_date).add(startTimeSecond, "second");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the duraction of maintenance in seconds
|
||||
* Get the duration of maintenance in seconds
|
||||
* @returns {number} Duration of maintenance
|
||||
*/
|
||||
getDuration() {
|
||||
calcDuration() {
|
||||
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
|
||||
if (duration < 0) {
|
||||
@@ -169,71 +145,270 @@ class Maintenance extends BeanModel {
|
||||
* @param {Object} obj Data to fill bean with
|
||||
* @returns {Bean} Filled bean
|
||||
*/
|
||||
static jsonToBean(bean, obj) {
|
||||
static async jsonToBean(bean, obj) {
|
||||
if (obj.id) {
|
||||
bean.id = obj.id;
|
||||
}
|
||||
|
||||
// Apply timezone offset to timeRange, as it cannot apply automatically.
|
||||
if (obj.timeRange[0]) {
|
||||
timeObjectToUTC(obj.timeRange[0]);
|
||||
if (obj.timeRange[1]) {
|
||||
timeObjectToUTC(obj.timeRange[1]);
|
||||
}
|
||||
}
|
||||
|
||||
bean.title = obj.title;
|
||||
bean.description = obj.description;
|
||||
bean.strategy = obj.strategy;
|
||||
bean.interval_day = obj.intervalDay;
|
||||
bean.timezone = obj.timezoneOption;
|
||||
bean.active = obj.active;
|
||||
|
||||
if (obj.dateRange[0]) {
|
||||
bean.start_date = localToUTC(obj.dateRange[0]);
|
||||
|
||||
if (obj.dateRange[1]) {
|
||||
bean.end_date = localToUTC(obj.dateRange[1]);
|
||||
}
|
||||
bean.start_date = obj.dateRange[0];
|
||||
} else {
|
||||
bean.start_date = null;
|
||||
}
|
||||
|
||||
bean.start_time = parseTimeFromTimeObject(obj.timeRange[0]);
|
||||
bean.end_time = parseTimeFromTimeObject(obj.timeRange[1]);
|
||||
if (obj.dateRange[1]) {
|
||||
bean.end_date = obj.dateRange[1];
|
||||
} else {
|
||||
bean.end_date = null;
|
||||
}
|
||||
|
||||
bean.weekdays = JSON.stringify(obj.weekdays);
|
||||
bean.days_of_month = JSON.stringify(obj.daysOfMonth);
|
||||
if (bean.strategy === "cron") {
|
||||
bean.duration = obj.durationMinutes * 60;
|
||||
bean.cron = obj.cron;
|
||||
this.validateCron(bean.cron);
|
||||
}
|
||||
|
||||
if (bean.strategy.startsWith("recurring-")) {
|
||||
bean.start_time = parseTimeFromTimeObject(obj.timeRange[0]);
|
||||
bean.end_time = parseTimeFromTimeObject(obj.timeRange[1]);
|
||||
bean.weekdays = JSON.stringify(obj.weekdays);
|
||||
bean.days_of_month = JSON.stringify(obj.daysOfMonth);
|
||||
await bean.generateCron();
|
||||
this.validateCron(bean.cron);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL conditions for active maintenance
|
||||
* @returns {string}
|
||||
* Throw error if cron is invalid
|
||||
* @param cron
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
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)
|
||||
)
|
||||
`;
|
||||
static async validateCron(cron) {
|
||||
let job = new Cron(cron, () => {});
|
||||
job.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL conditions for active and future maintenance
|
||||
* @returns {string}
|
||||
* Run the cron
|
||||
*/
|
||||
static getActiveAndFutureMaintenanceSQLCondition() {
|
||||
return `
|
||||
(
|
||||
((maintenance_timeslot.end_date >= DATETIME('now')
|
||||
AND maintenance.active = 1)
|
||||
OR
|
||||
(maintenance.strategy = 'manual' AND active = 1))
|
||||
)
|
||||
`;
|
||||
async run(throwError = false) {
|
||||
if (this.beanMeta.job) {
|
||||
log.debug("maintenance", "Maintenance is already running, stop it first. id: " + this.id);
|
||||
this.stop();
|
||||
}
|
||||
|
||||
log.debug("maintenance", "Run maintenance id: " + this.id);
|
||||
|
||||
// 1.21.2 migration
|
||||
if (!this.cron) {
|
||||
await this.generateCron();
|
||||
if (!this.timezone) {
|
||||
this.timezone = "UTC";
|
||||
}
|
||||
if (this.cron) {
|
||||
await R.store(this);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.strategy === "manual") {
|
||||
// Do nothing, because it is controlled by the user
|
||||
} else if (this.strategy === "single") {
|
||||
this.beanMeta.job = new Cron(this.start_date, { timezone: await this.getTimezone() }, () => {
|
||||
log.info("maintenance", "Maintenance id: " + this.id + " is under maintenance now");
|
||||
UptimeKumaServer.getInstance().sendMaintenanceListByUserID(this.user_id);
|
||||
apicache.clear();
|
||||
});
|
||||
} else if (this.cron != null) {
|
||||
// Here should be cron or recurring
|
||||
try {
|
||||
this.beanMeta.status = "scheduled";
|
||||
|
||||
let startEvent = (customDuration = 0) => {
|
||||
log.info("maintenance", "Maintenance id: " + this.id + " is under maintenance now");
|
||||
|
||||
this.beanMeta.status = "under-maintenance";
|
||||
clearTimeout(this.beanMeta.durationTimeout);
|
||||
|
||||
// Check if duration is still in the window. If not, use the duration from the current time to the end of the window
|
||||
let duration;
|
||||
|
||||
if (customDuration > 0) {
|
||||
duration = customDuration;
|
||||
} else if (this.end_date) {
|
||||
let d = dayjs(this.end_date).diff(dayjs(), "second");
|
||||
if (d < this.duration) {
|
||||
duration = d * 1000;
|
||||
}
|
||||
} else {
|
||||
duration = this.duration * 1000;
|
||||
}
|
||||
|
||||
UptimeKumaServer.getInstance().sendMaintenanceListByUserID(this.user_id);
|
||||
|
||||
this.beanMeta.durationTimeout = setTimeout(() => {
|
||||
// End of maintenance for this timeslot
|
||||
this.beanMeta.status = "scheduled";
|
||||
UptimeKumaServer.getInstance().sendMaintenanceListByUserID(this.user_id);
|
||||
}, duration);
|
||||
};
|
||||
|
||||
// Create Cron
|
||||
this.beanMeta.job = new Cron(this.cron, {
|
||||
timezone: await this.getTimezone(),
|
||||
}, startEvent);
|
||||
|
||||
// Continue if the maintenance is still in the window
|
||||
let runningTimeslot = this.getRunningTimeslot();
|
||||
let current = dayjs();
|
||||
|
||||
if (runningTimeslot) {
|
||||
let duration = dayjs(runningTimeslot.endDate).diff(current, "second") * 1000;
|
||||
log.debug("maintenance", "Maintenance id: " + this.id + " Remaining duration: " + duration + "ms");
|
||||
startEvent(duration);
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
log.error("maintenance", "Error in maintenance id: " + this.id);
|
||||
log.error("maintenance", "Cron: " + this.cron);
|
||||
log.error("maintenance", e);
|
||||
|
||||
if (throwError) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
log.error("maintenance", "Maintenance id: " + this.id + " has no cron");
|
||||
}
|
||||
}
|
||||
|
||||
getRunningTimeslot() {
|
||||
let start = dayjs(this.beanMeta.job.nextRun(dayjs().add(-this.duration, "second").toDate()));
|
||||
let end = start.add(this.duration, "second");
|
||||
let current = dayjs();
|
||||
|
||||
if (current.isAfter(start) && current.isBefore(end)) {
|
||||
return {
|
||||
startDate: start.toISOString(),
|
||||
endDate: end.toISOString(),
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.beanMeta.job) {
|
||||
this.beanMeta.job.stop();
|
||||
delete this.beanMeta.job;
|
||||
}
|
||||
}
|
||||
|
||||
async isUnderMaintenance() {
|
||||
return (await this.getStatus()) === "under-maintenance";
|
||||
}
|
||||
|
||||
async getTimezone() {
|
||||
if (!this.timezone || this.timezone === "SAME_AS_SERVER") {
|
||||
return await UptimeKumaServer.getInstance().getTimezone();
|
||||
}
|
||||
return this.timezone;
|
||||
}
|
||||
|
||||
async getTimezoneOffset() {
|
||||
return dayjs.tz(dayjs(), await this.getTimezone()).format("Z");
|
||||
}
|
||||
|
||||
async getStatus() {
|
||||
if (!this.active) {
|
||||
return "inactive";
|
||||
}
|
||||
|
||||
if (this.strategy === "manual") {
|
||||
return "under-maintenance";
|
||||
}
|
||||
|
||||
// Check if the maintenance is started
|
||||
if (this.start_date && dayjs().isBefore(dayjs.tz(this.start_date, await this.getTimezone()))) {
|
||||
return "scheduled";
|
||||
}
|
||||
|
||||
// Check if the maintenance is ended
|
||||
if (this.end_date && dayjs().isAfter(dayjs.tz(this.end_date, await this.getTimezone()))) {
|
||||
return "ended";
|
||||
}
|
||||
|
||||
if (this.strategy === "single") {
|
||||
return "under-maintenance";
|
||||
}
|
||||
|
||||
if (!this.beanMeta.status) {
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
return this.beanMeta.status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Cron for recurring maintenance
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async generateCron() {
|
||||
log.info("maintenance", "Generate cron for maintenance id: " + this.id);
|
||||
|
||||
if (this.strategy === "cron") {
|
||||
// Do nothing for cron
|
||||
} else if (!this.strategy.startsWith("recurring-")) {
|
||||
this.cron = "";
|
||||
} else if (this.strategy === "recurring-interval") {
|
||||
let array = this.start_time.split(":");
|
||||
let hour = parseInt(array[0]);
|
||||
let minute = parseInt(array[1]);
|
||||
this.cron = minute + " " + hour + " */" + this.interval_day + " * *";
|
||||
this.duration = this.calcDuration();
|
||||
log.debug("maintenance", "Cron: " + this.cron);
|
||||
log.debug("maintenance", "Duration: " + this.duration);
|
||||
} else if (this.strategy === "recurring-weekday") {
|
||||
let list = this.getDayOfWeekList();
|
||||
let array = this.start_time.split(":");
|
||||
let hour = parseInt(array[0]);
|
||||
let minute = parseInt(array[1]);
|
||||
this.cron = minute + " " + hour + " * * " + list.join(",");
|
||||
this.duration = this.calcDuration();
|
||||
} else if (this.strategy === "recurring-day-of-month") {
|
||||
let list = this.getDayOfMonthList();
|
||||
let array = this.start_time.split(":");
|
||||
let hour = parseInt(array[0]);
|
||||
let minute = parseInt(array[1]);
|
||||
|
||||
let dayList = [];
|
||||
|
||||
for (let day of list) {
|
||||
if (typeof day === "string" && day.startsWith("lastDay")) {
|
||||
if (day === "lastDay1") {
|
||||
dayList.push("L");
|
||||
}
|
||||
// Unfortunately, lastDay2-4 is not supported by cron
|
||||
} else {
|
||||
dayList.push(day);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove duplicate
|
||||
dayList = [ ...new Set(dayList) ];
|
||||
|
||||
this.cron = minute + " " + hour + " " + dayList.join(",") + " * *";
|
||||
this.duration = this.calcDuration();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,223 +0,0 @@
|
||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||
const { R } = require("redbean-node");
|
||||
const dayjs = require("dayjs");
|
||||
const { log, utcToLocal, SQL_DATETIME_FORMAT_WITHOUT_SECOND, localToUTC } = require("../../src/util");
|
||||
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();
|
||||
|
||||
const obj = {
|
||||
id: this.id,
|
||||
startDate: this.start_date,
|
||||
endDate: this.end_date,
|
||||
startDateServerTimezone: utcToLocal(this.start_date, SQL_DATETIME_FORMAT_WITHOUT_SECOND),
|
||||
endDateServerTimezone: utcToLocal(this.end_date, SQL_DATETIME_FORMAT_WITHOUT_SECOND),
|
||||
serverTimezoneOffset,
|
||||
};
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an object that ready to parse to JSON
|
||||
* @returns {Object}
|
||||
*/
|
||||
async toJSON() {
|
||||
return await this.toPublicJSON();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Maintenance} maintenance
|
||||
* @param {dayjs} minDate (For recurring type only) Generate a next timeslot from this date.
|
||||
* @param {boolean} removeExist Remove existing timeslot before create
|
||||
* @returns {Promise<MaintenanceTimeslot>}
|
||||
*/
|
||||
static async generateTimeslot(maintenance, minDate = null, removeExist = false) {
|
||||
log.info("maintenance", "Generate Timeslot for maintenance id: " + maintenance.id);
|
||||
|
||||
if (removeExist) {
|
||||
await R.exec("DELETE FROM maintenance_timeslot WHERE maintenance_id = ? ", [
|
||||
maintenance.id
|
||||
]);
|
||||
}
|
||||
|
||||
if (maintenance.strategy === "manual") {
|
||||
log.debug("maintenance", "No need to generate timeslot for manual type");
|
||||
|
||||
} else if (maintenance.strategy === "single") {
|
||||
let bean = R.dispense("maintenance_timeslot");
|
||||
bean.maintenance_id = maintenance.id;
|
||||
bean.start_date = maintenance.start_date;
|
||||
bean.end_date = maintenance.end_date;
|
||||
bean.generated_next = true;
|
||||
|
||||
if (!await this.isDuplicateTimeslot(bean)) {
|
||||
await R.store(bean);
|
||||
return bean;
|
||||
} else {
|
||||
log.debug("maintenance", "Duplicate timeslot, skip");
|
||||
return null;
|
||||
}
|
||||
|
||||
} else if (maintenance.strategy === "recurring-interval") {
|
||||
// Prevent dead loop, in case interval_day is not set
|
||||
if (!maintenance.interval_day || maintenance.interval_day <= 0) {
|
||||
maintenance.interval_day = 1;
|
||||
}
|
||||
|
||||
return await this.handleRecurringType(maintenance, minDate, (startDateTime) => {
|
||||
return startDateTime.add(maintenance.interval_day, "day");
|
||||
}, () => {
|
||||
return true;
|
||||
});
|
||||
|
||||
} else if (maintenance.strategy === "recurring-weekday") {
|
||||
let dayOfWeekList = maintenance.getDayOfWeekList();
|
||||
log.debug("timeslot", dayOfWeekList);
|
||||
|
||||
if (dayOfWeekList.length <= 0) {
|
||||
log.debug("timeslot", "No weekdays selected?");
|
||||
return null;
|
||||
}
|
||||
|
||||
const isValid = (startDateTime) => {
|
||||
log.debug("timeslot", "nextDateTime: " + startDateTime);
|
||||
|
||||
let day = startDateTime.local().day();
|
||||
log.debug("timeslot", "nextDateTime.day(): " + day);
|
||||
|
||||
return dayOfWeekList.includes(day);
|
||||
};
|
||||
|
||||
return await this.handleRecurringType(maintenance, minDate, (startDateTime) => {
|
||||
while (true) {
|
||||
startDateTime = startDateTime.add(1, "day");
|
||||
|
||||
if (isValid(startDateTime)) {
|
||||
return startDateTime;
|
||||
}
|
||||
}
|
||||
}, isValid);
|
||||
|
||||
} else if (maintenance.strategy === "recurring-day-of-month") {
|
||||
let dayOfMonthList = maintenance.getDayOfMonthList();
|
||||
if (dayOfMonthList.length <= 0) {
|
||||
log.debug("timeslot", "No day selected?");
|
||||
return null;
|
||||
}
|
||||
|
||||
const isValid = (startDateTime) => {
|
||||
let day = parseInt(startDateTime.local().format("D"));
|
||||
|
||||
log.debug("timeslot", "day: " + day);
|
||||
|
||||
// Check 1-31
|
||||
if (dayOfMonthList.includes(day)) {
|
||||
return startDateTime;
|
||||
}
|
||||
|
||||
// Check "lastDay1","lastDay2"...
|
||||
let daysInMonth = startDateTime.daysInMonth();
|
||||
let lastDayList = [];
|
||||
|
||||
// Small first, e.g. 28 > 29 > 30 > 31
|
||||
for (let i = 4; i >= 1; i--) {
|
||||
if (dayOfMonthList.includes("lastDay" + i)) {
|
||||
lastDayList.push(daysInMonth - i + 1);
|
||||
}
|
||||
}
|
||||
log.debug("timeslot", lastDayList);
|
||||
return lastDayList.includes(day);
|
||||
};
|
||||
|
||||
return await this.handleRecurringType(maintenance, minDate, (startDateTime) => {
|
||||
while (true) {
|
||||
startDateTime = startDateTime.add(1, "day");
|
||||
if (isValid(startDateTime)) {
|
||||
return startDateTime;
|
||||
}
|
||||
}
|
||||
}, isValid);
|
||||
} else {
|
||||
throw new Error("Unknown maintenance strategy");
|
||||
}
|
||||
}
|
||||
|
||||
static async isDuplicateTimeslot(timeslot) {
|
||||
let bean = await R.findOne("maintenance_timeslot", "maintenance_id = ? AND start_date = ? AND end_date = ?", [
|
||||
timeslot.maintenance_id,
|
||||
timeslot.start_date,
|
||||
timeslot.end_date
|
||||
]);
|
||||
return bean !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a next timeslot for all recurring types
|
||||
* @param maintenance
|
||||
* @param minDate
|
||||
* @param {function} nextDayCallback The logic how to get the next possible day
|
||||
* @param {function} isValidCallback Check the day whether is matched the current strategy
|
||||
* @returns {Promise<null|MaintenanceTimeslot>}
|
||||
*/
|
||||
static async handleRecurringType(maintenance, minDate, nextDayCallback, isValidCallback) {
|
||||
let bean = R.dispense("maintenance_timeslot");
|
||||
|
||||
let duration = maintenance.getDuration();
|
||||
let startDateTime = maintenance.getStartDateTime();
|
||||
let endDateTime;
|
||||
|
||||
// Keep generating from the first possible date, until it is ok
|
||||
while (true) {
|
||||
//log.debug("timeslot", "startDateTime: " + startDateTime.format());
|
||||
|
||||
// Handling out of effective date range
|
||||
if (startDateTime.diff(dayjs.utc(maintenance.end_date)) > 0) {
|
||||
log.debug("timeslot", "Out of effective date range");
|
||||
return null;
|
||||
}
|
||||
|
||||
endDateTime = startDateTime.add(duration, "second");
|
||||
|
||||
// If endDateTime is out of effective date range, use the end datetime from effective date range
|
||||
if (endDateTime.diff(dayjs.utc(maintenance.end_date)) > 0) {
|
||||
endDateTime = dayjs.utc(maintenance.end_date);
|
||||
}
|
||||
|
||||
// If minDate is set, the endDateTime must be bigger than it.
|
||||
// And the endDateTime must be bigger current time
|
||||
// Is valid under current recurring strategy
|
||||
if (
|
||||
(!minDate || endDateTime.diff(minDate) > 0) &&
|
||||
endDateTime.diff(dayjs()) > 0 &&
|
||||
isValidCallback(startDateTime)
|
||||
) {
|
||||
break;
|
||||
}
|
||||
startDateTime = nextDayCallback(startDateTime);
|
||||
}
|
||||
|
||||
bean.maintenance_id = maintenance.id;
|
||||
bean.start_date = localToUTC(startDateTime);
|
||||
bean.end_date = localToUTC(endDateTime);
|
||||
bean.generated_next = false;
|
||||
|
||||
if (!await this.isDuplicateTimeslot(bean)) {
|
||||
await R.store(bean);
|
||||
return bean;
|
||||
} else {
|
||||
log.debug("maintenance", "Duplicate timeslot, skip");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MaintenanceTimeslot;
|
@@ -2,7 +2,9 @@ const https = require("https");
|
||||
const dayjs = require("dayjs");
|
||||
const axios = require("axios");
|
||||
const { Prometheus } = require("../prometheus");
|
||||
const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, TimeLogger, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND } = require("../../src/util");
|
||||
const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, TimeLogger, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND,
|
||||
SQL_DATETIME_FORMAT
|
||||
} = require("../../src/util");
|
||||
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, mqttAsync, setSetting, httpNtlm, radius, grpcQuery,
|
||||
redisPingAsync, mongodbPing,
|
||||
} = require("../util-server");
|
||||
@@ -16,7 +18,6 @@ const apicache = require("../modules/apicache");
|
||||
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");
|
||||
const Gamedig = require("gamedig");
|
||||
|
||||
@@ -73,13 +74,17 @@ class Monitor extends BeanModel {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
pathName: await this.getPathName(),
|
||||
parent: this.parent,
|
||||
childrenIDs: await Monitor.getAllChildrenIDs(this.id),
|
||||
url: this.url,
|
||||
method: this.method,
|
||||
hostname: this.hostname,
|
||||
port: this.port,
|
||||
maxretries: this.maxretries,
|
||||
weight: this.weight,
|
||||
active: this.active,
|
||||
active: await this.isActive(),
|
||||
forceInactive: !await Monitor.isParentActive(this.id),
|
||||
type: this.type,
|
||||
interval: this.interval,
|
||||
retryInterval: this.retryInterval,
|
||||
@@ -143,6 +148,16 @@ class Monitor extends BeanModel {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the monitor is active based on itself and its parents
|
||||
* @returns {Promise<Boolean>}
|
||||
*/
|
||||
async isActive() {
|
||||
const parentActive = await Monitor.isParentActive(this.id);
|
||||
|
||||
return this.active && parentActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all tags applied to this monitor
|
||||
* @returns {Promise<LooseObject<any>[]>}
|
||||
@@ -258,6 +273,36 @@ class Monitor extends BeanModel {
|
||||
if (await Monitor.isUnderMaintenance(this.id)) {
|
||||
bean.msg = "Monitor under maintenance";
|
||||
bean.status = MAINTENANCE;
|
||||
} else if (this.type === "group") {
|
||||
const children = await Monitor.getChildren(this.id);
|
||||
|
||||
if (children.length > 0) {
|
||||
bean.status = UP;
|
||||
bean.msg = "All children up and running";
|
||||
for (const child of children) {
|
||||
if (!child.active) {
|
||||
// Ignore inactive childs
|
||||
continue;
|
||||
}
|
||||
const lastBeat = await Monitor.getPreviousHeartbeat(child.id);
|
||||
|
||||
// Only change state if the monitor is in worse conditions then the ones before
|
||||
if (bean.status === UP && (lastBeat.status === PENDING || lastBeat.status === DOWN)) {
|
||||
bean.status = lastBeat.status;
|
||||
} else if (bean.status === PENDING && lastBeat.status === DOWN) {
|
||||
bean.status = lastBeat.status;
|
||||
}
|
||||
}
|
||||
|
||||
if (bean.status !== UP) {
|
||||
bean.msg = "Child inaccessible";
|
||||
}
|
||||
} else {
|
||||
// Set status pending if group is empty
|
||||
bean.status = PENDING;
|
||||
bean.msg = "Group empty";
|
||||
}
|
||||
|
||||
} else if (this.type === "http" || this.type === "keyword") {
|
||||
// Do not do any queries/high loading things before the "bean.ping"
|
||||
let startTime = dayjs().valueOf();
|
||||
@@ -364,8 +409,8 @@ class Monitor extends BeanModel {
|
||||
tlsInfo = await this.updateTlsInfo(tlsInfoObject);
|
||||
|
||||
if (!this.getIgnoreTls() && this.isEnabledExpiryNotification()) {
|
||||
log.debug("monitor", `[${this.name}] call sendCertNotification`);
|
||||
await this.sendCertNotification(tlsInfoObject);
|
||||
log.debug("monitor", `[${this.name}] call checkCertExpiryNotifications`);
|
||||
await this.checkCertExpiryNotifications(tlsInfoObject);
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
@@ -399,7 +444,7 @@ class Monitor extends BeanModel {
|
||||
bean.msg += ", keyword is found";
|
||||
bean.status = UP;
|
||||
} else {
|
||||
data = data.replace(/<[^>]*>?|[\n\r]|\s+/gm, " ");
|
||||
data = data.replace(/<[^>]*>?|[\n\r]|\s+/gm, " ").trim();
|
||||
if (data.length > 50) {
|
||||
data = data.substring(0, 47) + "...";
|
||||
}
|
||||
@@ -637,9 +682,7 @@ class Monitor extends BeanModel {
|
||||
} else if (this.type === "mysql") {
|
||||
let startTime = dayjs().valueOf();
|
||||
|
||||
await mysqlQuery(this.databaseConnectionString, this.databaseQuery);
|
||||
|
||||
bean.msg = "";
|
||||
bean.msg = await mysqlQuery(this.databaseConnectionString, this.databaseQuery);
|
||||
bean.status = UP;
|
||||
bean.ping = dayjs().valueOf() - startTime;
|
||||
} else if (this.type === "mongodb") {
|
||||
@@ -1179,12 +1222,18 @@ class Monitor extends BeanModel {
|
||||
|
||||
for (let notification of notificationList) {
|
||||
try {
|
||||
// Prevent if the msg is undefined, notifications such as Discord cannot send out.
|
||||
const heartbeatJSON = bean.toJSON();
|
||||
|
||||
// Prevent if the msg is undefined, notifications such as Discord cannot send out.
|
||||
if (!heartbeatJSON["msg"]) {
|
||||
heartbeatJSON["msg"] = "N/A";
|
||||
}
|
||||
|
||||
// Also provide the time in server timezone
|
||||
heartbeatJSON["timezone"] = await UptimeKumaServer.getInstance().getTimezone();
|
||||
heartbeatJSON["timezoneOffset"] = UptimeKumaServer.getInstance().getTimezoneOffset();
|
||||
heartbeatJSON["localDateTime"] = dayjs.utc(heartbeatJSON["time"]).tz(heartbeatJSON["timezone"]).format(SQL_DATETIME_FORMAT);
|
||||
|
||||
await Notification.send(JSON.parse(notification.config), msg, await monitor.toJSON(false), heartbeatJSON);
|
||||
} catch (e) {
|
||||
log.error("monitor", "Cannot send notification to " + notification.name);
|
||||
@@ -1207,13 +1256,19 @@ class Monitor extends BeanModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Send notification about a certificate
|
||||
* checks certificate chain for expiring certificates
|
||||
* @param {Object} tlsInfoObject Information about certificate
|
||||
*/
|
||||
async sendCertNotification(tlsInfoObject) {
|
||||
async checkCertExpiryNotifications(tlsInfoObject) {
|
||||
if (tlsInfoObject && tlsInfoObject.certInfo && tlsInfoObject.certInfo.daysRemaining) {
|
||||
const notificationList = await Monitor.getNotificationList(this);
|
||||
|
||||
if (! notificationList.length > 0) {
|
||||
// fail fast. If no notification is set, all the following checks can be skipped.
|
||||
log.debug("monitor", "No notification, no need to send cert notification");
|
||||
return;
|
||||
}
|
||||
|
||||
let notifyDays = await setting("tlsExpiryNotifyDays");
|
||||
if (notifyDays == null || !Array.isArray(notifyDays)) {
|
||||
// Reset Default
|
||||
@@ -1221,10 +1276,19 @@ class Monitor extends BeanModel {
|
||||
notifyDays = [ 7, 14, 21 ];
|
||||
}
|
||||
|
||||
if (notifyDays != null && Array.isArray(notifyDays)) {
|
||||
for (const day of notifyDays) {
|
||||
log.debug("monitor", "call sendCertNotificationByTargetDays", day);
|
||||
await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, day, notificationList);
|
||||
if (Array.isArray(notifyDays)) {
|
||||
for (const targetDays of notifyDays) {
|
||||
let certInfo = tlsInfoObject.certInfo;
|
||||
while (certInfo) {
|
||||
let subjectCN = certInfo.subject["CN"];
|
||||
if (certInfo.daysRemaining > targetDays) {
|
||||
log.debug("monitor", `No need to send cert notification for ${certInfo.certType} certificate "${subjectCN}" (${certInfo.daysRemaining} days valid) on ${targetDays} deadline.`);
|
||||
} else {
|
||||
log.debug("monitor", `call sendCertNotificationByTargetDays for ${targetDays} deadline on certificate ${subjectCN}.`);
|
||||
await this.sendCertNotificationByTargetDays(subjectCN, certInfo.certType, certInfo.daysRemaining, targetDays, notificationList);
|
||||
}
|
||||
certInfo = certInfo.issuerCertificate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1233,55 +1297,47 @@ class Monitor extends BeanModel {
|
||||
/**
|
||||
* Send a certificate notification when certificate expires in less
|
||||
* than target days
|
||||
* @param {number} daysRemaining Number of days remaining on certifcate
|
||||
* @param {string} certCN Common Name attribute from the certificate subject
|
||||
* @param {string} certType certificate type
|
||||
* @param {number} daysRemaining Number of days remaining on certificate
|
||||
* @param {number} targetDays Number of days to alert after
|
||||
* @param {LooseObject<any>[]} notificationList List of notification providers
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async sendCertNotificationByTargetDays(daysRemaining, targetDays, notificationList) {
|
||||
async sendCertNotificationByTargetDays(certCN, certType, daysRemaining, targetDays, notificationList) {
|
||||
|
||||
if (daysRemaining > targetDays) {
|
||||
log.debug("monitor", `No need to send cert notification. ${daysRemaining} > ${targetDays}`);
|
||||
let row = await R.getRow("SELECT * FROM notification_sent_history WHERE type = ? AND monitor_id = ? AND days <= ?", [
|
||||
"certificate",
|
||||
this.id,
|
||||
targetDays,
|
||||
]);
|
||||
|
||||
// Sent already, no need to send again
|
||||
if (row) {
|
||||
log.debug("monitor", "Sent already, no need to send again");
|
||||
return;
|
||||
}
|
||||
|
||||
if (notificationList.length > 0) {
|
||||
let sent = false;
|
||||
log.debug("monitor", "Send certificate notification");
|
||||
|
||||
let row = await R.getRow("SELECT * FROM notification_sent_history WHERE type = ? AND monitor_id = ? AND days = ?", [
|
||||
for (let notification of notificationList) {
|
||||
try {
|
||||
log.debug("monitor", "Sending to " + notification.name);
|
||||
await Notification.send(JSON.parse(notification.config), `[${this.name}][${this.url}] ${certType} certificate ${certCN} will be expired in ${daysRemaining} days`);
|
||||
sent = true;
|
||||
} catch (e) {
|
||||
log.error("monitor", "Cannot send cert notification to " + notification.name);
|
||||
log.error("monitor", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (sent) {
|
||||
await R.exec("INSERT INTO notification_sent_history (type, monitor_id, days) VALUES(?, ?, ?)", [
|
||||
"certificate",
|
||||
this.id,
|
||||
targetDays,
|
||||
]);
|
||||
|
||||
// Sent already, no need to send again
|
||||
if (row) {
|
||||
log.debug("monitor", "Sent already, no need to send again");
|
||||
return;
|
||||
}
|
||||
|
||||
let sent = false;
|
||||
log.debug("monitor", "Send certificate notification");
|
||||
|
||||
for (let notification of notificationList) {
|
||||
try {
|
||||
log.debug("monitor", "Sending to " + notification.name);
|
||||
await Notification.send(JSON.parse(notification.config), `[${this.name}][${this.url}] Certificate will expire in ${daysRemaining} days`);
|
||||
sent = true;
|
||||
} catch (e) {
|
||||
log.error("monitor", "Cannot send cert notification to " + notification.name);
|
||||
log.error("monitor", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (sent) {
|
||||
await R.exec("INSERT INTO notification_sent_history (type, monitor_id, days) VALUES(?, ?, ?)", [
|
||||
"certificate",
|
||||
this.id,
|
||||
targetDays,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
log.debug("monitor", "No notification, no need to send cert notification");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1305,18 +1361,24 @@ class Monitor extends BeanModel {
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
static async isUnderMaintenance(monitorID) {
|
||||
let activeCondition = Maintenance.getActiveMaintenanceSQLCondition();
|
||||
const maintenance = await R.getRow(`
|
||||
SELECT COUNT(*) AS count
|
||||
FROM monitor_maintenance mm
|
||||
JOIN maintenance
|
||||
ON mm.maintenance_id = maintenance.id
|
||||
AND mm.monitor_id = ?
|
||||
LEFT JOIN maintenance_timeslot
|
||||
ON maintenance_timeslot.maintenance_id = maintenance.id
|
||||
WHERE ${activeCondition}
|
||||
LIMIT 1`, [ monitorID ]);
|
||||
return maintenance.count !== 0;
|
||||
const maintenanceIDList = await R.getCol(`
|
||||
SELECT maintenance_id FROM monitor_maintenance
|
||||
WHERE monitor_id = ?
|
||||
`, [ monitorID ]);
|
||||
|
||||
for (const maintenanceID of maintenanceIDList) {
|
||||
const maintenance = await UptimeKumaServer.getInstance().getMaintenance(maintenanceID);
|
||||
if (maintenance && await maintenance.isUnderMaintenance()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const parent = await Monitor.getParent(monitorID);
|
||||
if (parent != null) {
|
||||
return await Monitor.isUnderMaintenance(parent.id);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Make sure monitor interval is between bounds */
|
||||
@@ -1328,6 +1390,94 @@ class Monitor extends BeanModel {
|
||||
throw new Error(`Interval cannot be less than ${MIN_INTERVAL_SECOND} seconds`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets Parent of the monitor
|
||||
* @param {number} monitorID ID of monitor to get
|
||||
* @returns {Promise<LooseObject<any>>}
|
||||
*/
|
||||
static async getParent(monitorID) {
|
||||
return await R.getRow(`
|
||||
SELECT parent.* FROM monitor parent
|
||||
LEFT JOIN monitor child
|
||||
ON child.parent = parent.id
|
||||
WHERE child.id = ?
|
||||
`, [
|
||||
monitorID,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all Children of the monitor
|
||||
* @param {number} monitorID ID of monitor to get
|
||||
* @returns {Promise<LooseObject<any>>}
|
||||
*/
|
||||
static async getChildren(monitorID) {
|
||||
return await R.getAll(`
|
||||
SELECT * FROM monitor
|
||||
WHERE parent = ?
|
||||
`, [
|
||||
monitorID,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets Full Path-Name (Groups and Name)
|
||||
* @returns {Promise<String>}
|
||||
*/
|
||||
async getPathName() {
|
||||
let path = this.name;
|
||||
|
||||
if (this.parent === null) {
|
||||
return path;
|
||||
}
|
||||
|
||||
let parent = await Monitor.getParent(this.id);
|
||||
while (parent !== null) {
|
||||
path = `${parent.name} / ${path}`;
|
||||
parent = await Monitor.getParent(parent.id);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets recursive all child ids
|
||||
* @param {number} monitorID ID of the monitor to get
|
||||
* @returns {Promise<Array>}
|
||||
*/
|
||||
static async getAllChildrenIDs(monitorID) {
|
||||
const childs = await Monitor.getChildren(monitorID);
|
||||
|
||||
if (childs === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let childrenIDs = [];
|
||||
|
||||
for (const child of childs) {
|
||||
childrenIDs.push(child.id);
|
||||
childrenIDs = childrenIDs.concat(await Monitor.getAllChildrenIDs(child.id));
|
||||
}
|
||||
|
||||
return childrenIDs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks recursive if parent (ancestors) are active
|
||||
* @param {number} monitorID ID of the monitor to get
|
||||
* @returns {Promise<Boolean>}
|
||||
*/
|
||||
static async isParentActive(monitorID) {
|
||||
const parent = await Monitor.getParent(monitorID);
|
||||
|
||||
if (parent === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const parentActive = await Monitor.isParentActive(parent.id);
|
||||
return parent.active && parentActive;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Monitor;
|
||||
|
@@ -3,7 +3,6 @@ const { R } = require("redbean-node");
|
||||
const cheerio = require("cheerio");
|
||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||
const jsesc = require("jsesc");
|
||||
const Maintenance = require("./maintenance");
|
||||
const googleAnalytics = require("../google-analytics");
|
||||
|
||||
class StatusPage extends BeanModel {
|
||||
@@ -290,21 +289,17 @@ class StatusPage extends BeanModel {
|
||||
try {
|
||||
const publicMaintenanceList = [];
|
||||
|
||||
let activeCondition = Maintenance.getActiveMaintenanceSQLCondition();
|
||||
let maintenanceBeanList = R.convertToBeans("maintenance", await R.getAll(`
|
||||
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 ]));
|
||||
let maintenanceIDList = await R.getCol(`
|
||||
SELECT DISTINCT maintenance_id
|
||||
FROM maintenance_status_page
|
||||
WHERE status_page_id = ?
|
||||
`, [ statusPageId ]);
|
||||
|
||||
for (const bean of maintenanceBeanList) {
|
||||
publicMaintenanceList.push(await bean.toPublicJSON());
|
||||
for (const maintenanceID of maintenanceIDList) {
|
||||
let maintenance = UptimeKumaServer.getInstance().getMaintenance(maintenanceID);
|
||||
if (maintenance && await maintenance.isUnderMaintenance()) {
|
||||
publicMaintenanceList.push(await maintenance.toPublicJSON());
|
||||
}
|
||||
}
|
||||
|
||||
return publicMaintenanceList;
|
||||
|
@@ -15,7 +15,7 @@ class DingDing extends NotificationProvider {
|
||||
msgtype: "markdown",
|
||||
markdown: {
|
||||
title: `[${this.statusToString(heartbeatJSON["status"])}] ${monitorJSON["name"]}`,
|
||||
text: `## [${this.statusToString(heartbeatJSON["status"])}] ${monitorJSON["name"]} \n > ${heartbeatJSON["msg"]} \n > Time(UTC):${heartbeatJSON["time"]}`,
|
||||
text: `## [${this.statusToString(heartbeatJSON["status"])}] ${monitorJSON["name"]} \n> ${heartbeatJSON["msg"]}\n> Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`,
|
||||
}
|
||||
};
|
||||
if (this.sendToDingDing(notification, params)) {
|
||||
|
@@ -59,8 +59,8 @@ class Discord extends NotificationProvider {
|
||||
value: monitorJSON["type"] === "push" ? "Heartbeat" : address,
|
||||
},
|
||||
{
|
||||
name: "Time (UTC)",
|
||||
value: heartbeatJSON["time"],
|
||||
name: `Time (${heartbeatJSON["timezone"]})`,
|
||||
value: heartbeatJSON["localDateTime"],
|
||||
},
|
||||
{
|
||||
name: "Error",
|
||||
@@ -94,8 +94,8 @@ class Discord extends NotificationProvider {
|
||||
value: monitorJSON["type"] === "push" ? "Heartbeat" : address,
|
||||
},
|
||||
{
|
||||
name: "Time (UTC)",
|
||||
value: heartbeatJSON["time"],
|
||||
name: `Time (${heartbeatJSON["timezone"]})`,
|
||||
value: heartbeatJSON["localDateTime"],
|
||||
},
|
||||
{
|
||||
name: "Ping",
|
||||
|
@@ -35,8 +35,7 @@ class Feishu extends NotificationProvider {
|
||||
text:
|
||||
"[Down] " +
|
||||
heartbeatJSON["msg"] +
|
||||
"\nTime (UTC): " +
|
||||
heartbeatJSON["time"],
|
||||
`\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`
|
||||
},
|
||||
],
|
||||
],
|
||||
@@ -62,8 +61,7 @@ class Feishu extends NotificationProvider {
|
||||
text:
|
||||
"[Up] " +
|
||||
heartbeatJSON["msg"] +
|
||||
"\nTime (UTC): " +
|
||||
heartbeatJSON["time"],
|
||||
`\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`,
|
||||
},
|
||||
],
|
||||
],
|
||||
|
@@ -33,7 +33,10 @@ class Line extends NotificationProvider {
|
||||
"messages": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "UptimeKuma Alert: [🔴 Down]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"]
|
||||
"text": "UptimeKuma Alert: [🔴 Down]\n" +
|
||||
"Name: " + monitorJSON["name"] + " \n" +
|
||||
heartbeatJSON["msg"] +
|
||||
`\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -44,7 +47,10 @@ class Line extends NotificationProvider {
|
||||
"messages": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "UptimeKuma Alert: [✅ Up]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"]
|
||||
"text": "UptimeKuma Alert: [✅ Up]\n" +
|
||||
"Name: " + monitorJSON["name"] + " \n" +
|
||||
heartbeatJSON["msg"] +
|
||||
`\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`
|
||||
}
|
||||
]
|
||||
};
|
||||
|
@@ -24,12 +24,18 @@ class LineNotify extends NotificationProvider {
|
||||
await axios.post(lineAPIUrl, qs.stringify(testMessage), config);
|
||||
} else if (heartbeatJSON["status"] === DOWN) {
|
||||
let downMessage = {
|
||||
"message": "\n[🔴 Down]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"]
|
||||
"message": "\n[🔴 Down]\n" +
|
||||
"Name: " + monitorJSON["name"] + " \n" +
|
||||
heartbeatJSON["msg"] + "\n" +
|
||||
`Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`
|
||||
};
|
||||
await axios.post(lineAPIUrl, qs.stringify(downMessage), config);
|
||||
} else if (heartbeatJSON["status"] === UP) {
|
||||
let upMessage = {
|
||||
"message": "\n[✅ Up]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"]
|
||||
"message": "\n[✅ Up]\n" +
|
||||
"Name: " + monitorJSON["name"] + " \n" +
|
||||
heartbeatJSON["msg"] + "\n" +
|
||||
`Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`
|
||||
};
|
||||
await axios.post(lineAPIUrl, qs.stringify(upMessage), config);
|
||||
}
|
||||
|
@@ -28,7 +28,9 @@ class LunaSea extends NotificationProvider {
|
||||
if (heartbeatJSON["status"] === DOWN) {
|
||||
let downdata = {
|
||||
"title": "UptimeKuma Alert: " + monitorJSON["name"],
|
||||
"body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"],
|
||||
"body": "[🔴 Down] " +
|
||||
heartbeatJSON["msg"] +
|
||||
`\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`
|
||||
};
|
||||
await axios.post(lunaseaurl, downdata);
|
||||
return okMsg;
|
||||
@@ -37,7 +39,9 @@ class LunaSea extends NotificationProvider {
|
||||
if (heartbeatJSON["status"] === UP) {
|
||||
let updata = {
|
||||
"title": "UptimeKuma Alert: " + monitorJSON["name"],
|
||||
"body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"],
|
||||
"body": "[✅ Up] " +
|
||||
heartbeatJSON["msg"] +
|
||||
`\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`
|
||||
};
|
||||
await axios.post(lunaseaurl, updata);
|
||||
return okMsg;
|
||||
|
@@ -10,7 +10,7 @@ class Mattermost extends NotificationProvider {
|
||||
let okMsg = "Sent Successfully.";
|
||||
try {
|
||||
const mattermostUserName = notification.mattermostusername || "Uptime Kuma";
|
||||
// If heartbeatJSON is null, assume we're testing.
|
||||
// If heartbeatJSON is null, assume non monitoring notification (Certificate warning) or testing.
|
||||
if (heartbeatJSON == null) {
|
||||
let mattermostTestData = {
|
||||
username: mattermostUserName,
|
||||
@@ -27,97 +27,79 @@ class Mattermost extends NotificationProvider {
|
||||
}
|
||||
|
||||
const mattermostIconEmoji = notification.mattermosticonemo;
|
||||
const mattermostIconUrl = notification.mattermosticonurl;
|
||||
let mattermostIconEmojiOnline = "";
|
||||
let mattermostIconEmojiOffline = "";
|
||||
|
||||
if (heartbeatJSON["status"] === DOWN) {
|
||||
let mattermostdowndata = {
|
||||
username: mattermostUserName,
|
||||
text: "Uptime Kuma Alert",
|
||||
channel: mattermostChannel,
|
||||
icon_emoji: mattermostIconEmoji,
|
||||
icon_url: mattermostIconUrl,
|
||||
attachments: [
|
||||
{
|
||||
fallback:
|
||||
"Your " +
|
||||
monitorJSON["name"] +
|
||||
" service went down.",
|
||||
color: "#FF0000",
|
||||
title:
|
||||
"❌ " +
|
||||
monitorJSON["name"] +
|
||||
" service went down. ❌",
|
||||
title_link: monitorJSON["url"],
|
||||
fields: [
|
||||
{
|
||||
short: true,
|
||||
title: "Service Name",
|
||||
value: monitorJSON["name"],
|
||||
},
|
||||
{
|
||||
short: true,
|
||||
title: "Time (UTC)",
|
||||
value: heartbeatJSON["time"],
|
||||
},
|
||||
{
|
||||
short: false,
|
||||
title: "Error",
|
||||
value: heartbeatJSON["msg"],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
await axios.post(
|
||||
notification.mattermostWebhookUrl,
|
||||
mattermostdowndata
|
||||
);
|
||||
return okMsg;
|
||||
} else if (heartbeatJSON["status"] === UP) {
|
||||
let mattermostupdata = {
|
||||
username: mattermostUserName,
|
||||
text: "Uptime Kuma Alert",
|
||||
channel: mattermostChannel,
|
||||
icon_emoji: mattermostIconEmoji,
|
||||
icon_url: mattermostIconUrl,
|
||||
attachments: [
|
||||
{
|
||||
fallback:
|
||||
"Your " +
|
||||
monitorJSON["name"] +
|
||||
" service went up!",
|
||||
color: "#32CD32",
|
||||
title:
|
||||
"✅ " +
|
||||
monitorJSON["name"] +
|
||||
" service went up! ✅",
|
||||
title_link: monitorJSON["url"],
|
||||
fields: [
|
||||
{
|
||||
short: true,
|
||||
title: "Service Name",
|
||||
value: monitorJSON["name"],
|
||||
},
|
||||
{
|
||||
short: true,
|
||||
title: "Time (UTC)",
|
||||
value: heartbeatJSON["time"],
|
||||
},
|
||||
{
|
||||
short: false,
|
||||
title: "Ping",
|
||||
value: heartbeatJSON["ping"] + "ms",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
await axios.post(
|
||||
notification.mattermostWebhookUrl,
|
||||
mattermostupdata
|
||||
);
|
||||
return okMsg;
|
||||
if (mattermostIconEmoji && typeof mattermostIconEmoji === "string") {
|
||||
const emojiArray = mattermostIconEmoji.split(" ");
|
||||
if (emojiArray.length >= 2) {
|
||||
mattermostIconEmojiOnline = emojiArray[0];
|
||||
mattermostIconEmojiOffline = emojiArray[1];
|
||||
}
|
||||
}
|
||||
const mattermostIconUrl = notification.mattermosticonurl;
|
||||
let iconEmoji = mattermostIconEmoji;
|
||||
let statusField = {
|
||||
short: false,
|
||||
title: "Error",
|
||||
value: heartbeatJSON.msg,
|
||||
};
|
||||
let statusText = "unknown";
|
||||
let color = "#000000";
|
||||
if (heartbeatJSON.status === DOWN) {
|
||||
iconEmoji = mattermostIconEmojiOffline || mattermostIconEmoji;
|
||||
statusField = {
|
||||
short: false,
|
||||
title: "Error",
|
||||
value: heartbeatJSON.msg,
|
||||
};
|
||||
statusText = "down.";
|
||||
color = "#FF0000";
|
||||
} else if (heartbeatJSON.status === UP) {
|
||||
iconEmoji = mattermostIconEmojiOnline || mattermostIconEmoji;
|
||||
statusField = {
|
||||
short: false,
|
||||
title: "Ping",
|
||||
value: heartbeatJSON.ping + "ms",
|
||||
};
|
||||
statusText = "up!";
|
||||
color = "#32CD32";
|
||||
}
|
||||
|
||||
let mattermostdata = {
|
||||
username: monitorJSON.name + " " + mattermostUserName,
|
||||
channel: mattermostChannel,
|
||||
icon_emoji: iconEmoji,
|
||||
icon_url: mattermostIconUrl,
|
||||
attachments: [
|
||||
{
|
||||
fallback:
|
||||
"Your " +
|
||||
monitorJSON.name +
|
||||
" service went " +
|
||||
statusText,
|
||||
color: color,
|
||||
title:
|
||||
monitorJSON.name +
|
||||
" service went " +
|
||||
statusText,
|
||||
title_link: monitorJSON.url,
|
||||
fields: [
|
||||
statusField,
|
||||
{
|
||||
short: true,
|
||||
title: `Time (${heartbeatJSON["timezone"]})`,
|
||||
value: heartbeatJSON.localDateTime,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
await axios.post(
|
||||
notification.mattermostWebhookUrl,
|
||||
mattermostdata
|
||||
);
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
const { DOWN, UP } = require("../../src/util");
|
||||
|
||||
class Ntfy extends NotificationProvider {
|
||||
|
||||
@@ -9,16 +10,54 @@ class Ntfy extends NotificationProvider {
|
||||
let okMsg = "Sent Successfully.";
|
||||
try {
|
||||
let headers = {};
|
||||
if (notification.ntfyusername) {
|
||||
if (notification.ntfyAuthenticationMethod === "usernamePassword") {
|
||||
headers = {
|
||||
"Authorization": "Basic " + Buffer.from(notification.ntfyusername + ":" + notification.ntfypassword).toString("base64"),
|
||||
};
|
||||
} else if (notification.ntfyAuthenticationMethod === "accessToken") {
|
||||
headers = {
|
||||
"Authorization": "Bearer " + notification.ntfyaccesstoken,
|
||||
};
|
||||
}
|
||||
// If heartbeatJSON is null, assume non monitoring notification (Certificate warning) or testing.
|
||||
if (heartbeatJSON == null) {
|
||||
let ntfyTestData = {
|
||||
"topic": notification.ntfytopic,
|
||||
"title": (monitorJSON?.name || notification.ntfytopic) + " [Uptime-Kuma]",
|
||||
"message": msg,
|
||||
"priority": notification.ntfyPriority,
|
||||
"tags": [ "test_tube" ],
|
||||
};
|
||||
await axios.post(`${notification.ntfyserverurl}`, ntfyTestData, { headers: headers });
|
||||
return okMsg;
|
||||
}
|
||||
let tags = [];
|
||||
let status = "unknown";
|
||||
let priority = notification.ntfyPriority || 4;
|
||||
if ("status" in heartbeatJSON) {
|
||||
if (heartbeatJSON.status === DOWN) {
|
||||
tags = [ "red_circle" ];
|
||||
status = "Down";
|
||||
// if priority is not 5, increase priority for down alerts
|
||||
priority = priority === 5 ? priority : priority + 1;
|
||||
} else if (heartbeatJSON["status"] === UP) {
|
||||
tags = [ "green_circle" ];
|
||||
status = "Up";
|
||||
}
|
||||
}
|
||||
let data = {
|
||||
"topic": notification.ntfytopic,
|
||||
"message": msg,
|
||||
"priority": notification.ntfyPriority || 4,
|
||||
"title": "Uptime-Kuma",
|
||||
"message": heartbeatJSON.msg,
|
||||
"priority": priority,
|
||||
"title": monitorJSON.name + " " + status + " [Uptime-Kuma]",
|
||||
"tags": tags,
|
||||
"actions": [
|
||||
{
|
||||
"action": "view",
|
||||
"label": "Open " + monitorJSON.name,
|
||||
"url": monitorJSON.url,
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
if (notification.ntfyIcon) {
|
||||
|
97
server/notification-providers/opsgenie.js
Normal file
97
server/notification-providers/opsgenie.js
Normal file
@@ -0,0 +1,97 @@
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
const { UP, DOWN } = require("../../src/util");
|
||||
|
||||
const opsgenieAlertsUrlEU = "https://api.eu.opsgenie.com/v2/alerts";
|
||||
const opsgenieAlertsUrlUS = "https://api.opsgenie.com/v2/alerts";
|
||||
let okMsg = "Sent Successfully.";
|
||||
|
||||
class Opsgenie extends NotificationProvider {
|
||||
|
||||
name = "Opsgenie";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
let opsgenieAlertsUrl;
|
||||
let priority = (!notification.opsgeniePriority) ? 3 : notification.opsgeniePriority;
|
||||
const textMsg = "Uptime Kuma Alert";
|
||||
|
||||
try {
|
||||
switch (notification.opsgenieRegion) {
|
||||
case "US":
|
||||
opsgenieAlertsUrl = opsgenieAlertsUrlUS;
|
||||
break;
|
||||
case "EU":
|
||||
opsgenieAlertsUrl = opsgenieAlertsUrlEU;
|
||||
break;
|
||||
default:
|
||||
opsgenieAlertsUrl = opsgenieAlertsUrlUS;
|
||||
}
|
||||
|
||||
if (heartbeatJSON == null) {
|
||||
let notificationTestAlias = "uptime-kuma-notification-test";
|
||||
let data = {
|
||||
"message": msg,
|
||||
"alias": notificationTestAlias,
|
||||
"source": "Uptime Kuma",
|
||||
"priority": "P5"
|
||||
};
|
||||
|
||||
return this.post(notification, opsgenieAlertsUrl, data);
|
||||
}
|
||||
|
||||
if (heartbeatJSON.status === DOWN) {
|
||||
let data = {
|
||||
"message": monitorJSON ? textMsg + `: ${monitorJSON.name}` : textMsg,
|
||||
"alias": monitorJSON.name,
|
||||
"description": msg,
|
||||
"source": "Uptime Kuma",
|
||||
"priority": `P${priority}`
|
||||
};
|
||||
|
||||
return this.post(notification, opsgenieAlertsUrl, data);
|
||||
}
|
||||
|
||||
if (heartbeatJSON.status === UP) {
|
||||
let opsgenieAlertsCloseUrl = `${opsgenieAlertsUrl}/${encodeURIComponent(monitorJSON.name)}/close?identifierType=alias`;
|
||||
let data = {
|
||||
"source": "Uptime Kuma",
|
||||
};
|
||||
|
||||
return this.post(notification, opsgenieAlertsCloseUrl, data);
|
||||
}
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {BeanModel} notification
|
||||
* @param {string} url Request url
|
||||
* @param {Object} data Request body
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async post(notification, url, data) {
|
||||
let config = {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `GenieKey ${notification.opsgenieApiKey}`,
|
||||
}
|
||||
};
|
||||
|
||||
let res = await axios.post(url, data, config);
|
||||
if (res.status == null) {
|
||||
return "Opsgenie notification failed with invalid response!";
|
||||
}
|
||||
if (res.status < 200 || res.status >= 300) {
|
||||
return `Opsgenie notification failed with status code ${res.status}`;
|
||||
}
|
||||
|
||||
return okMsg;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Opsgenie;
|
@@ -29,14 +29,18 @@ class Pushbullet extends NotificationProvider {
|
||||
let downData = {
|
||||
"type": "note",
|
||||
"title": "UptimeKuma Alert: " + monitorJSON["name"],
|
||||
"body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"],
|
||||
"body": "[🔴 Down] " +
|
||||
heartbeatJSON["msg"] +
|
||||
`\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`,
|
||||
};
|
||||
await axios.post(pushbulletUrl, downData, config);
|
||||
} else if (heartbeatJSON["status"] === UP) {
|
||||
let upData = {
|
||||
"type": "note",
|
||||
"title": "UptimeKuma Alert: " + monitorJSON["name"],
|
||||
"body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"],
|
||||
"body": "[✅ Up] " +
|
||||
heartbeatJSON["msg"] +
|
||||
`\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`,
|
||||
};
|
||||
await axios.post(pushbulletUrl, upData, config);
|
||||
}
|
||||
|
@@ -24,13 +24,16 @@ class Pushover extends NotificationProvider {
|
||||
if (notification.pushoverdevice) {
|
||||
data.device = notification.pushoverdevice;
|
||||
}
|
||||
if (notification.pushoverttl) {
|
||||
data.ttl = notification.pushoverttl;
|
||||
}
|
||||
|
||||
try {
|
||||
if (heartbeatJSON == null) {
|
||||
await axios.post(pushoverlink, data);
|
||||
return okMsg;
|
||||
} else {
|
||||
data.message += "\n<b>Time (UTC)</b>:" + heartbeatJSON["time"];
|
||||
data.message += `\n<b>Time (${heartbeatJSON["timezone"]})</b>:${heartbeatJSON["localDateTime"]}`;
|
||||
await axios.post(pushoverlink, data);
|
||||
return okMsg;
|
||||
}
|
||||
|
@@ -22,8 +22,6 @@ class RocketChat extends NotificationProvider {
|
||||
return okMsg;
|
||||
}
|
||||
|
||||
const time = heartbeatJSON["time"];
|
||||
|
||||
let data = {
|
||||
"text": "Uptime Kuma Alert",
|
||||
"channel": notification.rocketchannel,
|
||||
@@ -31,7 +29,7 @@ class RocketChat extends NotificationProvider {
|
||||
"icon_emoji": notification.rocketiconemo,
|
||||
"attachments": [
|
||||
{
|
||||
"title": "Uptime Kuma Alert *Time (UTC)*\n" + time,
|
||||
"title": `Uptime Kuma Alert *Time (${heartbeatJSON["timezone"]})*\n${heartbeatJSON["localDateTime"]}`,
|
||||
"text": "*Message*\n" + msg,
|
||||
}
|
||||
]
|
||||
|
@@ -39,7 +39,6 @@ class Slack extends NotificationProvider {
|
||||
return okMsg;
|
||||
}
|
||||
|
||||
const time = heartbeatJSON["time"];
|
||||
const textMsg = "Uptime Kuma Alert";
|
||||
let data = {
|
||||
"text": `${textMsg}\n${msg}`,
|
||||
@@ -65,7 +64,7 @@ class Slack extends NotificationProvider {
|
||||
},
|
||||
{
|
||||
"type": "mrkdwn",
|
||||
"text": "*Time (UTC)*\n" + time,
|
||||
"text": `*Time (${heartbeatJSON["timezone"]})*\n${heartbeatJSON["localDateTime"]}`,
|
||||
}],
|
||||
}
|
||||
],
|
||||
|
@@ -91,7 +91,7 @@ class SMTP extends NotificationProvider {
|
||||
|
||||
let bodyTextContent = msg;
|
||||
if (heartbeatJSON) {
|
||||
bodyTextContent = `${msg}\nTime (UTC): ${heartbeatJSON["time"]}`;
|
||||
bodyTextContent = `${msg}\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`;
|
||||
}
|
||||
|
||||
// send mail with defined transport object
|
||||
|
@@ -25,8 +25,11 @@ class Telegram extends NotificationProvider {
|
||||
return okMsg;
|
||||
|
||||
} catch (error) {
|
||||
let msg = (error.response.data.description) ? error.response.data.description : "Error without description";
|
||||
throw new Error(msg);
|
||||
if (error.response && error.response.data && error.response.data.description) {
|
||||
throw new Error(error.response.data.description);
|
||||
} else {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
41
server/notification-providers/twilio.js
Normal file
41
server/notification-providers/twilio.js
Normal file
@@ -0,0 +1,41 @@
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
|
||||
class Twilio extends NotificationProvider {
|
||||
|
||||
name = "twilio";
|
||||
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
|
||||
let okMsg = "Sent Successfully.";
|
||||
|
||||
let accountSID = notification.twilioAccountSID;
|
||||
let authToken = notification.twilioAuthToken;
|
||||
|
||||
try {
|
||||
|
||||
let config = {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
|
||||
"Authorization": "Basic " + Buffer.from(accountSID + ":" + authToken).toString("base64"),
|
||||
}
|
||||
};
|
||||
|
||||
let data = new URLSearchParams();
|
||||
data.append("To", notification.twilioToNumber);
|
||||
data.append("From", notification.twilioFromNumber);
|
||||
data.append("Body", msg);
|
||||
|
||||
let url = "https://api.twilio.com/2010-04-01/Accounts/" + accountSID + "/Messages.json";
|
||||
|
||||
await axios.post(url, data, config);
|
||||
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Twilio;
|
@@ -23,6 +23,7 @@ const Mattermost = require("./notification-providers/mattermost");
|
||||
const Ntfy = require("./notification-providers/ntfy");
|
||||
const Octopush = require("./notification-providers/octopush");
|
||||
const OneBot = require("./notification-providers/onebot");
|
||||
const Opsgenie = require("./notification-providers/opsgenie");
|
||||
const PagerDuty = require("./notification-providers/pagerduty");
|
||||
const PagerTree = require("./notification-providers/pagertree");
|
||||
const PromoSMS = require("./notification-providers/promosms");
|
||||
@@ -41,6 +42,7 @@ 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 Twilio = require("./notification-providers/twilio");
|
||||
const Splunk = require("./notification-providers/splunk");
|
||||
const Webhook = require("./notification-providers/webhook");
|
||||
const WeCom = require("./notification-providers/wecom");
|
||||
@@ -83,6 +85,7 @@ class Notification {
|
||||
new Ntfy(),
|
||||
new Octopush(),
|
||||
new OneBot(),
|
||||
new Opsgenie(),
|
||||
new PagerDuty(),
|
||||
new PagerTree(),
|
||||
new PromoSMS(),
|
||||
@@ -103,6 +106,7 @@ class Notification {
|
||||
new Teams(),
|
||||
new TechulusPush(),
|
||||
new Telegram(),
|
||||
new Twilio(),
|
||||
new Splunk(),
|
||||
new Webhook(),
|
||||
new WeCom(),
|
||||
|
@@ -132,6 +132,9 @@ class Proxy {
|
||||
...httpAgentOptions,
|
||||
...httpsAgentOptions,
|
||||
...proxyOptions,
|
||||
tls: {
|
||||
rejectUnauthorized: httpsAgentOptions.rejectUnauthorized,
|
||||
},
|
||||
});
|
||||
|
||||
httpAgent = agent;
|
||||
|
@@ -10,6 +10,7 @@ const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||
const { UptimeCacheList } = require("../uptime-cache-list");
|
||||
const { makeBadge } = require("badge-maker");
|
||||
const { badgeConstants } = require("../config");
|
||||
const { Prometheus } = require("../prometheus");
|
||||
|
||||
let router = express.Router();
|
||||
|
||||
@@ -37,7 +38,7 @@ router.get("/api/push/:pushToken", async (request, response) => {
|
||||
|
||||
let pushToken = request.params.pushToken;
|
||||
let msg = request.query.msg || "OK";
|
||||
let ping = request.query.ping || null;
|
||||
let ping = parseInt(request.query.ping) || null;
|
||||
let statusString = request.query.status || "up";
|
||||
let status = (statusString === "up") ? UP : DOWN;
|
||||
|
||||
@@ -89,6 +90,7 @@ router.get("/api/push/:pushToken", async (request, response) => {
|
||||
io.to(monitor.user_id).emit("heartbeat", bean.toJSON());
|
||||
UptimeCacheList.clearCache(monitor.id);
|
||||
Monitor.sendStats(io, monitor.id, monitor.user_id);
|
||||
new Prometheus(monitor).update(bean, undefined);
|
||||
|
||||
response.json({
|
||||
ok: true,
|
||||
@@ -147,7 +149,11 @@ router.get("/api/badge/:id/status", cache("5 minutes"), async (request, response
|
||||
const heartbeat = await Monitor.getPreviousHeartbeat(requestedMonitorId);
|
||||
const state = overrideValue !== undefined ? overrideValue : heartbeat.status;
|
||||
|
||||
badgeValues.label = label ?? "Status";
|
||||
if (label === undefined) {
|
||||
badgeValues.label = "Status";
|
||||
} else {
|
||||
badgeValues.label = label;
|
||||
}
|
||||
switch (state) {
|
||||
case DOWN:
|
||||
badgeValues.color = downColor;
|
||||
@@ -224,7 +230,7 @@ router.get("/api/badge/:id/uptime/:duration?", cache("5 minutes"), async (reques
|
||||
);
|
||||
|
||||
// limit the displayed uptime percentage to four (two, when displayed as percent) decimal digits
|
||||
const cleanUptime = parseFloat(uptime.toPrecision(4));
|
||||
const cleanUptime = (uptime * 100).toPrecision(4);
|
||||
|
||||
// use a given, custom color or calculate one based on the uptime value
|
||||
badgeValues.color = color ?? percentageToColor(uptime);
|
||||
@@ -235,7 +241,7 @@ router.get("/api/badge/:id/uptime/:duration?", cache("5 minutes"), async (reques
|
||||
labelPrefix,
|
||||
label ?? `Uptime (${requestedDuration}${labelSuffix})`,
|
||||
]);
|
||||
badgeValues.message = filterAndJoin([ prefix, `${cleanUptime * 100}`, suffix ]);
|
||||
badgeValues.message = filterAndJoin([ prefix, cleanUptime, suffix ]);
|
||||
}
|
||||
|
||||
// build the SVG based on given values
|
||||
|
@@ -19,6 +19,11 @@ const nodeVersion = parseInt(process.versions.node.split(".")[0]);
|
||||
const requiredVersion = 14;
|
||||
console.log(`Your Node.js version: ${nodeVersion}`);
|
||||
|
||||
// See more: https://github.com/louislam/uptime-kuma/issues/3138
|
||||
if (nodeVersion >= 20) {
|
||||
console.warn("\x1b[31m%s\x1b[0m", "Warning: Uptime Kuma is currently not stable on Node.js >= 20, please use Node.js 18.");
|
||||
}
|
||||
|
||||
if (nodeVersion < requiredVersion) {
|
||||
console.error(`Error: Your Node.js version is not supported, please upgrade to Node.js >= ${requiredVersion}.`);
|
||||
process.exit(-1);
|
||||
@@ -679,8 +684,17 @@ let needSetup = false;
|
||||
throw new Error("Permission denied.");
|
||||
}
|
||||
|
||||
// Check if Parent is Decendant (would cause endless loop)
|
||||
if (monitor.parent !== null) {
|
||||
const childIDs = await Monitor.getAllChildrenIDs(monitor.id);
|
||||
if (childIDs.includes(monitor.parent)) {
|
||||
throw new Error("Invalid Monitor Group");
|
||||
}
|
||||
}
|
||||
|
||||
bean.name = monitor.name;
|
||||
bean.description = monitor.description;
|
||||
bean.parent = monitor.parent;
|
||||
bean.type = monitor.type;
|
||||
bean.url = monitor.url;
|
||||
bean.method = monitor.method;
|
||||
@@ -740,7 +754,7 @@ let needSetup = false;
|
||||
|
||||
await updateMonitorNotification(bean.id, monitor.notificationIDList);
|
||||
|
||||
if (bean.active) {
|
||||
if (bean.isActive()) {
|
||||
await restartMonitor(socket.userID, bean.id);
|
||||
}
|
||||
|
||||
@@ -1557,7 +1571,7 @@ let needSetup = false;
|
||||
}
|
||||
});
|
||||
|
||||
initBackgroundJobs(args);
|
||||
await initBackgroundJobs();
|
||||
|
||||
// Start cloudflared at the end if configured
|
||||
await cloudflaredAutoStart(cloudflaredToken);
|
||||
|
@@ -5,7 +5,6 @@ const apicache = require("../modules/apicache");
|
||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||
const Maintenance = require("../model/maintenance");
|
||||
const server = UptimeKumaServer.getInstance();
|
||||
const MaintenanceTimeslot = require("../model/maintenance_timeslot");
|
||||
|
||||
/**
|
||||
* Handlers for Maintenance
|
||||
@@ -19,10 +18,12 @@ module.exports.maintenanceSocketHandler = (socket) => {
|
||||
|
||||
log.debug("maintenance", maintenance);
|
||||
|
||||
let bean = Maintenance.jsonToBean(R.dispense("maintenance"), maintenance);
|
||||
let bean = await Maintenance.jsonToBean(R.dispense("maintenance"), maintenance);
|
||||
bean.user_id = socket.userID;
|
||||
let maintenanceID = await R.store(bean);
|
||||
await MaintenanceTimeslot.generateTimeslot(bean);
|
||||
|
||||
server.maintenanceList[maintenanceID] = bean;
|
||||
await bean.run(true);
|
||||
|
||||
await server.sendMaintenanceList(socket);
|
||||
|
||||
@@ -45,17 +46,15 @@ module.exports.maintenanceSocketHandler = (socket) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
|
||||
let bean = await R.findOne("maintenance", " id = ? ", [ maintenance.id ]);
|
||||
let bean = server.getMaintenance(maintenance.id);
|
||||
|
||||
if (bean.user_id !== socket.userID) {
|
||||
throw new Error("Permission denied.");
|
||||
}
|
||||
|
||||
Maintenance.jsonToBean(bean, maintenance);
|
||||
|
||||
await Maintenance.jsonToBean(bean, maintenance);
|
||||
await R.store(bean);
|
||||
await MaintenanceTimeslot.generateTimeslot(bean, null, true);
|
||||
|
||||
await bean.run(true);
|
||||
await server.sendMaintenanceList(socket);
|
||||
|
||||
callback({
|
||||
@@ -187,7 +186,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
|
||||
|
||||
log.debug("maintenance", `Get Monitors for Maintenance: ${maintenanceID} User ID: ${socket.userID}`);
|
||||
|
||||
let monitors = await R.getAll("SELECT monitor.id, monitor.name FROM monitor_maintenance mm JOIN monitor ON mm.monitor_id = monitor.id WHERE mm.maintenance_id = ? ", [
|
||||
let monitors = await R.getAll("SELECT monitor.id FROM monitor_maintenance mm JOIN monitor ON mm.monitor_id = monitor.id WHERE mm.maintenance_id = ? ", [
|
||||
maintenanceID,
|
||||
]);
|
||||
|
||||
@@ -236,6 +235,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
|
||||
log.debug("maintenance", `Delete Maintenance: ${maintenanceID} User ID: ${socket.userID}`);
|
||||
|
||||
if (maintenanceID in server.maintenanceList) {
|
||||
server.maintenanceList[maintenanceID].stop();
|
||||
delete server.maintenanceList[maintenanceID];
|
||||
}
|
||||
|
||||
@@ -267,9 +267,15 @@ module.exports.maintenanceSocketHandler = (socket) => {
|
||||
|
||||
log.debug("maintenance", `Pause Maintenance: ${maintenanceID} User ID: ${socket.userID}`);
|
||||
|
||||
await R.exec("UPDATE maintenance SET active = 0 WHERE id = ? ", [
|
||||
maintenanceID,
|
||||
]);
|
||||
let maintenance = server.getMaintenance(maintenanceID);
|
||||
|
||||
if (!maintenance) {
|
||||
throw new Error("Maintenance not found");
|
||||
}
|
||||
|
||||
maintenance.active = false;
|
||||
await R.store(maintenance);
|
||||
maintenance.stop();
|
||||
|
||||
apicache.clear();
|
||||
|
||||
@@ -294,9 +300,15 @@ module.exports.maintenanceSocketHandler = (socket) => {
|
||||
|
||||
log.debug("maintenance", `Resume Maintenance: ${maintenanceID} User ID: ${socket.userID}`);
|
||||
|
||||
await R.exec("UPDATE maintenance SET active = 1 WHERE id = ? ", [
|
||||
maintenanceID,
|
||||
]);
|
||||
let maintenance = server.getMaintenance(maintenanceID);
|
||||
|
||||
if (!maintenance) {
|
||||
throw new Error("Maintenance not found");
|
||||
}
|
||||
|
||||
maintenance.active = true;
|
||||
await R.store(maintenance);
|
||||
await maintenance.run();
|
||||
|
||||
apicache.clear();
|
||||
|
||||
|
@@ -276,7 +276,7 @@ module.exports.statusPageSocketHandler = (socket) => {
|
||||
let statusPage = R.dispense("status_page");
|
||||
statusPage.slug = slug;
|
||||
statusPage.title = title;
|
||||
statusPage.theme = "light";
|
||||
statusPage.theme = "auto";
|
||||
statusPage.icon = "";
|
||||
await R.store(statusPage);
|
||||
|
||||
|
@@ -47,8 +47,6 @@ class UptimeKumaServer {
|
||||
*/
|
||||
indexHTML = "";
|
||||
|
||||
generateMaintenanceTimeslotsInterval = undefined;
|
||||
|
||||
/**
|
||||
* Plugins Manager
|
||||
* @type {PluginsManager}
|
||||
@@ -74,6 +72,7 @@ class UptimeKumaServer {
|
||||
// SSL
|
||||
const sslKey = args["ssl-key"] || process.env.UPTIME_KUMA_SSL_KEY || process.env.SSL_KEY || undefined;
|
||||
const sslCert = args["ssl-cert"] || process.env.UPTIME_KUMA_SSL_CERT || process.env.SSL_CERT || undefined;
|
||||
const sslKeyPassphrase = args["ssl-key-passphrase"] || process.env.UPTIME_KUMA_SSL_KEY_PASSPHRASE || process.env.SSL_KEY_PASSPHRASE || undefined;
|
||||
|
||||
log.info("server", "Creating express and socket.io instance");
|
||||
this.app = express();
|
||||
@@ -81,7 +80,8 @@ class UptimeKumaServer {
|
||||
log.info("server", "Server Type: HTTPS");
|
||||
this.httpServer = https.createServer({
|
||||
key: fs.readFileSync(sslKey),
|
||||
cert: fs.readFileSync(sslCert)
|
||||
cert: fs.readFileSync(sslCert),
|
||||
passphrase: sslKeyPassphrase,
|
||||
}, this.app);
|
||||
} else {
|
||||
log.info("server", "Server Type: HTTP");
|
||||
@@ -110,8 +110,7 @@ class UptimeKumaServer {
|
||||
log.debug("DEBUG", "Timezone: " + process.env.TZ);
|
||||
log.debug("DEBUG", "Current Time: " + dayjs.tz().format());
|
||||
|
||||
await this.generateMaintenanceTimeslots();
|
||||
this.generateMaintenanceTimeslotsInterval = setInterval(this.generateMaintenanceTimeslots, 60 * 1000);
|
||||
await this.loadMaintenanceList();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -173,16 +172,33 @@ class UptimeKumaServer {
|
||||
*/
|
||||
async getMaintenanceJSONList(userID) {
|
||||
let result = {};
|
||||
for (let maintenanceID in this.maintenanceList) {
|
||||
result[maintenanceID] = await this.maintenanceList[maintenanceID].toJSON();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load maintenance list and run
|
||||
* @param userID
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async loadMaintenanceList(userID) {
|
||||
let maintenanceList = await R.findAll("maintenance", " ORDER BY end_date DESC, title", [
|
||||
|
||||
let maintenanceList = await R.find("maintenance", " user_id = ? ORDER BY end_date DESC, title", [
|
||||
userID,
|
||||
]);
|
||||
|
||||
for (let maintenance of maintenanceList) {
|
||||
result[maintenance.id] = await maintenance.toJSON();
|
||||
this.maintenanceList[maintenance.id] = maintenance;
|
||||
maintenance.run(this);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
getMaintenance(maintenanceID) {
|
||||
if (this.maintenanceList[maintenanceID]) {
|
||||
return this.maintenanceList[maintenanceID];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -238,7 +254,7 @@ class UptimeKumaServer {
|
||||
* Attempt to get the current server timezone
|
||||
* If this fails, fall back to environment variables and then make a
|
||||
* guess.
|
||||
* @returns {string}
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async getTimezone() {
|
||||
let timezone = await Settings.get("serverTimezone");
|
||||
@@ -269,28 +285,9 @@ class UptimeKumaServer {
|
||||
dayjs.tz.setDefault(timezone);
|
||||
}
|
||||
|
||||
/** Load the timeslots for maintenance */
|
||||
async generateMaintenanceTimeslots() {
|
||||
log.debug("maintenance", "Routine: Generating Maintenance Timeslots");
|
||||
|
||||
// Prevent #2776
|
||||
// Remove duplicate maintenance_timeslot with same start_date, end_date and maintenance_id
|
||||
await R.exec("DELETE FROM maintenance_timeslot WHERE id NOT IN (SELECT MIN(id) FROM maintenance_timeslot GROUP BY start_date, end_date, maintenance_id)");
|
||||
|
||||
let list = await R.find("maintenance_timeslot", " generated_next = 0 AND start_date <= DATETIME('now') ");
|
||||
|
||||
for (let maintenanceTimeslot of list) {
|
||||
let maintenance = await maintenanceTimeslot.maintenance;
|
||||
await MaintenanceTimeslot.generateTimeslot(maintenance, maintenanceTimeslot.end_date, false);
|
||||
maintenanceTimeslot.generated_next = true;
|
||||
await R.store(maintenanceTimeslot);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Stop the server */
|
||||
async stop() {
|
||||
clearTimeout(this.generateMaintenanceTimeslotsInterval);
|
||||
|
||||
}
|
||||
|
||||
loadPlugins() {
|
||||
@@ -339,5 +336,4 @@ module.exports = {
|
||||
};
|
||||
|
||||
// Must be at the end
|
||||
const MaintenanceTimeslot = require("./model/maintenance_timeslot");
|
||||
const { MonitorType } = require("./monitor-types/monitor-type");
|
||||
|
@@ -322,21 +322,33 @@ exports.postgresQuery = function (connectionString, query) {
|
||||
* Run a query on MySQL/MariaDB
|
||||
* @param {string} connectionString The database connection string
|
||||
* @param {string} query The query to validate the database with
|
||||
* @returns {Promise<(string[]|Object[]|Object)>}
|
||||
* @returns {Promise<(string)>}
|
||||
*/
|
||||
exports.mysqlQuery = function (connectionString, query) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const connection = mysql.createConnection(connectionString);
|
||||
connection.promise().query(query)
|
||||
.then(res => {
|
||||
resolve(res);
|
||||
})
|
||||
.catch(err => {
|
||||
|
||||
connection.on("error", (err) => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
connection.query(query, (err, res) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
})
|
||||
.finally(() => {
|
||||
} else {
|
||||
if (Array.isArray(res)) {
|
||||
resolve("Rows: " + res.length);
|
||||
} else {
|
||||
resolve("No Error, but the result is not an array. Type: " + typeof res);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
connection.end();
|
||||
} catch (_) {
|
||||
connection.destroy();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -512,12 +524,16 @@ const parseCertificateInfo = function (info) {
|
||||
|
||||
// Move up the chain until loop is encountered
|
||||
if (link.issuerCertificate == null) {
|
||||
link.certType = (i === 0) ? "self-signed" : "root CA";
|
||||
break;
|
||||
} else if (link.issuerCertificate.fingerprint in existingList) {
|
||||
// a root CA certificate is typically "signed by itself" (=> "self signed certificate") and thus the "issuerCertificate" is a reference to itself.
|
||||
log.debug("cert", `[Last] ${link.issuerCertificate.fingerprint}`);
|
||||
link.certType = (i === 0) ? "self-signed" : "root CA";
|
||||
link.issuerCertificate = null;
|
||||
break;
|
||||
} else {
|
||||
link.certType = (i === 0) ? "server" : "intermediate CA";
|
||||
link = link.issuerCertificate;
|
||||
}
|
||||
|
||||
|
@@ -556,6 +556,31 @@ h5.settings-subheading::after {
|
||||
border-bottom: 1px solid $dark-border-color;
|
||||
}
|
||||
|
||||
|
||||
$shadow-box-padding: 20px;
|
||||
|
||||
.shadow-box-with-fixed-bottom-bar {
|
||||
padding-top: $shadow-box-padding;
|
||||
padding-bottom: 0;
|
||||
padding-right: $shadow-box-padding;
|
||||
padding-left: $shadow-box-padding;
|
||||
}
|
||||
|
||||
.fixed-bottom-bar {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
margin-left: -$shadow-box-padding;
|
||||
margin-right: -$shadow-box-padding;
|
||||
z-index: 100;
|
||||
background-color: rgba(white, 0.2);
|
||||
backdrop-filter: blur(2px);
|
||||
border-radius: 0 0 10px 10px;
|
||||
|
||||
.dark & {
|
||||
background-color: rgba($dark-header-bg, 0.9);
|
||||
}
|
||||
}
|
||||
|
||||
// Localization
|
||||
|
||||
@import "localization.scss";
|
||||
|
@@ -1,6 +1,12 @@
|
||||
@import "vars.scss";
|
||||
@import "node_modules/vue-multiselect/dist/vue-multiselect";
|
||||
|
||||
.multiselect {
|
||||
.dark & {
|
||||
color: $dark-font-color;
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect__tags {
|
||||
border-radius: 1.5rem;
|
||||
border: 1px solid #ced4da;
|
||||
@@ -14,10 +20,12 @@
|
||||
|
||||
.multiselect__option--highlight {
|
||||
background: $primary !important;
|
||||
color: $dark-font-color2 !important;
|
||||
}
|
||||
|
||||
.multiselect__option--highlight::after {
|
||||
background: $primary !important;
|
||||
color: $dark-font-color2 !important;
|
||||
}
|
||||
|
||||
.multiselect__tag {
|
||||
|
@@ -48,15 +48,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
id="monitor-submit-btn" class="btn btn-primary" type="submit"
|
||||
:disabled="processing"
|
||||
>
|
||||
{{ $t("Generate") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
id="monitor-submit-btn" class="btn btn-primary" type="submit"
|
||||
:disabled="processing"
|
||||
>
|
||||
{{ $t("Generate") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -159,6 +158,16 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/** Clear Form inputs */
|
||||
clearForm() {
|
||||
this.key = {
|
||||
name: "",
|
||||
expires: this.minDate,
|
||||
active: 1,
|
||||
};
|
||||
this.noExpire = false;
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
299
src/components/BadgeGeneratorDialog.vue
Normal file
299
src/components/BadgeGeneratorDialog.vue
Normal file
@@ -0,0 +1,299 @@
|
||||
<template>
|
||||
<div ref="BadgeGeneratorModal" class="modal fade" tabindex="-1" data-bs-backdrop="static">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
{{ $t("Badge Generator", [monitor.name]) }}
|
||||
</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="type" class="form-label">{{ $t("Badge Type") }}</label>
|
||||
<select id="type" v-model="badge.type" class="form-select">
|
||||
<option value="status">status</option>
|
||||
<option value="uptime">uptime</option>
|
||||
<option value="ping">ping</option>
|
||||
<option value="avg-response">avg-response</option>
|
||||
<option value="cert-exp">cert-exp</option>
|
||||
<option value="response">response</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div v-if=" (parameters[badge.type || 'null'] || [] ).includes('duration') " class="mb-3">
|
||||
<label for="duration" class="form-label">{{ $t("Badge Duration") }}</label>
|
||||
<input id="duration" v-model="badge.duration" type="number" min="0" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div v-if=" (parameters[badge.type || 'null'] || [] ).includes('label') " class="mb-3">
|
||||
<label for="label" class="form-label">{{ $t("Badge Label") }}</label>
|
||||
<input id="label" v-model="badge.label" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div v-if=" (parameters[badge.type || 'null'] || [] ).includes('prefix') " class="mb-3">
|
||||
<label for="prefix" class="form-label">{{ $t("Badge Prefix") }}</label>
|
||||
<input id="prefix" v-model="badge.prefix" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div v-if=" (parameters[badge.type || 'null'] || [] ).includes('suffix') " class="mb-3">
|
||||
<label for="suffix" class="form-label">{{ $t("Badge Suffix") }}</label>
|
||||
<input id="suffix" v-model="badge.suffix" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div v-if=" (parameters[badge.type || 'null'] || [] ).includes('labelColor') " class="mb-3">
|
||||
<label for="labelColor" class="form-label">{{ $t("Badge Label Color") }}</label>
|
||||
<input id="labelColor" v-model="badge.labelColor" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div v-if=" (parameters[badge.type || 'null'] || [] ).includes('color') " class="mb-3">
|
||||
<label for="color" class="form-label">{{ $t("Badge Color") }}</label>
|
||||
<input id="color" v-model="badge.color" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div v-if=" (parameters[badge.type || 'null'] || [] ).includes('labelPrefix') " class="mb-3">
|
||||
<label for="labelPrefix" class="form-label">{{ $t("Badge Label Prefix") }}</label>
|
||||
<input id="labelPrefix" v-model="badge.labelPrefix" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div v-if=" (parameters[badge.type || 'null'] || [] ).includes('labelSuffix') " class="mb-3">
|
||||
<label for="labelSuffix" class="form-label">{{ $t("Badge Label Suffix") }}</label>
|
||||
<input id="labelSuffix" v-model="badge.labelSuffix" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div v-if=" (parameters[badge.type || 'null'] || [] ).includes('upColor') " class="mb-3">
|
||||
<label for="upColor" class="form-label">{{ $t("Badge Up Color") }}</label>
|
||||
<input id="upColor" v-model="badge.upColor" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div v-if=" (parameters[badge.type || 'null'] || [] ).includes('downColor') " class="mb-3">
|
||||
<label for="downColor" class="form-label">{{ $t("Badge Down Color") }}</label>
|
||||
<input id="downColor" v-model="badge.downColor" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div v-if=" (parameters[badge.type || 'null'] || [] ).includes('pendingColor') " class="mb-3">
|
||||
<label for="pendingColor" class="form-label">{{ $t("Badge Pending Color") }}</label>
|
||||
<input id="pendingColor" v-model="badge.pendingColor" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div v-if=" (parameters[badge.type || 'null'] || [] ).includes('maintenanceColor') " class="mb-3">
|
||||
<label for="maintenanceColor" class="form-label">{{ $t("Badge Maintenance Color") }}</label>
|
||||
<input id="maintenanceColor" v-model="badge.maintenanceColor" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div v-if=" (parameters[badge.type || 'null'] || [] ).includes('warnColor') " class="mb-3">
|
||||
<label for="warnColor" class="form-label">{{ $t("Badge Warn Color") }}</label>
|
||||
<input id="warnColor" v-model="badge.warnColor" type="number" min="0" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div v-if=" (parameters[badge.type || 'null'] || [] ).includes('warnDays') " class="mb-3">
|
||||
<label for="warnDays" class="form-label">{{ $t("Badge Warn Days") }}</label>
|
||||
<input id="warnDays" v-model="badge.warnDays" type="number" min="0" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div v-if=" (parameters[badge.type || 'null'] || [] ).includes('downDays') " class="mb-3">
|
||||
<label for="downDays" class="form-label">{{ $t("Badge Down Days") }}</label>
|
||||
<input id="downDays" v-model="badge.downDays" type="number" min="0" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="style" class="form-label">{{ $t("Badge Style") }}</label>
|
||||
<select id="style" v-model="badge.style" class="form-select">
|
||||
<option value="plastic">plastic</option>
|
||||
<option value="flat">flat</option>
|
||||
<option value="flat-square">flat-square</option>
|
||||
<option value="for-the-badge">for-the-badge</option>
|
||||
<option value="social">social</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="value" class="form-label">{{ $t("Badge value (For Testing only.)") }}</label>
|
||||
<input id="value" v-model="badge.value" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="my-3">
|
||||
<label for="push-url" class="form-label">{{ $t("Badge URL") }}</label>
|
||||
<CopyableInput id="push-url" v-model="badgeURL" type="url" disabled="disabled" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-danger" data-bs-dismiss="modal">
|
||||
{{ $t("Close") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Modal } from "bootstrap";
|
||||
import CopyableInput from "./CopyableInput.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CopyableInput
|
||||
},
|
||||
props: {},
|
||||
emits: [],
|
||||
data() {
|
||||
return {
|
||||
model: null,
|
||||
processing: false,
|
||||
monitor: {
|
||||
id: null,
|
||||
name: null,
|
||||
},
|
||||
badge: {
|
||||
type: "status",
|
||||
duration: null,
|
||||
label: null,
|
||||
prefix: null,
|
||||
suffix: null,
|
||||
labelColor: null,
|
||||
color: null,
|
||||
labelPrefix: null,
|
||||
labelSuffix: null,
|
||||
upColor: null,
|
||||
downColor: null,
|
||||
pendingColor: null,
|
||||
maintenanceColor: null,
|
||||
warnColor: null,
|
||||
warnDays: null,
|
||||
downDays: null,
|
||||
style: "flat",
|
||||
value: null,
|
||||
},
|
||||
parameters: {
|
||||
status: [
|
||||
"upLabel",
|
||||
"downLabel",
|
||||
"pendingLabel",
|
||||
"maintenanceLabel",
|
||||
"upColor",
|
||||
"downColor",
|
||||
"pendingColor",
|
||||
"maintenanceColor",
|
||||
],
|
||||
uptime: [
|
||||
"duration",
|
||||
"labelPrefix",
|
||||
"labelSuffix",
|
||||
"prefix",
|
||||
"suffix",
|
||||
"color",
|
||||
"labelColor",
|
||||
],
|
||||
ping: [
|
||||
"duration",
|
||||
"labelPrefix",
|
||||
"labelSuffix",
|
||||
"prefix",
|
||||
"suffix",
|
||||
"color",
|
||||
"labelColor",
|
||||
],
|
||||
"avg-response": [
|
||||
"duration",
|
||||
"labelPrefix",
|
||||
"labelSuffix",
|
||||
"prefix",
|
||||
"suffix",
|
||||
"color",
|
||||
"labelColor",
|
||||
],
|
||||
"cert-exp": [
|
||||
"labelPrefix",
|
||||
"labelSuffix",
|
||||
"prefix",
|
||||
"suffix",
|
||||
"upColor",
|
||||
"warnColor",
|
||||
"downColor",
|
||||
"warnDays",
|
||||
"downDays",
|
||||
"labelColor",
|
||||
],
|
||||
response: [
|
||||
"labelPrefix",
|
||||
"labelSuffix",
|
||||
"prefix",
|
||||
"suffix",
|
||||
"color",
|
||||
"labelColor",
|
||||
],
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
badgeURL() {
|
||||
if (!this.monitor.id || !this.badge.type) {
|
||||
return;
|
||||
}
|
||||
let badgeURL = this.$root.baseURL + "/api/badge/" + this.monitor.id + "/" + this.badge.type;
|
||||
|
||||
let parameterList = {};
|
||||
|
||||
for (let parameter of this.parameters[this.badge.type] || []) {
|
||||
if (parameter === "duration" && this.badge.duration) {
|
||||
badgeURL += "/" + this.badge.duration;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.badge[parameter]) {
|
||||
parameterList[parameter] = this.badge[parameter];
|
||||
}
|
||||
}
|
||||
|
||||
for (let parameter of [ "label", "style", "value" ]) {
|
||||
if (parameter === "style" && this.badge.style === "flat") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.badge[parameter]) {
|
||||
parameterList[parameter] = this.badge[parameter];
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(parameterList).length > 0) {
|
||||
return badgeURL + "?" + new URLSearchParams(parameterList);
|
||||
}
|
||||
|
||||
return badgeURL;
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.BadgeGeneratorModal = new Modal(this.$refs.BadgeGeneratorModal);
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Setting monitor
|
||||
* @param {number} monitorId ID of monitor
|
||||
* @param {string} monitorName Name of monitor
|
||||
*/
|
||||
show(monitorId, monitorName) {
|
||||
this.monitor = {
|
||||
id: monitorId,
|
||||
name: monitorName,
|
||||
};
|
||||
|
||||
this.BadgeGeneratorModal.show();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../assets/vars.scss";
|
||||
|
||||
.dark {
|
||||
.modal-dialog .form-text, .modal-dialog p {
|
||||
color: $dark-font-color;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -13,6 +13,9 @@
|
||||
:disabled="disabled"
|
||||
>
|
||||
|
||||
<!-- A hidden textarea for copying text on non-https -->
|
||||
<textarea ref="hiddenTextarea" style="position: fixed; left: -999999px; top: -999999px;"></textarea>
|
||||
|
||||
<a class="btn btn-outline-primary" @click="copyToClipboard(model)">
|
||||
<font-awesome-icon :icon="icon" />
|
||||
</a>
|
||||
@@ -111,24 +114,19 @@ export default {
|
||||
}, 3000);
|
||||
|
||||
// navigator clipboard api needs a secure context (https)
|
||||
// For http, use the text area method (else part)
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
// navigator clipboard api method'
|
||||
return navigator.clipboard.writeText(textToCopy);
|
||||
} else {
|
||||
// text area method
|
||||
let textArea = document.createElement("textarea");
|
||||
let textArea = this.$refs.hiddenTextarea;
|
||||
textArea.value = textToCopy;
|
||||
// make the textarea out of viewport
|
||||
textArea.style.position = "fixed";
|
||||
textArea.style.left = "-999999px";
|
||||
textArea.style.top = "-999999px";
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
return new Promise((res, rej) => {
|
||||
// here the magic happens
|
||||
document.execCommand("copy") ? res() : rej();
|
||||
textArea.remove();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -3,16 +3,23 @@
|
||||
<div v-if="maintenance.strategy === 'manual'" class="timeslot">
|
||||
{{ $t("Manual") }}
|
||||
</div>
|
||||
<div v-else-if="maintenance.timeslotList.length > 0" class="timeslot">
|
||||
{{ maintenance.timeslotList[0].startDateServerTimezone }}
|
||||
<span class="to">-</span>
|
||||
{{ maintenance.timeslotList[0].endDateServerTimezone }}
|
||||
(UTC{{ maintenance.timeslotList[0].serverTimezoneOffset }})
|
||||
<div v-else-if="maintenance.timeslotList.length > 0">
|
||||
<div class="timeslot">
|
||||
{{ startDateTime }}
|
||||
<span class="to">-</span>
|
||||
{{ endDateTime }}
|
||||
</div>
|
||||
<div class="timeslot">
|
||||
UTC{{ maintenance.timezoneOffset }} <span v-if="maintenance.timezone !== 'UTC'">{{ maintenance.timezone }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dayjs from "dayjs";
|
||||
import { SQL_DATETIME_FORMAT_WITHOUT_SECOND } from "../util.ts";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
maintenance: {
|
||||
@@ -20,6 +27,14 @@ export default {
|
||||
required: true
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
startDateTime() {
|
||||
return dayjs(this.maintenance.timeslotList[0].startDate).tz(this.maintenance.timezone).format(SQL_DATETIME_FORMAT_WITHOUT_SECOND);
|
||||
},
|
||||
endDateTime() {
|
||||
return dayjs(this.maintenance.timeslotList[0].endDate).tz(this.maintenance.timezone).format(SQL_DATETIME_FORMAT_WITHOUT_SECOND);
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -31,6 +46,7 @@ export default {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
border-radius: 20px;
|
||||
padding: 0 10px;
|
||||
margin-right: 5px;
|
||||
|
||||
.to {
|
||||
margin: 0 6px;
|
||||
|
@@ -19,43 +19,18 @@
|
||||
{{ $t("No Monitors, please") }} <router-link to="/add">{{ $t("add one") }}</router-link>
|
||||
</div>
|
||||
|
||||
<router-link v-for="(item, index) in sortedMonitorList" :key="index" :to="monitorURL(item.id)" class="item" :class="{ 'disabled': ! item.active }" :title="item.description">
|
||||
<div class="row">
|
||||
<div class="col-9 col-md-8 small-padding" :class="{ 'monitor-item': $root.userHeartbeatBar == 'bottom' || $root.userHeartbeatBar == 'none' }">
|
||||
<div class="info">
|
||||
<Uptime :monitor="item" type="24" :pill="true" />
|
||||
{{ item.name }}
|
||||
</div>
|
||||
<div class="tags">
|
||||
<Tag v-for="tag in item.tags" :key="tag" :item="tag" :size="'sm'" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="$root.userHeartbeatBar == 'normal'" :key="$root.userHeartbeatBar" class="col-3 col-md-4">
|
||||
<HeartbeatBar size="small" :monitor-id="item.id" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="$root.userHeartbeatBar == 'bottom'" class="row">
|
||||
<div class="col-12 bottom-style">
|
||||
<HeartbeatBar size="small" :monitor-id="item.id" />
|
||||
</div>
|
||||
</div>
|
||||
</router-link>
|
||||
<MonitorListItem v-for="(item, index) in sortedMonitorList" :key="index" :monitor="item" :isSearch="searchText !== ''" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HeartbeatBar from "../components/HeartbeatBar.vue";
|
||||
import Tag from "../components/Tag.vue";
|
||||
import Uptime from "../components/Uptime.vue";
|
||||
import MonitorListItem from "../components/MonitorListItem.vue";
|
||||
import { getMonitorRelativeURL } from "../util.ts";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Uptime,
|
||||
HeartbeatBar,
|
||||
Tag,
|
||||
MonitorListItem,
|
||||
},
|
||||
props: {
|
||||
/** Should the scrollbar be shown */
|
||||
@@ -91,6 +66,20 @@ export default {
|
||||
sortedMonitorList() {
|
||||
let result = Object.values(this.$root.monitorList);
|
||||
|
||||
// Simple filter by search text
|
||||
// finds monitor name, tag name or tag value
|
||||
if (this.searchText !== "") {
|
||||
const loweredSearchText = this.searchText.toLowerCase();
|
||||
result = result.filter(monitor => {
|
||||
return monitor.name.toLowerCase().includes(loweredSearchText)
|
||||
|| monitor.tags.find(tag => tag.name.toLowerCase().includes(loweredSearchText)
|
||||
|| tag.value?.toLowerCase().includes(loweredSearchText));
|
||||
});
|
||||
} else {
|
||||
result = result.filter(monitor => monitor.parent === null);
|
||||
}
|
||||
|
||||
// Filter result by active state, weight and alphabetical
|
||||
result.sort((m1, m2) => {
|
||||
|
||||
if (m1.active !== m2.active) {
|
||||
@@ -116,17 +105,6 @@ export default {
|
||||
return m1.name.localeCompare(m2.name);
|
||||
});
|
||||
|
||||
// Simple filter by search text
|
||||
// finds monitor name, tag name or tag value
|
||||
if (this.searchText !== "") {
|
||||
const loweredSearchText = this.searchText.toLowerCase();
|
||||
result = result.filter(monitor => {
|
||||
return monitor.name.toLowerCase().includes(loweredSearchText)
|
||||
|| monitor.tags.find(tag => tag.name.toLowerCase().includes(loweredSearchText)
|
||||
|| tag.value?.toLowerCase().includes(loweredSearchText));
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
},
|
||||
|
204
src/components/MonitorListItem.vue
Normal file
204
src/components/MonitorListItem.vue
Normal file
@@ -0,0 +1,204 @@
|
||||
<template>
|
||||
<div>
|
||||
<router-link :to="monitorURL(monitor.id)" class="item" :class="{ 'disabled': ! monitor.active }">
|
||||
<div class="row">
|
||||
<div class="col-9 col-md-8 small-padding" :class="{ 'monitor-item': $root.userHeartbeatBar == 'bottom' || $root.userHeartbeatBar == 'none' }">
|
||||
<div class="info" :style="depthMargin">
|
||||
<Uptime :monitor="monitor" type="24" :pill="true" />
|
||||
<span v-if="hasChildren" class="collapse-padding" @click.prevent="changeCollapsed">
|
||||
<font-awesome-icon icon="chevron-down" class="animated" :class="{ collapsed: isCollapsed}" />
|
||||
</span>
|
||||
{{ monitorName }}
|
||||
</div>
|
||||
<div class="tags">
|
||||
<Tag v-for="tag in monitor.tags" :key="tag" :item="tag" :size="'sm'" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="$root.userHeartbeatBar == 'normal'" :key="$root.userHeartbeatBar" class="col-3 col-md-4">
|
||||
<HeartbeatBar size="small" :monitor-id="monitor.id" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="$root.userHeartbeatBar == 'bottom'" class="row">
|
||||
<div class="col-12 bottom-style">
|
||||
<HeartbeatBar size="small" :monitor-id="monitor.id" />
|
||||
</div>
|
||||
</div>
|
||||
</router-link>
|
||||
|
||||
<transition name="slide-fade-up">
|
||||
<div v-if="!isCollapsed" class="childs">
|
||||
<MonitorListItem v-for="(item, index) in sortedChildMonitorList" :key="index" :monitor="item" :isSearch="isSearch" :depth="depth + 1" />
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HeartbeatBar from "../components/HeartbeatBar.vue";
|
||||
import Tag from "../components/Tag.vue";
|
||||
import Uptime from "../components/Uptime.vue";
|
||||
import { getMonitorRelativeURL } from "../util.ts";
|
||||
|
||||
export default {
|
||||
name: "MonitorListItem",
|
||||
components: {
|
||||
Uptime,
|
||||
HeartbeatBar,
|
||||
Tag,
|
||||
},
|
||||
props: {
|
||||
/** Monitor this represents */
|
||||
monitor: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
/** If the user is currently searching */
|
||||
isSearch: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
/** How many ancestors are above this monitor */
|
||||
depth: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isCollapsed: true,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
sortedChildMonitorList() {
|
||||
let result = Object.values(this.$root.monitorList);
|
||||
|
||||
result = result.filter(childMonitor => childMonitor.parent === this.monitor.id);
|
||||
|
||||
result.sort((m1, m2) => {
|
||||
|
||||
if (m1.active !== m2.active) {
|
||||
if (m1.active === 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (m2.active === 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (m1.weight !== m2.weight) {
|
||||
if (m1.weight > m2.weight) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (m1.weight < m2.weight) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return m1.name.localeCompare(m2.name);
|
||||
});
|
||||
|
||||
return result;
|
||||
},
|
||||
hasChildren() {
|
||||
return this.sortedChildMonitorList.length > 0;
|
||||
},
|
||||
depthMargin() {
|
||||
return {
|
||||
marginLeft: `${31 * this.depth}px`,
|
||||
};
|
||||
},
|
||||
monitorName() {
|
||||
if (this.isSearch) {
|
||||
return this.monitor.pathName;
|
||||
} else {
|
||||
return this.monitor.name;
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeMount() {
|
||||
|
||||
// Always unfold if monitor is accessed directly
|
||||
if (this.monitor.childrenIDs.includes(parseInt(this.$route.params.id))) {
|
||||
this.isCollapsed = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Set collapsed value based on local storage
|
||||
let storage = window.localStorage.getItem("monitorCollapsed");
|
||||
if (storage === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let storageObject = JSON.parse(storage);
|
||||
if (storageObject[`monitor_${this.monitor.id}`] == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isCollapsed = storageObject[`monitor_${this.monitor.id}`];
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Changes the collapsed value of the current monitor and saves it to local storage
|
||||
*/
|
||||
changeCollapsed() {
|
||||
this.isCollapsed = !this.isCollapsed;
|
||||
|
||||
// Save collapsed value into local storage
|
||||
let storage = window.localStorage.getItem("monitorCollapsed");
|
||||
let storageObject = {};
|
||||
if (storage !== null) {
|
||||
storageObject = JSON.parse(storage);
|
||||
}
|
||||
storageObject[`monitor_${this.monitor.id}`] = this.isCollapsed;
|
||||
|
||||
window.localStorage.setItem("monitorCollapsed", JSON.stringify(storageObject));
|
||||
},
|
||||
/**
|
||||
* Get URL of monitor
|
||||
* @param {number} id ID of monitor
|
||||
* @returns {string} Relative URL of monitor
|
||||
*/
|
||||
monitorURL(id) {
|
||||
return getMonitorRelativeURL(id);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../assets/vars.scss";
|
||||
|
||||
.small-padding {
|
||||
padding-left: 5px !important;
|
||||
padding-right: 5px !important;
|
||||
}
|
||||
|
||||
.collapse-padding {
|
||||
padding-left: 8px !important;
|
||||
padding-right: 2px !important;
|
||||
}
|
||||
|
||||
// .monitor-item {
|
||||
// width: 100%;
|
||||
// }
|
||||
|
||||
.tags {
|
||||
margin-top: 4px;
|
||||
padding-left: 67px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.collapsed {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.animated {
|
||||
transition: all 0.2s $easing-in;
|
||||
}
|
||||
|
||||
</style>
|
123
src/components/MonitorSettingDialog.vue
Normal file
123
src/components/MonitorSettingDialog.vue
Normal file
@@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<div ref="MonitorSettingDialog" class="modal fade" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
{{ $t("Monitor Setting", [monitor.name]) }}
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" />
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="my-3 form-check">
|
||||
<input id="show-clickable-link" v-model="monitor.isClickAble" class="form-check-input" type="checkbox" @click="toggleLink(monitor.group_index, monitor.monitor_index)" />
|
||||
<label class="form-check-label" for="show-clickable-link">
|
||||
{{ $t("Show Clickable Link") }}
|
||||
</label>
|
||||
<div class="form-text">
|
||||
{{ $t("Show Clickable Link Description") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="btn btn-primary btn-add-group me-2"
|
||||
@click="$refs.badgeGeneratorDialog.show(monitor.id, monitor.name)"
|
||||
>
|
||||
<font-awesome-icon icon="certificate" />
|
||||
{{ $t("Open Badge Generator") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-danger" data-bs-dismiss="modal">
|
||||
{{ $t("Close") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<BadgeGeneratorDialog ref="badgeGeneratorDialog" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Modal } from "bootstrap";
|
||||
import BadgeGeneratorDialog from "./BadgeGeneratorDialog.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BadgeGeneratorDialog
|
||||
},
|
||||
props: {},
|
||||
emits: [],
|
||||
data() {
|
||||
return {
|
||||
monitor: {
|
||||
id: null,
|
||||
name: null,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
computed: {},
|
||||
|
||||
mounted() {
|
||||
this.MonitorSettingDialog = new Modal(this.$refs.MonitorSettingDialog);
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Setting monitor
|
||||
* @param {Object} group Data of monitor
|
||||
* @param {Object} monitor Data of monitor
|
||||
*/
|
||||
show(group, monitor) {
|
||||
this.monitor = {
|
||||
id: monitor.element.id,
|
||||
name: monitor.element.name,
|
||||
monitor_index: monitor.index,
|
||||
group_index: group.index,
|
||||
isClickAble: this.showLink(monitor),
|
||||
};
|
||||
|
||||
this.MonitorSettingDialog.show();
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle the value of sendUrl
|
||||
* @param {number} groupIndex Index of group monitor is member of
|
||||
* @param {number} index Index of monitor within group
|
||||
*/
|
||||
toggleLink(groupIndex, index) {
|
||||
this.$root.publicGroupList[groupIndex].monitorList[index].sendUrl = !this.$root.publicGroupList[groupIndex].monitorList[index].sendUrl;
|
||||
},
|
||||
|
||||
/**
|
||||
* Should a link to the monitor be shown?
|
||||
* Attempts to guess if a link should be shown based upon if
|
||||
* sendUrl is set and if the URL is default or not.
|
||||
* @param {Object} monitor Monitor to check
|
||||
* @param {boolean} [ignoreSendUrl=false] Should the presence of the sendUrl
|
||||
* property be ignored. This will only work in edit mode.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
showLink(monitor, ignoreSendUrl = false) {
|
||||
// We must check if there are any elements in monitorList to
|
||||
// prevent undefined errors if it hasn't been loaded yet
|
||||
if (this.$parent.editMode && ignoreSendUrl && Object.keys(this.$root.monitorList).length) {
|
||||
return this.$root.monitorList[monitor.element.id].type === "http" || this.$root.monitorList[monitor.element.id].type === "keyword";
|
||||
}
|
||||
return monitor.element.sendUrl && monitor.element.url && monitor.element.url !== "https://" && !this.editMode;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../assets/vars.scss";
|
||||
|
||||
.dark {
|
||||
.modal-dialog .form-text, .modal-dialog p {
|
||||
color: $dark-font-color;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -129,7 +129,9 @@ export default {
|
||||
"ntfy": "Ntfy",
|
||||
"octopush": "Octopush",
|
||||
"OneBot": "OneBot",
|
||||
"Opsgenie": "Opsgenie",
|
||||
"PagerDuty": "PagerDuty",
|
||||
"PagerTree": "PagerTree",
|
||||
"pushbullet": "Pushbullet",
|
||||
"PushByTechulus": "Push by Techulus",
|
||||
"pushover": "Pushover",
|
||||
@@ -143,6 +145,7 @@ export default {
|
||||
"stackfield": "Stackfield",
|
||||
"teams": "Microsoft Teams",
|
||||
"telegram": "Telegram",
|
||||
"twilio": "Twilio",
|
||||
"Splunk": "Splunk",
|
||||
"webhook": "Webhook",
|
||||
"GoAlert": "GoAlert",
|
||||
|
@@ -11,16 +11,16 @@
|
||||
</ul>
|
||||
</div>
|
||||
<div class="chart-wrapper" :class="{ loading : loading}">
|
||||
<LineChart :chart-data="chartData" :options="chartOptions" />
|
||||
<Line :data="chartData" :options="chartOptions" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { BarController, BarElement, Chart, Filler, LinearScale, LineController, LineElement, PointElement, TimeScale, Tooltip } from "chart.js";
|
||||
import "chartjs-adapter-dayjs";
|
||||
import "chartjs-adapter-dayjs-4";
|
||||
import dayjs from "dayjs";
|
||||
import { LineChart } from "vue-chart-3";
|
||||
import { Line } from "vue-chartjs";
|
||||
import { useToast } from "vue-toastification";
|
||||
import { DOWN, PENDING, MAINTENANCE, log } from "../util.ts";
|
||||
|
||||
@@ -29,7 +29,7 @@ const toast = useToast();
|
||||
Chart.register(LineController, BarController, LineElement, PointElement, TimeScale, BarElement, LinearScale, Tooltip, Filler);
|
||||
|
||||
export default {
|
||||
components: { LineChart },
|
||||
components: { Line },
|
||||
props: {
|
||||
/** ID of monitor */
|
||||
monitorId: {
|
||||
@@ -104,8 +104,10 @@ export default {
|
||||
}
|
||||
},
|
||||
ticks: {
|
||||
sampleSize: 3,
|
||||
maxRotation: 0,
|
||||
autoSkipPadding: 30,
|
||||
padding: 3,
|
||||
},
|
||||
grid: {
|
||||
color: this.$root.theme === "light" ? "rgba(0,0,0,0.1)" : "rgba(255,255,255,0.1)",
|
||||
@@ -197,6 +199,7 @@ export default {
|
||||
borderColor: "#5CDD8B",
|
||||
backgroundColor: "#5CDD8B38",
|
||||
yAxisID: "y",
|
||||
label: "ping",
|
||||
},
|
||||
{
|
||||
// Bar Chart
|
||||
@@ -208,6 +211,8 @@ export default {
|
||||
barThickness: "flex",
|
||||
barPercentage: 1,
|
||||
categoryPercentage: 1,
|
||||
inflateAmount: 0.05,
|
||||
label: "status",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@@ -49,16 +49,15 @@
|
||||
{{ monitor.element.name }}
|
||||
</a>
|
||||
<p v-else class="item-name"> {{ monitor.element.name }} </p>
|
||||
|
||||
<span
|
||||
v-if="showLink(monitor, true)"
|
||||
title="Toggle Clickable Link"
|
||||
title="Setting"
|
||||
>
|
||||
<font-awesome-icon
|
||||
v-if="editMode"
|
||||
:class="{'link-active': monitor.element.sendUrl, 'btn-link': true}"
|
||||
icon="link" class="action me-3"
|
||||
|
||||
@click="toggleLink(group.index, monitor.index)"
|
||||
:class="{'link-active': true, 'btn-link': true}"
|
||||
icon="cog" class="action me-3"
|
||||
@click="$refs.monitorSettingDialog.show(group, monitor)"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
@@ -77,9 +76,11 @@
|
||||
</div>
|
||||
</template>
|
||||
</Draggable>
|
||||
<MonitorSettingDialog ref="monitorSettingDialog" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MonitorSettingDialog from "./MonitorSettingDialog.vue";
|
||||
import Draggable from "vuedraggable";
|
||||
import HeartbeatBar from "./HeartbeatBar.vue";
|
||||
import Uptime from "./Uptime.vue";
|
||||
@@ -87,6 +88,7 @@ import Tag from "./Tag.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MonitorSettingDialog,
|
||||
Draggable,
|
||||
HeartbeatBar,
|
||||
Uptime,
|
||||
@@ -135,15 +137,6 @@ export default {
|
||||
this.$root.publicGroupList[groupIndex].monitorList.splice(index, 1);
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle the value of sendUrl
|
||||
* @param {number} groupIndex Index of group monitor is member of
|
||||
* @param {number} index Index of monitor within group
|
||||
*/
|
||||
toggleLink(groupIndex, index) {
|
||||
this.$root.publicGroupList[groupIndex].monitorList[index].sendUrl = !this.$root.publicGroupList[groupIndex].monitorList[index].sendUrl;
|
||||
},
|
||||
|
||||
/**
|
||||
* Should a link to the monitor be shown?
|
||||
* Attempts to guess if a link should be shown based upon if
|
||||
|
@@ -6,7 +6,7 @@
|
||||
'm-2': size == 'normal',
|
||||
'px-2': size == 'sm',
|
||||
'py-0': size == 'sm',
|
||||
'm-1': size == 'sm',
|
||||
'mx-1': size == 'sm',
|
||||
}"
|
||||
:style="{ backgroundColor: item.color, fontSize: size == 'sm' ? '0.7em' : '1em' }"
|
||||
>
|
||||
|
@@ -76,11 +76,24 @@
|
||||
</button>
|
||||
</router-link>
|
||||
</div>
|
||||
<div v-if="allMonitorList.length > 0" class="pt-3 px-3">
|
||||
<div v-if="allMonitorList.length > 0" class="pt-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>
|
||||
<VueMultiselect
|
||||
v-model="selectedAddMonitor"
|
||||
:options="allMonitorList"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
:placeholder="$t('Add a monitor')"
|
||||
label="name"
|
||||
trackBy="name"
|
||||
class="mt-1"
|
||||
>
|
||||
<template #option="{ option }">
|
||||
<div class="d-inline-flex">
|
||||
<span>{{ option.name }} <Tag v-for="monitorTag in option.tags" :key="monitorTag" :item="monitorTag" :size="'sm'" /></span>
|
||||
</div>
|
||||
</template>
|
||||
</VueMultiselect>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -107,6 +120,7 @@
|
||||
<script>
|
||||
import { Modal } from "bootstrap";
|
||||
import Confirm from "./Confirm.vue";
|
||||
import Tag from "./Tag.vue";
|
||||
import VueMultiselect from "vue-multiselect";
|
||||
import { colorOptions } from "../util-frontend";
|
||||
import { useToast } from "vue-toastification";
|
||||
@@ -117,6 +131,7 @@ export default {
|
||||
components: {
|
||||
VueMultiselect,
|
||||
Confirm,
|
||||
Tag,
|
||||
},
|
||||
props: {
|
||||
updated: {
|
||||
|
@@ -16,17 +16,29 @@
|
||||
<input id="ntfy-priority" v-model="$parent.notification.ntfyPriority" type="number" class="form-control" required min="1" max="5" step="1">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="ntfy-username" class="form-label">{{ $t("Username") }} ({{ $t("Optional") }})</label>
|
||||
<label for="authentication-method" class="form-label">{{ $t("ntfyAuthenticationMethod") }}</label>
|
||||
<select id="authentication-method" v-model="$parent.notification.ntfyAuthenticationMethod" class="form-select">
|
||||
<option v-for="(name, type) in authenticationMethods" :key="type" :value="type">{{ name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div v-if="$parent.notification.ntfyAuthenticationMethod === 'usernamePassword'" class="mb-3">
|
||||
<label for="ntfy-username" class="form-label">{{ $t("Username") }}</label>
|
||||
<div class="input-group mb-3">
|
||||
<input id="ntfy-username" v-model="$parent.notification.ntfyusername" type="text" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="ntfy-password" class="form-label">{{ $t("Password") }} ({{ $t("Optional") }})</label>
|
||||
<div v-if="$parent.notification.ntfyAuthenticationMethod === 'usernamePassword'" class="mb-3">
|
||||
<label for="ntfy-password" class="form-label">{{ $t("Password") }}</label>
|
||||
<div class="input-group mb-3">
|
||||
<HiddenInput id="ntfy-password" v-model="$parent.notification.ntfypassword" autocomplete="new-password"></HiddenInput>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="$parent.notification.ntfyAuthenticationMethod === 'accessToken'" class="mb-3">
|
||||
<label for="ntfy-access-token" class="form-label">{{ $t("Access Token") }}</label>
|
||||
<div class="input-group mb-3">
|
||||
<HiddenInput id="ntfy-access-token" v-model="$parent.notification.ntfyaccesstoken"></HiddenInput>
|
||||
</div>
|
||||
</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">
|
||||
@@ -40,11 +52,29 @@ export default {
|
||||
components: {
|
||||
HiddenInput,
|
||||
},
|
||||
computed: {
|
||||
authenticationMethods() {
|
||||
return {
|
||||
none: this.$t("None"),
|
||||
usernamePassword: this.$t("ntfyUsernameAndPassword"),
|
||||
accessToken: this.$t("Access Token")
|
||||
};
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (typeof this.$parent.notification.ntfyPriority === "undefined") {
|
||||
this.$parent.notification.ntfyserverurl = "https://ntfy.sh";
|
||||
this.$parent.notification.ntfyPriority = 5;
|
||||
}
|
||||
|
||||
// Handling notifications that added before 1.22.0
|
||||
if (typeof this.$parent.notification.ntfyAuthenticationMethod === "undefined") {
|
||||
if (!this.$parent.notification.ntfyusername) {
|
||||
this.$parent.notification.ntfyAuthenticationMethod = "none";
|
||||
} else {
|
||||
this.$parent.notification.ntfyAuthenticationMethod = "usernamePassword";
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
36
src/components/notifications/Opsgenie.vue
Normal file
36
src/components/notifications/Opsgenie.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="opsgenie-region" class="form-label">{{ $t("Region") }}<span style="color: red;"><sup>*</sup></span></label>
|
||||
<select id="opsgenie-region" v-model="$parent.notification.opsgenieRegion" class="form-select" required>
|
||||
<option value="us">
|
||||
US (Default)
|
||||
</option>
|
||||
<option value="eu">
|
||||
EU
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="opsgenie-apikey" class="form-label">{{ $t("API Key") }}<span style="color: red;"><sup>*</sup></span></label>
|
||||
<HiddenInput id="opsgenie-apikey" v-model="$parent.notification.opsgenieApiKey" required="true" autocomplete="false"></HiddenInput>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="opsgenie-priority" class="form-label">{{ $t("Priority") }}</label>
|
||||
<input id="opsgenie-priority" v-model="$parent.notification.opsgeniePriority" type="number" class="form-control" min="1" max="5" step="1">
|
||||
</div>
|
||||
<div class="form-text">
|
||||
<span style="color: red;"><sup>*</sup></span>{{ $t("Required") }}
|
||||
<i18n-t tag="p" keypath="aboutWebhooks" style="margin-top: 8px;">
|
||||
<a href="https://docs.opsgenie.com/docs/alert-api" target="_blank">https://docs.opsgenie.com/docs/alert-api</a>
|
||||
</i18n-t>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HiddenInput from "../HiddenInput.vue";
|
||||
export default {
|
||||
components: {
|
||||
HiddenInput,
|
||||
},
|
||||
};
|
||||
</script>
|
@@ -42,6 +42,8 @@
|
||||
<option value="vibrate">{{ $t("pushoversounds vibrate") }}</option>
|
||||
<option value="none">{{ $t("pushoversounds none") }}</option>
|
||||
</select>
|
||||
<label for="pushover-ttl" class="form-label">{{ $t("pushoverMessageTtl") }}</label>
|
||||
<input id="pushover-ttl" v-model="$parent.notification.pushoverttl" type="number" min="0" step="1" class="form-control">
|
||||
<div class="form-text">
|
||||
<span style="color: red;"><sup>*</sup></span>{{ $t("Required") }}
|
||||
<i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;">
|
||||
|
27
src/components/notifications/Twilio.vue
Normal file
27
src/components/notifications/Twilio.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="twilio-account-sid" class="form-label">{{ $t("Account SID") }}</label>
|
||||
<input id="twilio-account-sid" v-model="$parent.notification.twilioAccountSID" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="twilio-auth-token" class="form-label">{{ $t("Auth Token") }}</label>
|
||||
<input id="twilio-auth-token" v-model="$parent.notification.twilioAuthToken" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="twilio-from-number" class="form-label">{{ $t("From Number") }}</label>
|
||||
<input id="twilio-from-number" v-model="$parent.notification.twilioFromNumber" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="twilio-to-number" class="form-label">{{ $t("To Number") }}</label>
|
||||
<input id="twilio-to-number" v-model="$parent.notification.twilioToNumber" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;">
|
||||
<a href="https://www.twilio.com/docs/sms" target="_blank">https://www.twilio.com/docs/sms</a>
|
||||
</i18n-t>
|
||||
</div>
|
||||
</template>
|
@@ -21,6 +21,7 @@ import Mattermost from "./Mattermost.vue";
|
||||
import Ntfy from "./Ntfy.vue";
|
||||
import Octopush from "./Octopush.vue";
|
||||
import OneBot from "./OneBot.vue";
|
||||
import Opsgenie from "./Opsgenie.vue";
|
||||
import PagerDuty from "./PagerDuty.vue";
|
||||
import PagerTree from "./PagerTree.vue";
|
||||
import PromoSMS from "./PromoSMS.vue";
|
||||
@@ -41,6 +42,7 @@ import STMP from "./SMTP.vue";
|
||||
import Teams from "./Teams.vue";
|
||||
import TechulusPush from "./TechulusPush.vue";
|
||||
import Telegram from "./Telegram.vue";
|
||||
import Twilio from "./Twilio.vue";
|
||||
import Webhook from "./Webhook.vue";
|
||||
import WeCom from "./WeCom.vue";
|
||||
import GoAlert from "./GoAlert.vue";
|
||||
@@ -76,6 +78,7 @@ const NotificationFormList = {
|
||||
"ntfy": Ntfy,
|
||||
"octopush": Octopush,
|
||||
"OneBot": OneBot,
|
||||
"Opsgenie": Opsgenie,
|
||||
"PagerDuty": PagerDuty,
|
||||
"PagerTree": PagerTree,
|
||||
"promosms": PromoSMS,
|
||||
@@ -95,6 +98,7 @@ const NotificationFormList = {
|
||||
"stackfield": Stackfield,
|
||||
"teams": Teams,
|
||||
"telegram": Telegram,
|
||||
"twilio": Twilio,
|
||||
"Splunk": Splunk,
|
||||
"webhook": Webhook,
|
||||
"WeCom": WeCom,
|
||||
|
@@ -1,21 +1,18 @@
|
||||
<template>
|
||||
<div class="my-4">
|
||||
<div class="mx-4 pt-1 my-3">
|
||||
<div class="mx-0 mx-lg-4 pt-1 mb-4">
|
||||
<button class="btn btn-primary" @click.stop="addTag"><font-awesome-icon icon="plus" /> {{ $t("Add New Tag") }}</button>
|
||||
</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">
|
||||
<div v-for="(tag, index) in tagsList" :key="tag.id" class="d-flex align-items-center mx-0 mx-lg-4 py-1 tags-list-row" :disabled="processing" @click="editTag(index)">
|
||||
<div class="col-10 col-sm-5">
|
||||
<Tag :item="tag" />
|
||||
</div>
|
||||
<div class="col-5 px-1">
|
||||
<div class="col-5 px-1 d-none d-sm-block">
|
||||
<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>
|
||||
<div class="col-2 pe-2 pe-lg-3 d-flex justify-content-end">
|
||||
<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>
|
||||
@@ -156,8 +153,8 @@ export default {
|
||||
@import "../../assets/vars.scss";
|
||||
|
||||
.btn-rm-tag {
|
||||
padding-left: 11px;
|
||||
padding-right: 11px;
|
||||
padding-left: 9px;
|
||||
padding-right: 9px;
|
||||
}
|
||||
|
||||
.tags-list .tags-list-row {
|
||||
|
@@ -49,6 +49,7 @@ import {
|
||||
faFilter,
|
||||
faInfoCircle,
|
||||
faClone,
|
||||
faCertificate,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
library.add(
|
||||
@@ -95,6 +96,7 @@ library.add(
|
||||
faFilter,
|
||||
faInfoCircle,
|
||||
faClone,
|
||||
faCertificate,
|
||||
);
|
||||
|
||||
export { FontAwesomeIcon };
|
||||
|
@@ -683,6 +683,6 @@
|
||||
"backupDescription2": "ملحوظة",
|
||||
"languageName": "العربية",
|
||||
"Game": "الألعاب",
|
||||
"List": "قائمة",
|
||||
"List": "القائمة",
|
||||
"statusMaintenance": "الصيانة"
|
||||
}
|
||||
|
@@ -178,7 +178,7 @@
|
||||
"Degraded Service": "Всички услуги са недостъпни",
|
||||
"Add Group": "Добави група",
|
||||
"Add a monitor": "Добави монитор",
|
||||
"Edit Status Page": "Редактиране Статус страница",
|
||||
"Edit Status Page": "Редактиране на статус страницата",
|
||||
"Go to Dashboard": "Към Таблото",
|
||||
"telegram": "Telegram",
|
||||
"webhook": "Уеб кука",
|
||||
@@ -200,7 +200,7 @@
|
||||
"mattermost": "Mattermost",
|
||||
"Status Page": "Статус страница",
|
||||
"Status Pages": "Статус страници",
|
||||
"Primary Base URL": "Основен базов URL адрес",
|
||||
"Primary Base URL": "Базов URL адрес",
|
||||
"Push URL": "Генериран Push URL адрес",
|
||||
"needPushEvery": "Необходимо е да извършвате заявка към този URL адрес на всеки {0} секунди.",
|
||||
"pushOptionalParams": "Допълнителни, но не задължителни параметри: {0}",
|
||||
@@ -591,7 +591,7 @@
|
||||
"All Status Pages": "Всички статус страници",
|
||||
"Select status pages...": "Изберете статус страници…",
|
||||
"recurringIntervalMessage": "Изпълнявай ежедневно | Изпълнявай всеки {0} дни",
|
||||
"affectedMonitorsDescription": "Изберете монитори, засегнати от текущата поддръжка",
|
||||
"affectedMonitorsDescription": "Изберете монитори, попадащи в обсега на текущата поддръжка",
|
||||
"affectedStatusPages": "Покажи това съобщение за поддръжка на избрани статус страници",
|
||||
"atLeastOneMonitor": "Изберете поне един засегнат монитор",
|
||||
"deleteMaintenanceMsg": "Сигурни ли сте, че желаете да изтриете тази поддръжка?",
|
||||
@@ -652,7 +652,7 @@
|
||||
"dnsCacheDescription": "Възможно е да не работи в IPv6 среда - деактивирайте, ако срещнете проблеми.",
|
||||
"Single Maintenance Window": "Единичен времеви интервал за поддръжка",
|
||||
"Maintenance Time Window of a Day": "Времеви интервал от деня за поддръжка",
|
||||
"Effective Date Range": "Интервал от дни на влизане в сила",
|
||||
"Effective Date Range": "Ефективен интервал от дни (по желание)",
|
||||
"Schedule Maintenance": "Планирай поддръжка",
|
||||
"Date and Time": "Дата и час",
|
||||
"DateTime Range": "Изтрий времеви интервал",
|
||||
@@ -707,7 +707,7 @@
|
||||
"telegramSendSilently": "Изпрати тихо",
|
||||
"Clone Monitor": "Клониране на монитор",
|
||||
"Clone": "Клонирай",
|
||||
"cloneOf": "Клонинг на {0}",
|
||||
"cloneOf": "Клониран {0}",
|
||||
"Expiry": "Валиден до",
|
||||
"Expiry date": "Дата на изтичане",
|
||||
"Add Another": "Добави друг",
|
||||
@@ -738,5 +738,43 @@
|
||||
"Add New Tag": "Добави нов етикет",
|
||||
"lunaseaTarget": "Цел",
|
||||
"lunaseaDeviceID": "ID на устройството",
|
||||
"lunaseaUserID": "ID на потребител"
|
||||
"lunaseaUserID": "ID на потребител",
|
||||
"twilioAccountSID": "Профил SID",
|
||||
"twilioAuthToken": "Удостоверяващ токен",
|
||||
"twilioFromNumber": "От номер",
|
||||
"twilioToNumber": "Към номер",
|
||||
"sameAsServerTimezone": "Kато часовата зона на сървъра",
|
||||
"startDateTime": "Старт Дата/Час",
|
||||
"endDateTime": "Край Дата/Час",
|
||||
"cronSchedule": "График: ",
|
||||
"invalidCronExpression": "Невалиден \"Cron\" израз: {0}",
|
||||
"cronExpression": "Израз тип \"Cron\"",
|
||||
"statusPageRefreshIn": "Обновяване след: {0}",
|
||||
"ntfyUsernameAndPassword": "Потребителско име и парола",
|
||||
"ntfyAuthenticationMethod": "Метод за удостоверяване",
|
||||
"pushoverMessageTtl": "TTL на съобщението (секунди)",
|
||||
"Open Badge Generator": "Отвори генератора на баджове",
|
||||
"Badge Generator": "Генератор на баджове на {0}",
|
||||
"Badge Type": "Тип бадж",
|
||||
"Badge Duration": "Продължителност на баджа",
|
||||
"Badge Prefix": "Префикс на баджа",
|
||||
"Badge Label Color": "Цвят на етикета на баджа",
|
||||
"Badge Color": "Цвят на баджа",
|
||||
"Badge Label Suffix": "Суфикс на етикета на значката",
|
||||
"Badge Up Color": "Цвят на баджа за достъпен",
|
||||
"Badge Down Color": "Цвят на баджа за недостъпен",
|
||||
"Badge Maintenance Color": "Цвят на баджа за поддръжка",
|
||||
"Badge Warn Color": "Цвят на баджа за предупреждение",
|
||||
"Badge Warn Days": "Дни за показване на баджа",
|
||||
"Badge Style": "Стил на баджа",
|
||||
"Badge value (For Testing only.)": "Стойност на баджа (само за тест.)",
|
||||
"Badge URL": "URL адрес на баджа",
|
||||
"Monitor Setting": "Настройка на монитор {0}",
|
||||
"Show Clickable Link": "Покажи връзка, която може да се кликне",
|
||||
"Show Clickable Link Description": "Ако е отбелязано, всеки който има достъп до тази статус страница, ще може да достъпва URL адреса на монитора.",
|
||||
"Badge Label": "Етикет на баджа",
|
||||
"Badge Suffix": "Суфикс на баджа",
|
||||
"Badge Label Prefix": "Префикс на етикета на значката",
|
||||
"Badge Pending Color": "Цвят на баджа за изчакващ",
|
||||
"Badge Down Days": "Колко дни баджът да не се показва"
|
||||
}
|
||||
|
1
src/lang/ckb.json
Normal file
1
src/lang/ckb.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"languageName": "Czech",
|
||||
"languageName": "Čeština",
|
||||
"checkEverySecond": "Kontrolovat každých {0} sekund",
|
||||
"retryCheckEverySecond": "Opakovat každých {0} sekund",
|
||||
"resendEveryXTimes": "Znovu zaslat {0}krát",
|
||||
@@ -134,7 +134,7 @@
|
||||
"Remember me": "Zapamatovat si mě",
|
||||
"Login": "Přihlášení",
|
||||
"No Monitors, please": "Žádné dohledy, prosím",
|
||||
"add one": "přidat jeden",
|
||||
"add one": "začněte přidáním nového",
|
||||
"Notification Type": "Typ oznámení",
|
||||
"Email": "E-mail",
|
||||
"Test": "Test",
|
||||
@@ -518,7 +518,7 @@
|
||||
"PushDeer Key": "PushDeer klíč",
|
||||
"Footer Text": "Text v patičce",
|
||||
"Show Powered By": "Zobrazit \"Poskytuje\"",
|
||||
"Domain Names": "Názvy domén",
|
||||
"Domain Names": "Doménová jména",
|
||||
"signedInDisp": "Přihlášen jako {0}",
|
||||
"signedInDispDisabled": "Ověření je vypnuté.",
|
||||
"RadiusSecret": "Tajemství Radius",
|
||||
@@ -542,11 +542,11 @@
|
||||
"promosmsPassword": "API Password",
|
||||
"pushoversounds pushover": "Pushover (výchozí)",
|
||||
"pushoversounds bike": "Kolo",
|
||||
"pushoversounds bugle": "Bugle",
|
||||
"pushoversounds bugle": "Trumpeta",
|
||||
"pushoversounds cashregister": "Pokladna",
|
||||
"pushoversounds classical": "Classical",
|
||||
"pushoversounds cosmic": "Kosmický",
|
||||
"pushoversounds falling": "Falling",
|
||||
"pushoversounds falling": "Padající",
|
||||
"pushoversounds gamelan": "Gamelan",
|
||||
"pushoversounds incoming": "Příchozí",
|
||||
"pushoversounds intermission": "Přestávka",
|
||||
@@ -554,9 +554,9 @@
|
||||
"pushoversounds mechanical": "Mechanika",
|
||||
"pushoversounds pianobar": "Barové piano",
|
||||
"pushoversounds siren": "Siréna",
|
||||
"pushoversounds spacealarm": "Space Alarm",
|
||||
"pushoversounds tugboat": "Tug Boat",
|
||||
"pushoversounds alien": "Alien Alarm (dlouhý)",
|
||||
"pushoversounds spacealarm": "Vesmírný alarm",
|
||||
"pushoversounds tugboat": "Remorkér",
|
||||
"pushoversounds alien": "Mimozemský poplach (dlouhý)",
|
||||
"pushoversounds climb": "Climb (dlouhý)",
|
||||
"pushoversounds persistent": "Persistent (dlouhý)",
|
||||
"pushoversounds echo": "Pushover Echo (dlouhý)",
|
||||
@@ -661,7 +661,7 @@
|
||||
"dnsCacheDescription": "V některých IPv6 prostředích nemusí fungovat. Pokud narazíte na nějaké problémy, vypněte jej.",
|
||||
"Single Maintenance Window": "Konkrétní časové okno pro údržbu",
|
||||
"Maintenance Time Window of a Day": "Časové okno pro údržbu v daný den",
|
||||
"Effective Date Range": "Časové období",
|
||||
"Effective Date Range": "Časové období (volitelné)",
|
||||
"Schedule Maintenance": "Naplánovat údržbu",
|
||||
"Date and Time": "Datum a čas",
|
||||
"DateTime Range": "Rozsah data a času",
|
||||
@@ -669,7 +669,7 @@
|
||||
"Free Mobile User Identifier": "Identifikátor uživatele Free Mobile",
|
||||
"Free Mobile API Key": "API klíč Free Mobile",
|
||||
"Enable TLS": "Povolit TLS",
|
||||
"Proto Service Name": "Proto Service Name",
|
||||
"Proto Service Name": "Jméno Proto Service",
|
||||
"Proto Method": "Proto metoda",
|
||||
"Proto Content": "Proto obsah",
|
||||
"Economy": "Úsporná",
|
||||
@@ -705,9 +705,9 @@
|
||||
"telegramProtectContent": "Ochrana přeposílání/ukládání",
|
||||
"telegramSendSilently": "Odeslat potichu",
|
||||
"telegramSendSilentlyDescription": "Zprávu odešle tiše. Uživatelé obdrží oznámení bez zvuku.",
|
||||
"Clone": "Klonovat",
|
||||
"cloneOf": "Klonovat {0}",
|
||||
"Clone Monitor": "Klonovat dohled",
|
||||
"Clone": "Duplikovat",
|
||||
"cloneOf": "Kopie {0}",
|
||||
"Clone Monitor": "Duplikovat dohled",
|
||||
"API Keys": "API klíče",
|
||||
"Expiry": "Platnost",
|
||||
"Don't expire": "Nevyprší",
|
||||
@@ -738,5 +738,40 @@
|
||||
"Add New Tag": "Přidat nový štítek",
|
||||
"lunaseaTarget": "Cíl",
|
||||
"lunaseaDeviceID": "ID zařízení",
|
||||
"lunaseaUserID": "ID uživatele"
|
||||
"lunaseaUserID": "ID uživatele",
|
||||
"statusPageRefreshIn": "Obnovení za: {0}",
|
||||
"twilioAccountSID": "SID účtu",
|
||||
"twilioFromNumber": "Číslo odesílatele",
|
||||
"twilioToNumber": "Číslo příjemce",
|
||||
"twilioAuthToken": "Autorizační token",
|
||||
"sameAsServerTimezone": "Stejné jako časové pásmo serveru",
|
||||
"cronExpression": "Cron výraz",
|
||||
"cronSchedule": "Plán: ",
|
||||
"invalidCronExpression": "Neplatný cron výraz: {0}",
|
||||
"startDateTime": "Počáteční datum/čas",
|
||||
"endDateTime": "Datum/čas konce",
|
||||
"ntfyAuthenticationMethod": "Způsob ověření",
|
||||
"ntfyUsernameAndPassword": "Uživatelské jméno a heslo",
|
||||
"pushoverMessageTtl": "Zpráva TTL (Sekund)",
|
||||
"Show Clickable Link": "Zobrazit klikatelný odkaz",
|
||||
"Show Clickable Link Description": "Pokud je zaškrtnuto, všichni, kdo mají přístup k této stavové stránce, mají přístup k adrese URL monitoru.",
|
||||
"Open Badge Generator": "Otevřít generátor odznaků",
|
||||
"Badge Type": "Typ odznaku",
|
||||
"Badge Duration": "Délka platnosti odznaku",
|
||||
"Badge Label": "Štítek odznaku",
|
||||
"Badge Prefix": "Prefix odznaku",
|
||||
"Monitor Setting": "{0}'s Nastavení dohledu",
|
||||
"Badge Generator": "{0}'s Generátor odznaků",
|
||||
"Badge Label Color": "Barva štítku odznaku",
|
||||
"Badge Color": "Barva odznaku",
|
||||
"Badge Style": "Styl odznaku",
|
||||
"Badge Label Suffix": "Přípona štítku odznaku",
|
||||
"Badge URL": "URL odznaku",
|
||||
"Badge Suffix": "Přípona odznaku",
|
||||
"Badge Label Prefix": "Prefix štítku odznaku",
|
||||
"Badge Up Color": "Barva odzanaku při Běží",
|
||||
"Badge Down Color": "Barva odznaku při Nedostupné",
|
||||
"Badge Pending Color": "Barva odznaku při Pauze",
|
||||
"Badge Maintenance Color": "Barva odznaku při Údržbě",
|
||||
"Badge Warn Color": "Barva odznaku při Upozornění"
|
||||
}
|
||||
|
@@ -37,7 +37,7 @@
|
||||
"checkEverySecond": "Tjek hvert {0} sekund",
|
||||
"Response": "Respons",
|
||||
"Ping": "Ping",
|
||||
"Monitor Type": "Overvåger Type",
|
||||
"Monitor Type": "Overvåger type",
|
||||
"Keyword": "Nøgleord",
|
||||
"Friendly Name": "Visningsnavn",
|
||||
"URL": "URL",
|
||||
@@ -144,7 +144,7 @@
|
||||
"retryCheckEverySecond": "Prøv igen hvert {0} sekund.",
|
||||
"importHandleDescription": "Vælg 'Spring over eksisterende', hvis du vil springe over hver overvåger eller underretning med samme navn. 'Overskriv' sletter alle eksisterende overvågere og underretninger.",
|
||||
"confirmImportMsg": "Er du sikker på at importere sikkerhedskopien? Sørg for, at du har valgt den rigtige importindstilling.",
|
||||
"Heartbeat Retry Interval": "Hjerteslag Gentagelsesinterval",
|
||||
"Heartbeat Retry Interval": "Hjerteslag gentagelsesinterval",
|
||||
"Import Backup": "Importer Backup",
|
||||
"Export Backup": "Eksporter Backup",
|
||||
"Skip existing": "Spring over eksisterende",
|
||||
@@ -166,14 +166,14 @@
|
||||
"Purple": "Lilla",
|
||||
"Pink": "Pink",
|
||||
"Search...": "Søg…",
|
||||
"Avg. Ping": "Gns. Ping",
|
||||
"Avg. Response": "Gns. Respons",
|
||||
"Avg. Ping": "Gns. ping",
|
||||
"Avg. Response": "Gns. respons",
|
||||
"Entry Page": "Entry Side",
|
||||
"statusPageNothing": "Intet her, tilføj venligst en Gruppe eller en Overvåger.",
|
||||
"No Services": "Ingen Tjenester",
|
||||
"All Systems Operational": "Alle Systemer i Drift",
|
||||
"Partially Degraded Service": "Delvist Forringet Service",
|
||||
"Degraded Service": "Forringet Service",
|
||||
"Partially Degraded Service": "Delvist forringet service",
|
||||
"Degraded Service": "Forringet service",
|
||||
"Add Group": "Tilføj Gruppe",
|
||||
"Add a monitor": "Tilføj en Overvåger",
|
||||
"Edit Status Page": "Rediger Statusside",
|
||||
@@ -314,7 +314,7 @@
|
||||
"Steam API Key": "Steam API-nøgle",
|
||||
"Shrink Database": "Krymp Database",
|
||||
"Pick a RR-Type...": "Vælg en RR-Type…",
|
||||
"Pick Accepted Status Codes...": "Vælg Accepterede Statuskoder...",
|
||||
"Pick Accepted Status Codes...": "Vælg accepterede statuskoder…",
|
||||
"Default": "Standard",
|
||||
"HTTP Options": "HTTP Valgmuligheder",
|
||||
"Create Incident": "Opret Annoncering",
|
||||
@@ -447,7 +447,7 @@
|
||||
"Docker Hosts": "Docker Hosts",
|
||||
"loadingError": "Kan ikke hente dataene, prøv igen senere.",
|
||||
"Custom": "Brugerdefineret",
|
||||
"Monitor": "Monitor | Monitors",
|
||||
"Monitor": "Overvåger | Overvågere",
|
||||
"Specific Monitor Type": "Specifik monitor-type",
|
||||
"topic": "Emne",
|
||||
"Fingerprint:": "Fingerprint:",
|
||||
@@ -580,5 +580,7 @@
|
||||
"Expiry date": "Udløbsdato",
|
||||
"Expires": "Udløber",
|
||||
"deleteAPIKeyMsg": "Er du sikker på du vil slette denne API nøgle?",
|
||||
"pagertreeDoNothing": "Gør intet"
|
||||
"pagertreeDoNothing": "Gør intet",
|
||||
"Start of maintenance": "Start på vedligeholdelse",
|
||||
"Add New Tag": "Tilføj nyt tag"
|
||||
}
|
||||
|
@@ -102,7 +102,7 @@
|
||||
"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.",
|
||||
"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?",
|
||||
@@ -206,7 +206,7 @@
|
||||
"mattermost": "Mattermost",
|
||||
"Primary Base URL": "Primär URL",
|
||||
"Push URL": "Push URL",
|
||||
"needPushEvery": "Du solltest diese URL alle {0} Sekunden aufrufen",
|
||||
"needPushEvery": "Du solltest diese URL alle {0} Sekunden aufrufen.",
|
||||
"pushOptionalParams": "Optionale Parameter: {0}",
|
||||
"defaultNotificationName": "Mein {notification} Alarm ({number})",
|
||||
"here": "hier",
|
||||
@@ -259,6 +259,7 @@
|
||||
"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.",
|
||||
"pushoverMessageTtl": "Message TTL (Sekunden)",
|
||||
"SMS Type": "SMS Typ",
|
||||
"octopushTypePremium": "Premium (Schnell - zur Benachrichtigung empfohlen)",
|
||||
"octopushTypeLowCost": "Kostengünstig (Langsam - manchmal vom Betreiber gesperrt)",
|
||||
@@ -276,10 +277,10 @@
|
||||
"appriseInstalled": "Apprise ist installiert.",
|
||||
"appriseNotInstalled": "Apprise ist nicht installiert. {0}",
|
||||
"Access Token": "Access Token",
|
||||
"Channel access token": "Channel access token",
|
||||
"Channel access token": "Channel Access Token",
|
||||
"Line Developers Console": "Line Developers Console",
|
||||
"lineDevConsoleTo": "Line Developers Console - {0}",
|
||||
"Basic Settings": "Basic Settings",
|
||||
"Basic Settings": "Grundeinstellungen",
|
||||
"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.",
|
||||
@@ -298,7 +299,7 @@
|
||||
"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",
|
||||
"Method": "Methode",
|
||||
"Body": "Body",
|
||||
"Headers": "Headers",
|
||||
"PushUrl": "Push URL",
|
||||
@@ -348,7 +349,7 @@
|
||||
"Services": "Dienste",
|
||||
"Discard": "Verwerfen",
|
||||
"Cancel": "Abbrechen",
|
||||
"Powered by": "Powered by",
|
||||
"Powered by": "Erstellt mit",
|
||||
"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)",
|
||||
@@ -533,7 +534,7 @@
|
||||
"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",
|
||||
"Steam Game Server": "Steam Spielserver",
|
||||
"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.",
|
||||
@@ -590,7 +591,7 @@
|
||||
"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}",
|
||||
"goAlertInfo": "GoAlert ist eine Open-Source Applikation für Rufbereitschaftsplanung, automatische Eskalation und Benachrichtigung (z.B. SMS oder Telefonanrufe). Engagiere 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 die Backupfunktion nicht mehr gepflegt wird, kann keine vollständige Sicherung erstellt oder wiederhergestellt werden.",
|
||||
@@ -599,13 +600,13 @@
|
||||
"squadcast": "Squadcast",
|
||||
"SendKey": "SendKey",
|
||||
"SMSManager API Docs": "SMSManager API Dokumente ",
|
||||
"Gateway Type": "Gateway Type",
|
||||
"Gateway Type": "Gateway Typ",
|
||||
"SMSManager": "SMSManager",
|
||||
"You can divide numbers with": "Du kannst Zahlen teilen mit",
|
||||
"or": "oder",
|
||||
"recurringInterval": "Intervall",
|
||||
"Recurring": "Wiederkehrend",
|
||||
"strategyManual": "Active/Inactive Manually",
|
||||
"strategyManual": "Aktiv/Inaktiv Manuell",
|
||||
"warningTimezone": "Es wird die Zeitzone des Servers genutzt",
|
||||
"weekdayShortMon": "Mo",
|
||||
"weekdayShortTue": "Di",
|
||||
@@ -640,12 +641,12 @@
|
||||
"installing": "Installiere",
|
||||
"uninstall": "Deinstallieren",
|
||||
"uninstalling": "Deinstalliere",
|
||||
"confirmUninstallPlugin": "Möchten Sie dieses Plugin wirklich deinstallieren?",
|
||||
"confirmUninstallPlugin": "Möchtest du dieses Plugin wirklich deinstallieren?",
|
||||
"notificationRegional": "Regional",
|
||||
"Single Maintenance Window": "Einmaliges Wartungsfenster",
|
||||
"dnsCacheDescription": "In einigen IPv6-Umgebungen funktioniert es möglicherweise nicht. Deaktivieren Sie es, wenn Sie auf Probleme stossen.",
|
||||
"dnsCacheDescription": "In einigen IPv6-Umgebungen funktioniert es möglicherweise nicht. Deaktiviere es, wenn Probleme auftreten.",
|
||||
"Maintenance Time Window of a Day": "Wartungszeitfenster eines Tages",
|
||||
"Effective Date Range": "Gültigkeitsbereich",
|
||||
"Effective Date Range": "Gültigkeitsbereich (Optional)",
|
||||
"Schedule Maintenance": "Wartung planen",
|
||||
"Date and Time": "Datum und Uhrzeit",
|
||||
"DateTime Range": "Datums- und Zeitbereich",
|
||||
@@ -674,15 +675,15 @@
|
||||
"Don't expire": "Nicht ablaufen",
|
||||
"Add Another": "Hinzufügen",
|
||||
"Key Added": "Schlüssel hinzugefügt",
|
||||
"apiKeyAddedMsg": "Ihr API Schlüssel wurde hinzugefügt. Bitte notieren Sie Ihn, da er nicht erneut angezeigt wird.",
|
||||
"apiKeyAddedMsg": "API Schlüssel wurde hinzugefügt. Bitte notiere den Schlüssel, da er nicht erneut angezeigt wird.",
|
||||
"Add API Key": "API Schlüssel hinzufügen",
|
||||
"No API Keys": "Kein API Schlüssel",
|
||||
"apiKey-active": "Aktiv",
|
||||
"apiKey-expired": "Abgelaufen",
|
||||
"apiKey-inactive": "Inaktiv",
|
||||
"Expires": "Läuft ab",
|
||||
"disableAPIKeyMsg": "Sind Sie sicher, dass Sie diesen API Schlüssel deaktivieren möchten?",
|
||||
"deleteAPIKeyMsg": "Sind Sie sicher, dass Sie diesen API Schlüssel löschen möchten?",
|
||||
"disableAPIKeyMsg": "Bist du sicher, dass du diesen API Schlüssel deaktivieren willst?",
|
||||
"deleteAPIKeyMsg": "Bist du sicher, dass du diesen API Schlüssel löschen willst?",
|
||||
"Generate": "Generieren",
|
||||
"infiniteRetention": "Für unendliche Speicherung auf 0 setzen.",
|
||||
"dataRetentionTimeError": "Aufbewahrungsfrist muss grösser oder gleich 0 sein",
|
||||
@@ -691,8 +692,8 @@
|
||||
"cloneOf": "Klon von {0}",
|
||||
"wayToGetZohoCliqURL": "Wie eine Webhook URL erstellt werden kann, erfährst du {0}.",
|
||||
"enableGRPCTls": "Senden von gRPC Anforderungen mit TLS Verbindung zulassen",
|
||||
"grpcMethodDescription": "Der Name der Methode wird in das \"cammelCase \"-Format konvertiert (z.B. sayHello, check, etc.)",
|
||||
"wayToGetKookGuildID": "Schalten Sie den „Entwicklermodus“ in den Kook-Einstellungen ein und klicken Sie mit der rechten Maustaste auf die Gilde, um ihre ID zu erhalten",
|
||||
"grpcMethodDescription": "Der Name der Methode wird in das \"cammelCase\" Format konvertiert (z.B. sayHello, check, etc.)",
|
||||
"wayToGetKookGuildID": "Schalte den „Entwicklermodus“ in den Kook-Einstellungen ein und klicke mit der rechten Maustaste auf die Gilde, um die ID zu erhalten",
|
||||
"Guild ID": "Gilde ID",
|
||||
"Lowcost": "Kostengünstig",
|
||||
"high": "hoch",
|
||||
@@ -712,7 +713,7 @@
|
||||
"pagertreeCritical": "Kritisch",
|
||||
"pagertreeResolve": "Automatisch auflösen",
|
||||
"pagertreeDoNothing": "Nichts tun",
|
||||
"wayToGetPagerTreeIntegrationURL": "Nachdem Sie die Uptime Kuma Integration in PagerTree erstellt haben, kopieren Sie den Endpunkt. Siehe vollständige Details {0}",
|
||||
"wayToGetPagerTreeIntegrationURL": "Nachdem du die Uptime Kuma Integration in PagerTree erstellt hast, kopiere den Endpunkt. Siehe details {0}",
|
||||
"Server Address": "Serveradresse",
|
||||
"Learn More": "Erfahre mehr",
|
||||
"Edit Tag": "Tag editieren",
|
||||
@@ -724,8 +725,8 @@
|
||||
"smseagleEncoding": "Als Unicode senden",
|
||||
"smseaglePriority": "Nachrichtenpriorität (0-9, Standard = 0)",
|
||||
"smseagleContact": "Telefonbuch Kontaktname(n)",
|
||||
"confirmDeleteTagMsg": "Sind Sie sicher, dass Sie diesen Tag löschen möchten? Monitore, die mit diesem Tag verknüpft sind, werden nicht gelöscht.",
|
||||
"wayToGetKookBotToken": "Erstellen Sie eine Anwendung und erhalten Sie Ihren Bot-Token unter {0}",
|
||||
"confirmDeleteTagMsg": "Möchtest du dieses Tag wirklich löschen? Monitore, die mit diesem Tag verknüpft sind, werden nicht gelöscht.",
|
||||
"wayToGetKookBotToken": "Erstelle eine Anwendung und erhalte den Bot-Token unter {0}",
|
||||
"Strategy": "Strategie",
|
||||
"Free Mobile User Identifier": "Kostenlose mobile Benutzerkennung",
|
||||
"smseagleGroup": "Telefonbuch Gruppenname(n)",
|
||||
@@ -735,5 +736,42 @@
|
||||
"Add New Tag": "Neuen Tag hinzufügen",
|
||||
"lunaseaTarget": "Ziel",
|
||||
"lunaseaDeviceID": "Geräte-ID",
|
||||
"lunaseaUserID": "Benutzer-ID"
|
||||
"lunaseaUserID": "Benutzer-ID",
|
||||
"ntfyAuthenticationMethod": "Authentifizierungsmethode",
|
||||
"ntfyUsernameAndPassword": "Benutzername und Passwort",
|
||||
"twilioAccountSID": "Account SID",
|
||||
"twilioFromNumber": "Absender",
|
||||
"twilioToNumber": "Empfänger",
|
||||
"twilioAuthToken": "Auth Token",
|
||||
"statusPageRefreshIn": "Aktualisierung in: {0}",
|
||||
"sameAsServerTimezone": "Gleiche Zeitzone wie Server",
|
||||
"startDateTime": "Start Datum/Uhrzeit",
|
||||
"endDateTime": "Ende Datum/Uhrzeit",
|
||||
"cronExpression": "Cron-Ausdruck",
|
||||
"cronSchedule": "Zeitplan: ",
|
||||
"invalidCronExpression": "Ungültiger Cron-Ausdruck: {0}",
|
||||
"Open Badge Generator": "Open Badge Generator",
|
||||
"Badge Generator": "{0}'s Badge Generator",
|
||||
"Badge Type": "Badge Typ",
|
||||
"Badge Duration": "Badge Dauer",
|
||||
"Badge Label": "Badge Label",
|
||||
"Badge Prefix": "Badge Präfix",
|
||||
"Badge Suffix": "Badge Suffix",
|
||||
"Badge Label Color": "Badge Label Farbe",
|
||||
"Badge Color": "Badge Farbe",
|
||||
"Badge Label Prefix": "Badge Label Präfix",
|
||||
"Badge Up Color": "Badge Up Farbe",
|
||||
"Badge Maintenance Color": "Badge Wartung Farbe",
|
||||
"Badge Warn Color": "Badge Warnung Farbe",
|
||||
"Badge Warn Days": "Badge Warnung Tage",
|
||||
"Badge Style": "Badge Stil",
|
||||
"Badge URL": "Badge URL",
|
||||
"Badge Pending Color": "Badge Pending Farbe",
|
||||
"Badge Down Days": "Badge Down Tage",
|
||||
"Monitor Setting": "{0}'s Monitor Einstellung",
|
||||
"Show Clickable Link": "Klickbaren Link anzeigen",
|
||||
"Badge Label Suffix": "Badge Label Suffix",
|
||||
"Badge value (For Testing only.)": "Badge Wert (nur für Tests)",
|
||||
"Show Clickable Link Description": "Wenn diese Option aktiviert ist, kann jeder, der Zugriff auf diese Statusseite hat, auf die Monitor URL zugreifen.",
|
||||
"Badge Down Color": "Badge Down Farbe"
|
||||
}
|
||||
|
@@ -259,6 +259,7 @@
|
||||
"More info on:": "Mehr Infos auf: {0}",
|
||||
"pushoverDesc1": "Notfallpriorität (2) hat standardmäßig 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.",
|
||||
"pushoverMessageTtl": "Message TTL (Sekunden)",
|
||||
"SMS Type": "SMS Typ",
|
||||
"octopushTypePremium": "Premium (Schnell - zur Benachrichtigung empfohlen)",
|
||||
"octopushTypeLowCost": "Kostengünstig (Langsam - manchmal vom Betreiber gesperrt)",
|
||||
@@ -590,7 +591,7 @@
|
||||
"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}",
|
||||
"goAlertInfo": "GoAlert ist eine Open-Source Applikation für Rufbereitschaftsplanung, automatische Eskalation und Benachrichtigung (z.B. SMS oder Telefonanrufe). Engagiere 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 keine vollständige Sicherung erstellen oder wiederherstellen werden.",
|
||||
@@ -605,9 +606,9 @@
|
||||
"or": "oder",
|
||||
"recurringInterval": "Intervall",
|
||||
"Recurring": "Wiederkehrend",
|
||||
"Single Maintenance Window": "Einzigartiges Wartungsfenster",
|
||||
"Single Maintenance Window": "Einmaliges Wartungsfenster",
|
||||
"Maintenance Time Window of a Day": "Zeitfenster für die Wartung",
|
||||
"Effective Date Range": "Bereich der Wirksamkeitsdaten",
|
||||
"Effective Date Range": "Bereich der Wirksamkeitsdaten (Optional)",
|
||||
"strategyManual": "Aktiv/Inaktiv Manuell",
|
||||
"warningTimezone": "Es wird die Zeitzone des Servers verwendet",
|
||||
"weekdayShortMon": "Mo",
|
||||
@@ -646,19 +647,19 @@
|
||||
"Disable": "Deaktivieren",
|
||||
"Custom Monitor Type": "Benutzerdefinierter Monitortyp",
|
||||
"webhookAdditionalHeadersDesc": "Legt zusätzliche Header fest, die mit der Webhook gesendet wurden.",
|
||||
"dnsCacheDescription": "In einigen IPv6-Umgebungen funktioniert es möglicherweise nicht. Deaktivieren Sie es, wenn Sie auf Probleme stoßen.",
|
||||
"dnsCacheDescription": "In einigen IPv6-Umgebungen funktioniert es möglicherweise nicht. Deaktiviere es, wenn Probleme auftreten.",
|
||||
"loadingError": "Die Daten konnten nicht abgerufen werden, bitte später noch einmal versuchen.",
|
||||
"confirmUninstallPlugin": "Möchten Sie dieses Plugin wirklich deinstallieren?",
|
||||
"grpcMethodDescription": "Der Name der Methode wird in das \"cammelCase \"-Format konvertiert (z.B. sayHello, check, etc.)",
|
||||
"confirmUninstallPlugin": "Möchtest du dieses Plugin wirklich deinstallieren?",
|
||||
"grpcMethodDescription": "Der Name der Methode wird in das \"cammelCase\"-Format konvertiert (z.B. sayHello, check, etc.)",
|
||||
"Passive Monitor Type": "Passiver Monitortyp",
|
||||
"Specific Monitor Type": "Spezifischer Monitortyp",
|
||||
"webhookAdditionalHeadersTitle": "Zusätzliche Header",
|
||||
"Packet Size": "Paketgröße",
|
||||
"IconUrl": "Symbol-URL",
|
||||
"wayToGetZohoCliqURL": "Erfahren Sie, wie Sie eine Webhook-URL {0} erstellen.",
|
||||
"wayToGetZohoCliqURL": "Wie eine Webhook URL erstellt werden kann, erfährst du {0}.",
|
||||
"dataRetentionTimeError": "Aufbewahrungszeit muss 0 oder größer sein",
|
||||
"infiniteRetention": "Für unendliche Aufbewahrung auf 0 setzen.",
|
||||
"confirmDeleteTagMsg": "Möchten Sie dieses Tag wirklich löschen? Mit diesem Tag verknüpfte Monitore werden nicht gelöscht.",
|
||||
"confirmDeleteTagMsg": "Möchtest du dieses Tag wirklich löschen? Monitore, die mit diesem Tag verknüpft sind, werden nicht gelöscht.",
|
||||
"enableGRPCTls": "Senden von gRPC-Anforderungen mit TLS-Verbindung zulassen",
|
||||
"ZohoCliq": "ZohoCliq",
|
||||
"Monitor": "Überwachung | Monitore",
|
||||
@@ -668,8 +669,8 @@
|
||||
"uninstall": "Deinstallieren",
|
||||
"uninstalling": "Deinstallation",
|
||||
"markdownSupported": "Markdown-Syntax unterstützt",
|
||||
"wayToGetKookBotToken": "Erstellen Sie eine Anwendung und erhalten Sie Ihren Bot-Token unter {0}",
|
||||
"wayToGetKookGuildID": "Schalten Sie den „Entwicklermodus“ in den Kook-Einstellungen ein und klicken Sie mit der rechten Maustaste auf die Gilde, um ihre ID zu erhalten",
|
||||
"wayToGetKookBotToken": "Erstelle eine Anwendung und erhalte den Bot-Token unter {0}",
|
||||
"wayToGetKookGuildID": "Schalte den „Entwicklermodus“ in den Kook-Einstellungen ein und klicke mit der rechten Maustaste auf die Gilde, um die ID zu erhalten",
|
||||
"Guild ID": "Guild-ID",
|
||||
"Free Mobile User Identifier": "Kostenlose mobile Benutzerkennung",
|
||||
"Free Mobile API Key": "Kostenloser Mobile API-Schlüssel",
|
||||
@@ -687,7 +688,7 @@
|
||||
"smseagleGroup": "Telefonbuch Gruppenname(n)",
|
||||
"smseagleContact": "Telefonbuch Kontaktname(n)",
|
||||
"smseagleRecipientType": "Empfängertyp",
|
||||
"smseagleRecipient": "Empfänger (mehrere müssen mit Komma getrennt werden)",
|
||||
"smseagleRecipient": "Empfänger (mehrere müssen durch Komma getrennt werden)",
|
||||
"smseagleToken": "API-Zugriffstoken",
|
||||
"smseagleUrl": "Ihre SMSEagle-Geräte-URL",
|
||||
"Kook": "Kook",
|
||||
@@ -730,7 +731,7 @@
|
||||
"telegramProtectContentDescription": "Die Bot-Nachrichten in Telegram sind gegen Weiterleitung und Speichern geschützt.",
|
||||
"notificationRegional": "Regional",
|
||||
"Key Added": "Schlüssel hinzugefügt",
|
||||
"apiKeyAddedMsg": "Ihr API Schlüssel wurde hinzugefügt. Bitte notieren Sie Ihn, da er nicht erneut angezeigt wird.",
|
||||
"apiKeyAddedMsg": "API Schlüssel wurde hinzugefügt. Bitte notiere den Schlüssel, da er nicht erneut angezeigt wird.",
|
||||
"telegramMessageThreadID": "(Optional) Nachrichten Thread ID",
|
||||
"telegramMessageThreadIDDescription": "Optionale eindeutige Kennung für den Ziel-Thread (Thema) des Forums; nur für Forum-Supergroups",
|
||||
"telegramSendSilently": "Stumm Senden",
|
||||
@@ -738,5 +739,44 @@
|
||||
"Add New Tag": "Neuen Tag hinzufügen",
|
||||
"lunaseaDeviceID": "Geräte-ID",
|
||||
"lunaseaTarget": "Ziel",
|
||||
"lunaseaUserID": "Benutzer-ID"
|
||||
"lunaseaUserID": "Benutzer-ID",
|
||||
"ntfyAuthenticationMethod": "Authentifizierungsmethode",
|
||||
"ntfyUsernameAndPassword": "Benutzername und Passwort",
|
||||
"twilioAccountSID": "Account SID",
|
||||
"twilioFromNumber": "Absender",
|
||||
"twilioToNumber": "Empfänger",
|
||||
"twilioAuthToken": "Auth Token",
|
||||
"statusPageRefreshIn": "Aktualisierung in: {0}",
|
||||
"sameAsServerTimezone": "Gleiche Zeitzone wie Server",
|
||||
"startDateTime": "Start Datum/Uhrzeit",
|
||||
"endDateTime": "Ende Datum/Uhrzeit",
|
||||
"cronExpression": "Cron-Ausdruck",
|
||||
"cronSchedule": "Zeitplan: ",
|
||||
"invalidCronExpression": "Ungültiger Cron-Ausdruck: {0}",
|
||||
"Show Clickable Link": "Klickbaren Link anzeigen",
|
||||
"Open Badge Generator": "Open Badge Generator",
|
||||
"Badge Generator": "{0}'s Badge Generator",
|
||||
"Badge Type": "Badge Typ",
|
||||
"Badge Duration": "Badge Dauer",
|
||||
"Badge Label": "Badge Label",
|
||||
"Show Clickable Link Description": "Wenn diese Option aktiviert ist, kann jeder, der Zugriff auf diese Statusseite hat, auf die Monitor-URL zugreifen.",
|
||||
"Badge Label Color": "Badge Label Farbe",
|
||||
"Badge Color": "Badge Farbe",
|
||||
"Badge Label Prefix": "Badge Label Präfix",
|
||||
"Badge Label Suffix": "Badge Label Suffix",
|
||||
"Badge Maintenance Color": "Badge Wartung Farbe",
|
||||
"Badge Warn Color": "Badge Warnung Farbe",
|
||||
"Badge Style": "Badge Stil",
|
||||
"Badge value (For Testing only.)": "Badge Wert (nur für Tests)",
|
||||
"Badge URL": "Badge URL",
|
||||
"Badge Up Color": "Badge Up Farbe",
|
||||
"Badge Down Color": "Badge Down Farbe",
|
||||
"Badge Pending Color": "Badge Pending Farbe",
|
||||
"Badge Down Days": "Badge Down Tage",
|
||||
"Monitor Setting": "{0}'s Monitor Einstellung",
|
||||
"Badge Prefix": "Badge Präfix",
|
||||
"Badge Suffix": "Badge Suffix",
|
||||
"Badge Warn Days": "Badge Warnung Tage",
|
||||
"Group": "Gruppe",
|
||||
"Monitor Group": "Monitor Gruppe"
|
||||
}
|
||||
|
@@ -695,5 +695,7 @@
|
||||
"Learn More": "Μάθετε περισσότερα",
|
||||
"Free Mobile User Identifier": "Free Mobile User Identifier",
|
||||
"Free Mobile API Key": "Free Mobile API Key",
|
||||
"smseaglePriority": "Προτεραιότητα μηνύματος (0-9, προεπιλογή = 0)"
|
||||
"smseaglePriority": "Προτεραιότητα μηνύματος (0-9, προεπιλογή = 0)",
|
||||
"statusPageRefreshIn": "Ανανέωση σε {0}",
|
||||
"Add New Tag": "Πρόσθεσε νέα ετικέτα"
|
||||
}
|
||||
|
@@ -174,6 +174,7 @@
|
||||
"Avg. Response": "Avg. Response",
|
||||
"Entry Page": "Entry Page",
|
||||
"statusPageNothing": "Nothing here, please add a group or a monitor.",
|
||||
"statusPageRefreshIn": "Refresh in: {0}",
|
||||
"No Services": "No Services",
|
||||
"All Systems Operational": "All Systems Operational",
|
||||
"Partially Degraded Service": "Partially Degraded Service",
|
||||
@@ -393,6 +394,12 @@
|
||||
"backupRecommend": "Please backup the volume or the data folder (./data/) directly instead.",
|
||||
"Optional": "Optional",
|
||||
"or": "or",
|
||||
"sameAsServerTimezone": "Same as Server Timezone",
|
||||
"startDateTime": "Start Date/Time",
|
||||
"endDateTime": "End Date/Time",
|
||||
"cronExpression": "Cron Expression",
|
||||
"cronSchedule": "Schedule: ",
|
||||
"invalidCronExpression": "Invalid Cron Expression: {0}",
|
||||
"recurringInterval": "Interval",
|
||||
"Recurring": "Recurring",
|
||||
"strategyManual": "Active/Inactive Manually",
|
||||
@@ -428,7 +435,7 @@
|
||||
"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",
|
||||
"Effective Date Range": "Effective Date Range (Optional)",
|
||||
"Schedule Maintenance": "Schedule Maintenance",
|
||||
"Date and Time": "Date and Time",
|
||||
"DateTime Range": "DateTime Range",
|
||||
@@ -549,6 +556,7 @@
|
||||
"More info on:": "More info on: {0}",
|
||||
"pushoverDesc1": "Emergency priority (2) has default 30 second timeout between retries and will expire after 1 hour.",
|
||||
"pushoverDesc2": "If you want to send notifications to different devices, fill out Device field.",
|
||||
"pushoverMessageTtl": "Message TTL (Seconds)",
|
||||
"SMS Type": "SMS Type",
|
||||
"octopushTypePremium": "Premium (Fast - recommended for alerting)",
|
||||
"octopushTypeLowCost": "Low Cost (Slow - sometimes blocked by operator)",
|
||||
@@ -706,5 +714,37 @@
|
||||
"wayToGetPagerTreeIntegrationURL": "After creating the Uptime Kuma integration in PagerTree, copy the Endpoint. See full details {0}",
|
||||
"lunaseaTarget": "Target",
|
||||
"lunaseaDeviceID": "Device ID",
|
||||
"lunaseaUserID": "User ID"
|
||||
"lunaseaUserID": "User ID",
|
||||
"ntfyAuthenticationMethod": "Authentication Method",
|
||||
"ntfyUsernameAndPassword": "Username and Password",
|
||||
"twilioAccountSID": "Account SID",
|
||||
"twilioAuthToken": "Auth Token",
|
||||
"twilioFromNumber": "From Number",
|
||||
"twilioToNumber": "To Number",
|
||||
"Monitor Setting": "{0}'s Monitor Setting",
|
||||
"Show Clickable Link": "Show Clickable Link",
|
||||
"Show Clickable Link Description": "If checked everyone who have access to this status page can have access to monitor URL.",
|
||||
"Open Badge Generator": "Open Badge Generator",
|
||||
"Badge Generator": "{0}'s Badge Generator",
|
||||
"Badge Type": "Badge Type",
|
||||
"Badge Duration": "Badge Duration",
|
||||
"Badge Label": "Badge Label",
|
||||
"Badge Prefix": "Badge Prefix",
|
||||
"Badge Suffix": "Badge Suffix",
|
||||
"Badge Label Color": "Badge Label Color",
|
||||
"Badge Color": "Badge Color",
|
||||
"Badge Label Prefix": "Badge Label Prefix",
|
||||
"Badge Label Suffix": "Badge Label Suffix",
|
||||
"Badge Up Color": "Badge Up Color",
|
||||
"Badge Down Color": "Badge Down Color",
|
||||
"Badge Pending Color": "Badge Pending Color",
|
||||
"Badge Maintenance Color": "Badge Maintenance Color",
|
||||
"Badge Warn Color": "Badge Warn Color",
|
||||
"Badge Warn Days": "Badge Warn Days",
|
||||
"Badge Down Days": "Badge Down Days",
|
||||
"Badge Style": "Badge Style",
|
||||
"Badge value (For Testing only.)": "Badge value (For Testing only.)",
|
||||
"Badge URL": "Badge URL",
|
||||
"Group": "Group",
|
||||
"Monitor Group": "Monitor Group"
|
||||
}
|
||||
|
@@ -303,7 +303,7 @@
|
||||
"Maintenance": "Mantenimiento",
|
||||
"General Monitor Type": "Monitor Tipo General",
|
||||
"Specific Monitor Type": "Monitor Tipo Específico",
|
||||
"Monitor": "Monitores",
|
||||
"Monitor": "Monitor | Monitores",
|
||||
"Resend Notification if Down X times consecutively": "Reenviar Notificación si Caído X veces consecutivamente",
|
||||
"resendEveryXTimes": "Reenviar cada {0} veces",
|
||||
"resendDisabled": "Reenvío deshabilitado",
|
||||
@@ -679,15 +679,15 @@
|
||||
"serwersms": "SerwerSMS.pl",
|
||||
"serwersmsAPIUser": "Nombre de usuario de API (inc. webapi_ prefix)",
|
||||
"smseagleGroup": "Nombre(s) de grupo de Guía Telefónica",
|
||||
"Unpin": "Quitar de destacados",
|
||||
"Unpin": "Dejar de Fijar",
|
||||
"Prefix Custom Message": "Prefijo personalizado",
|
||||
"markdownSupported": "Soporta sintaxis Markdown",
|
||||
"markdownSupported": "Sintaxis de Markdown soportada",
|
||||
"Server Address": "Dirección del Servidor",
|
||||
"Learn More": "Aprende Más",
|
||||
"Pick a RR-Type...": "Seleccione un Tipo RR",
|
||||
"onebotHttpAddress": "Dirección HTTP OneBot",
|
||||
"SendKey": "Clave de Envío",
|
||||
"octopushAPIKey": "\"Clave API\" de las credenciales HTTP API en el panel de control",
|
||||
"octopushAPIKey": "\"Clave API\" desde credenciales API HTTP en panel de control",
|
||||
"octopushLogin": "\"Inicio de Sesión\" a partir de las credenciales API HTTP en el panel de control",
|
||||
"ntfy Topic": "Tema ntfy",
|
||||
"Google Analytics ID": "ID Analíticas de Google",
|
||||
@@ -696,5 +696,60 @@
|
||||
"Bark Endpoint": "Endpoint Bark",
|
||||
"WebHookUrl": "WebHookUrl",
|
||||
"High": "Alto",
|
||||
"alertaApiEndpoint": "Endpoint API"
|
||||
"alertaApiEndpoint": "Endpoint API",
|
||||
"Body Encoding": "Codificación del cuerpo",
|
||||
"Expiry date": "Fecha de expiración",
|
||||
"Expiry": "Expiración",
|
||||
"API Keys": "Claves API",
|
||||
"Key Added": "Clave añadida",
|
||||
"Add Another": "Añadir otro",
|
||||
"Continue": "Continuar",
|
||||
"Don't expire": "No caduca",
|
||||
"apiKey-inactive": "Inactivo",
|
||||
"apiKey-expired": "Expirado",
|
||||
"apiKey-active": "Activo",
|
||||
"No API Keys": "No hay claves API",
|
||||
"Add API Key": "Añadir clave API",
|
||||
"apiKeyAddedMsg": "Su clave API ha sido añadida. Anótala, ya que no se volverá a mostrar.",
|
||||
"Clone": "Clonar",
|
||||
"cloneOf": "Clon de {0}",
|
||||
"pagertreeDoNothing": "No hacer nada",
|
||||
"pagertreeResolve": "Resolución automática",
|
||||
"pagertreeCritical": "Crítico",
|
||||
"pagertreeHigh": "Alto",
|
||||
"pagertreeMedium": "Medio",
|
||||
"pagertreeLow": "Bajo",
|
||||
"pagertreeSilent": "Silencio",
|
||||
"pagertreeUrgency": "Urgencia",
|
||||
"pagertreeIntegrationUrl": "URL de integración",
|
||||
"lunaseaTarget": "Objetivo",
|
||||
"wayToGetPagerTreeIntegrationURL": "Después de crear la integración Uptime Kuma en PagerTree, copie el Endpoint. Ver todos los detalles {0}",
|
||||
"Generate": "Generar",
|
||||
"deleteAPIKeyMsg": "¿Está seguro de que desea eliminar esta clave API?",
|
||||
"telegramMessageThreadID": "(Opcional) ID del hilo de mensajes",
|
||||
"telegramMessageThreadIDDescription": "Opcional Identificador único para el hilo de mensajes de destino (asunto) del foro; solo para supergrupos de foros",
|
||||
"telegramProtectContent": "Proteger Forwarding/Saving",
|
||||
"telegramProtectContentDescription": "Si se activa, los mensajes del bot en Telegram estarán protegidos contra el reenvío y el guardado.",
|
||||
"notificationRegional": "Regional",
|
||||
"Clone Monitor": "Clonar Monitor",
|
||||
"telegramSendSilently": "Enviar en silencio",
|
||||
"telegramSendSilentlyDescription": "Envía el mensaje en silencio. Los usuarios recibirán una notificación sin sonido.",
|
||||
"Add New Tag": "Añadir nueva etiqueta",
|
||||
"lunaseaUserID": "ID Usuario",
|
||||
"lunaseaDeviceID": "ID Dispositivo",
|
||||
"disableAPIKeyMsg": "¿Está seguro de que desea desactivar esta clave API?",
|
||||
"Expires": "Expira",
|
||||
"twilioAccountSID": "SID de Cuenta",
|
||||
"twilioFromNumber": "Desde el numero",
|
||||
"twilioToNumber": "Hasta el numero",
|
||||
"startDateTime": "Fecha/Hora Inicio",
|
||||
"sameAsServerTimezone": "Igual a Zona horaria del Servidor",
|
||||
"endDateTime": "Fecha/Hora Fin",
|
||||
"cronExpression": "Expresión Cron",
|
||||
"cronSchedule": "Cronograma: ",
|
||||
"invalidCronExpression": "Expresión Cron invalida:{0}",
|
||||
"statusPageRefreshIn": "Reinicio en: {0}",
|
||||
"twilioAuthToken": "Token de Autentificación",
|
||||
"ntfyUsernameAndPassword": "Nombre de Usuario y Contraseña",
|
||||
"ntfyAuthenticationMethod": "Método de Autentificación"
|
||||
}
|
||||
|
@@ -74,7 +74,7 @@
|
||||
"Heartbeat Retry Interval": "Pultsu errepikatze interbaloak",
|
||||
"Advanced": "Aurreratua",
|
||||
"Upside Down Mode": "Alderantzizkako modua",
|
||||
"Max. Redirects": "Berbideratze max.",
|
||||
"Max. Redirects": "Birbideratze max.",
|
||||
"Accepted Status Codes": "Onartutako egoera kodeak",
|
||||
"Push URL": "Push URLa",
|
||||
"needPushEvery": "URL hau {0} segunduro deitu beharko zenuke.",
|
||||
@@ -159,7 +159,7 @@
|
||||
"Token": "Tokena",
|
||||
"Show URI": "Erakutsi URIa",
|
||||
"Tags": "Etiketak",
|
||||
"Add New below or Select...": "Gehitu beste bat behean edo hautatu...",
|
||||
"Add New below or Select...": "Gehitu beste bat behean edo hautatu…",
|
||||
"Tag with this name already exist.": "Izen hau duen etiketa dagoeneko badago.",
|
||||
"Tag with this value already exist.": "Balio hau duen etiketa dagoeneko badago.",
|
||||
"color": "kolorea",
|
||||
@@ -172,7 +172,7 @@
|
||||
"Indigo": "Indigo",
|
||||
"Purple": "Morea",
|
||||
"Pink": "Arrosa",
|
||||
"Search...": "Bilatu...",
|
||||
"Search...": "Bilatu…",
|
||||
"Avg. Ping": "Batazbesteko Pinga",
|
||||
"Avg. Response": "Batazbesteko erantzuna",
|
||||
"Entry Page": "Sarrera orria",
|
||||
@@ -218,7 +218,7 @@
|
||||
"wayToGetDiscordURL": "You can get this by going to Server Settings -> Integrations -> Create Webhook",
|
||||
"Bot Display Name": "Bot Display Name",
|
||||
"Prefix Custom Message": "Prefix Custom Message",
|
||||
"Hello @everyone is...": "Hello {'@'}everyone is...",
|
||||
"Hello @everyone is...": "Kaixo {'@'}edonor da…",
|
||||
"teams": "Microsoft Teams",
|
||||
"Webhook URL": "Webhook URL",
|
||||
"wayToGetTeamsURL": "You can learn how to create a webhook URL {0}.",
|
||||
@@ -325,7 +325,7 @@
|
||||
"Steam API Key": "Steam API Giltza",
|
||||
"Shrink Database": "Shrink Datubasea",
|
||||
"Pick a RR-Type...": "Pick a RR-Type...",
|
||||
"Pick Accepted Status Codes...": "Hautatu onartutako egoera kodeak...",
|
||||
"Pick Accepted Status Codes...": "Hautatu onartutako egoera kodeak…",
|
||||
"Default": "Lehenetsia",
|
||||
"HTTP Options": "HTTP Aukerak",
|
||||
"Create Incident": "Sortu inzidentzia",
|
||||
@@ -527,7 +527,7 @@
|
||||
"There might be a typing error in the address.": "Idazketa-akats bat egon daiteke helbidean.",
|
||||
"What you can try:": "Probatu dezakezuna:",
|
||||
"Retype the address.": "Berridatzi helbidea.",
|
||||
"Go back to the previous page.": "Itzuli aurreko orrialdera",
|
||||
"Go back to the previous page.": "Itzuli aurreko orrialdera.",
|
||||
"Coming Soon": "Laster",
|
||||
"wayToGetClickSendSMSToken": "API erabiltzailea and API giltza hemendik lortu ditzakezu: {0} .",
|
||||
"Connection String": "Konexio katea",
|
||||
@@ -537,5 +537,39 @@
|
||||
"ntfy Topic": "ntfy Topic",
|
||||
"Domain": "Domeinua",
|
||||
"Workstation": "Lan gunea",
|
||||
"disableCloudflaredNoAuthMsg": "Ez Auth moduan zaude, pasahitza ez da beharrezkoa."
|
||||
"disableCloudflaredNoAuthMsg": "Ez Auth moduan zaude, pasahitza ez da beharrezkoa.",
|
||||
"maintenanceStatus-ended": "Bukatuta",
|
||||
"maintenanceStatus-unknown": "Ezezaguna",
|
||||
"Enable": "Gaitu",
|
||||
"Strategy": "Estrategia",
|
||||
"General Monitor Type": "Monitorizazio mota orokorra",
|
||||
"Select status pages...": "Hautatu egoera orriak…",
|
||||
"Server Address": "Zerbitzari helbidea",
|
||||
"Learn More": "Ikasi gehiago",
|
||||
"weekdayShortTue": "Ast",
|
||||
"weekdayShortWed": "Asz",
|
||||
"Disable": "Desgaitu",
|
||||
"warningTimezone": "Zerbitzariaren orduzona erabiltzen ari da",
|
||||
"weekdayShortThu": "Og",
|
||||
"weekdayShortMon": "Asl",
|
||||
"Base URL": "Oinarri URLa",
|
||||
"high": "altua",
|
||||
"Economy": "Ekonomia",
|
||||
"Help": "Laguntza",
|
||||
"Game": "Jokoa",
|
||||
"statusMaintenance": "Mantenuan",
|
||||
"Maintenance": "Mantenua",
|
||||
"Passive Monitor Type": "Monitorizazio mota pasiboa",
|
||||
"Specific Monitor Type": "Zehaztutako monitorizazio mota",
|
||||
"markdownSupported": "Markdown sintaxia onartzen du",
|
||||
"Monitor": "Monitorizazio | Monitorizazioak",
|
||||
"resendDisabled": "Berbidaltzea desgaituta",
|
||||
"weekdayShortFri": "Ost",
|
||||
"weekdayShortSat": "Lar",
|
||||
"weekdayShortSun": "Iga",
|
||||
"dayOfWeek": "Asteko eguna",
|
||||
"dayOfMonth": "Hilabeteko eguna",
|
||||
"lastDay": "Azken eguna",
|
||||
"lastDay1": "Hilabeteko azken eguna",
|
||||
"Resend Notification if Down X times consecutively": "Bidali jakinarazpena X aldiz jarraian erortzen bada"
|
||||
}
|
||||
|
610
src/lang/fa.json
610
src/lang/fa.json
@@ -1,34 +1,34 @@
|
||||
{
|
||||
"languageName": "Farsi",
|
||||
"checkEverySecond": "بررسی هر {0} ثانیه.",
|
||||
"retryCheckEverySecond": "تکرار مجدد هر {0} ثانیه.",
|
||||
"retriesDescription": "حداکثر تعداد تکرار پیش از علامت گذاری وبسایت بعنوان خارج از دسترس و ارسال اطلاعرسانی.",
|
||||
"languageName": "فارسی",
|
||||
"checkEverySecond": "بررسی هر {0} ثانیه",
|
||||
"retryCheckEverySecond": "تکرار مجدد هر {0} ثانیه",
|
||||
"retriesDescription": "حداکثر تعداد تکرار پیش از علامت گذاری وبسایت بعنوان خارج از دسترس و ارسال اطلاعرسانی",
|
||||
"ignoreTLSError": "بیخیال ارور TLS/SSL برای سایتهای HTTPS",
|
||||
"upsideDownModeDescription": "نتیجه وضعیت را برعکس کن، مثلا اگر سرویس در دسترس بود فرض کن که سرویس پایین است!",
|
||||
"upsideDownModeDescription": "نتیجه وضعیت را برعکس کن، مثلا اگر سرویس در دسترس بود فرض کن که سرویس پایین است.",
|
||||
"maxRedirectDescription": "حداکثر تعداد ریدایرکتی که سرویس پشتیبانی کند. برای اینکه ریدایرکتها پشتیبانی نشوند، عدد 0 را وارد کنید.",
|
||||
"acceptedStatusCodesDescription": "لطفا HTTP Status Code هایی که میخواهید به عنوان پاسخ موفقیت آمیز در نظر گرفته شود را انتخاب کنید.",
|
||||
"passwordNotMatchMsg": "تکرار رمز عبور مطابقت ندارد!",
|
||||
"passwordNotMatchMsg": "تکرار رمز عبور مطابقت ندارد.",
|
||||
"notificationDescription": "برای اینکه سرویس اطلاعرسانی کار کند، آنرا به یکی از مانیتورها متصل کنید.",
|
||||
"keywordDescription": "در نتیجه درخواست (اهمیتی ندارد پاسخ JSON است یا HTML) بدنبال این کلمه بگرد (حساس به کوچک/بزرگ بودن حروف).",
|
||||
"pauseDashboardHome": "متوقف شده",
|
||||
"deleteMonitorMsg": "آیا از حذف این مانیتور مطمئن هستید؟",
|
||||
"deleteNotificationMsg": "آیا مطمئن هستید که میخواهید این سرویس اطلاعرسانی را برای تمامی مانیتورها حذف کنید؟",
|
||||
"resolverserverDescription": "سرویس CloudFlare به عنوان سرور پیشفرض استفاده میشود، شما میتوانید آنرا به هر سرور دیگری بعدا تغییر دهید.",
|
||||
"rrtypeDescription": "لطفا نوع Resource Record را انتخاب کنید.",
|
||||
"rrtypeDescription": "لطفا نوع Resource Record را انتخاب کنید",
|
||||
"pauseMonitorMsg": "آیا مطمئن هستید که میخواهید این مانیتور را متوقف کنید ؟",
|
||||
"enableDefaultNotificationDescription": "برای هر مانیتور جدید، این سرویس اطلاعرسانی به صورت پیشفرض فعال خواهد شد. البته که شما میتوانید به صورت دستی آنرا برای هر مانیتور به صورت جداگانه غیر فعال کنید.",
|
||||
"clearEventsMsg": "آیا از اینکه تمامی تاریخچه رویدادهای این مانیتور حذف شود مطمئن هستید؟",
|
||||
"clearHeartbeatsMsg": "آیا از اینکه تاریخچه تمامی Heartbeat های این مانیتور حذف شود مطمئن هستید؟ ",
|
||||
"clearHeartbeatsMsg": "آیا از اینکه تاریخچه تمامی ضربان قلب های این مانیتور حذف شود مطمئن هستید؟",
|
||||
"confirmClearStatisticsMsg": "آیا از حذف تمامی آمار و ارقام مطمئن هستید؟",
|
||||
"importHandleDescription": " اگر که میخواهید بیخیال مانیتورها و یا سرویسهای اطلاعرسانی که با نام مشابه از قبل موجود هستند شوید، گزینه 'بیخیال موارد ..' را انتخاب کنید. توجه کنید که گزینه 'بازنویسی' تمامی موارد موجود با نام مشابه را از بین خواهد برد.",
|
||||
"confirmImportMsg": "آیا از بازگردانی بک آپ مطمئن هستید؟ لطفا از اینکه نوع بازگردانی درستی را انتخاب کردهاید اطمینان حاصل کنید!",
|
||||
"twoFAVerifyLabel": "لطفا جهت اطمینان از عملکرد احراز هویت دو مرحلهای توکن خود را وارد کنید!",
|
||||
"tokenValidSettingsMsg": "توکن شما معتبر است، هم اکنون میتوانید احراز هویت دو مرحلهای را فعال کنید!",
|
||||
"confirmEnableTwoFAMsg": " آیا از فعال سازی احراز هویت دو مرحلهای مطمئن هستید؟",
|
||||
"importHandleDescription": "اگر که میخواهید بیخیال مانیتورها و یا سرویسهای اطلاعرسانی که با نام مشابه از قبل موجود هستند شوید، گزینه 'بیخیال موارد ..' را انتخاب کنید. توجه کنید که گزینه 'بازنویسی' تمامی موارد موجود با نام مشابه را از بین خواهد برد.",
|
||||
"confirmImportMsg": "آیا از بازگردانی بک آپ مطمئن هستید؟ لطفا از اینکه نوع بازگردانی درستی را انتخاب کردهاید اطمینان حاصل کنید.",
|
||||
"twoFAVerifyLabel": "لطفا جهت اطمینان از عملکرد احراز هویت دو مرحلهای توکن خود را وارد کنید:",
|
||||
"tokenValidSettingsMsg": "توکن شما معتبر است، هم اکنون میتوانید احراز هویت دو مرحلهای را فعال کنید.",
|
||||
"confirmEnableTwoFAMsg": "آیا از فعال سازی احراز هویت دو مرحلهای مطمئن هستید؟",
|
||||
"confirmDisableTwoFAMsg": "آیا از غیرفعال سازی احراز هویت دومرحلهای مطمئن هستید؟",
|
||||
"Settings": "تنظیمات",
|
||||
"Dashboard": "پیشخوان",
|
||||
"New Update": "بروزرسانی جدید!",
|
||||
"New Update": "بروزرسانی جدید",
|
||||
"Language": "زبان",
|
||||
"Appearance": "ظاهر",
|
||||
"Theme": "پوسته",
|
||||
@@ -48,13 +48,13 @@
|
||||
"Status": "وضعیت",
|
||||
"DateTime": "تاریخ و زمان",
|
||||
"Message": "پیام",
|
||||
"No important events": "رخداد جدیدی موجود نیست.",
|
||||
"No important events": "رخداد جدیدی موجود نیست",
|
||||
"Resume": "ادامه",
|
||||
"Edit": "ویرایش",
|
||||
"Delete": "حذف",
|
||||
"Current": "فعلی",
|
||||
"Uptime": "آپتایم",
|
||||
"Cert Exp.": "تاریخ انقضای SSL",
|
||||
"Cert Exp.": "تاریخ انقضای SSL.",
|
||||
"day": "روز",
|
||||
"-day": "-روز",
|
||||
"hour": "ساعت",
|
||||
@@ -76,7 +76,7 @@
|
||||
"Accepted Status Codes": "وضعیتهای (Status Code) های قابل قبول",
|
||||
"Save": "ذخیره",
|
||||
"Notifications": "اطلاعرسانیها",
|
||||
"Not available, please setup.": "هیچ موردی موجود نیست، اولین مورد را راه اندازی کنید!",
|
||||
"Not available, please setup.": "هیچ موردی موجود نیست، اولین مورد را راه اندازی کنید.",
|
||||
"Setup Notification": "راه اندازی اطلاعرسانی",
|
||||
"Light": "روشن",
|
||||
"Dark": "تاریک",
|
||||
@@ -87,8 +87,8 @@
|
||||
"None": "هیچ کدام",
|
||||
"Timezone": "موقعیت زمانی",
|
||||
"Search Engine Visibility": "قابلیت دسترسی برای موتورهای جستجو",
|
||||
"Allow indexing": "اجازه ایندکس شدن را بده.",
|
||||
"Discourage search engines from indexing site": "به موتورهای جستجو اجازه ایندکس کردن این سامانه را نده.",
|
||||
"Allow indexing": "اجازه ایندکس شدن در موتور های جستجو را بده",
|
||||
"Discourage search engines from indexing site": "به موتورهای جستجو اجازه ایندکس کردن این سامانه را نده",
|
||||
"Change Password": "تغییر رمزعبور",
|
||||
"Current Password": "رمزعبور فعلی",
|
||||
"New Password": "رمزعبور جدید",
|
||||
@@ -98,10 +98,10 @@
|
||||
"Enable Auth": "فعال سازی تایید هویت",
|
||||
"disableauth.message1": "آیا مطمئن هستید که میخواهید <strong>احراز هویت را غیر فعال کنید</strong>?",
|
||||
"disableauth.message2": "این ویژگی برای کسانی است که <strong> لایه امنیتی شخص ثالث دیگر بر روی این آدرس فعال کردهاند</strong>، مانند Cloudflare Access.",
|
||||
"Please use this option carefully!": "لطفا از این امکان با دقت استفاده کنید.",
|
||||
"Please use this option carefully!": "لطفا از این امکان با دقت استفاده کنید!",
|
||||
"Logout": "خروج",
|
||||
"Leave": "منصرف شدم",
|
||||
"I understand, please disable": "متوجه هستم، لطفا غیرفعال کنید!",
|
||||
"I understand, please disable": "متوجه هستم، غیرفعال کن",
|
||||
"Confirm": "تایید",
|
||||
"Yes": "بلی",
|
||||
"No": "خیر",
|
||||
@@ -126,12 +126,12 @@
|
||||
"Import": "ورود اطلاعات",
|
||||
"respTime": "زمان پاسخگویی (میلیثانیه)",
|
||||
"notAvailableShort": "ناموجود",
|
||||
"Default enabled": "به صورت پیشفرض فعال باشد.",
|
||||
"Apply on all existing monitors": "بر روی تمامی مانیتورهای فعلی اعمال شود.",
|
||||
"Default enabled": "به صورت پیشفرض فعال باشد",
|
||||
"Apply on all existing monitors": "بر روی تمامی مانیتورهای فعلی اعمال شود",
|
||||
"Create": "ایجاد",
|
||||
"Clear Data": "پاکسازی دادهها",
|
||||
"Events": "رخدادها",
|
||||
"Heartbeats": "Heartbeats",
|
||||
"Heartbeats": "ضربان قلب",
|
||||
"Auto Get": "Auto Get",
|
||||
"backupDescription": "شما میتوانید تمامی مانیتورها و تنظیمات اطلاعرسانیها را در قالب یه فایل JSON دریافت کنید.",
|
||||
"backupDescription2": "البته تاریخچه رخدادها دراین فایل قرار نخواهند داشت.",
|
||||
@@ -152,10 +152,10 @@
|
||||
"Active": "فعال",
|
||||
"Inactive": "غیرفعال",
|
||||
"Token": "توکن",
|
||||
"Show URI": "نمایش آدرس (URI) ",
|
||||
"Show URI": "نمایش آدرس (URI)",
|
||||
"Tags": "برچسبها",
|
||||
"Add New below or Select...": "یک مورد جدید اضافه کنید و یا از لیست انتخاب کنید…",
|
||||
"Tag with this name already exist.": "یک برچسب با این «نام» از قبل وجود دارد",
|
||||
"Tag with this name already exist.": "یک برچسب با این «نام» از قبل وجود دارد.",
|
||||
"Tag with this value already exist.": "یک برچسب با این «مقدار» از قبل وجود دارد.",
|
||||
"color": "رنگ",
|
||||
"value (optional)": "مقدار (اختیاری)",
|
||||
@@ -167,13 +167,13 @@
|
||||
"Indigo": "نیلی",
|
||||
"Purple": "بنفش",
|
||||
"Pink": "صورتی",
|
||||
"Search...": "جستجو...",
|
||||
"Search...": "جستجو …",
|
||||
"Avg. Ping": "متوسط پینگ",
|
||||
"Avg. Response": "متوسط زمان پاسخ",
|
||||
"Entry Page": "صفحه ورودی",
|
||||
"statusPageNothing": "چیزی اینجا نیست، لطفا یک گروه و یا یک مانیتور اضافه کنید!",
|
||||
"statusPageNothing": "چیزی اینجا نیست، لطفا یک گروه و یا یک مانیتور اضافه کنید.",
|
||||
"No Services": "هیچ سرویسی موجود نیست",
|
||||
"All Systems Operational": "تمامی سیستمها عملیاتی هستند!",
|
||||
"All Systems Operational": "تمامی سیستمها فعال هستند",
|
||||
"Partially Degraded Service": "افت نسبی کیفیت سرویس",
|
||||
"Degraded Service": "افت کامل کیفیت سرویس",
|
||||
"Add Group": "اضافه کردن گروه",
|
||||
@@ -187,7 +187,7 @@
|
||||
"One record": "یک مورد",
|
||||
"Info": "اطلاعات",
|
||||
"Powered by": "نیرو گرفته از",
|
||||
"apprise": "Apprise (Support 50+ Notification services)",
|
||||
"apprise": "Apprise (پشتیبانی از 50+ خدمات اعلان)",
|
||||
"Monitor": "مانیتور | مانتیور ها",
|
||||
"Help": "کمک",
|
||||
"Game": "بازی",
|
||||
@@ -197,5 +197,553 @@
|
||||
"statusMaintenance": "در دست تعمیر",
|
||||
"Maintenance": "در حال تعمیر",
|
||||
"General Monitor Type": "حالت مانیتور عمومی",
|
||||
"markdownSupported": "شیوه نگارشی Markdown پشتیبانی می شود"
|
||||
"markdownSupported": "شیوه نگارشی Markdown پشتیبانی می شود",
|
||||
"Body Encoding": "انکودینگ محتوا",
|
||||
"twilioFromNumber": "از شماره",
|
||||
"twilioToNumber": "به شماره",
|
||||
"Resend Notification if Down X times consecutively": "اگر X بار متوالی غیرفعال بود، مجددا اطلاع بده",
|
||||
"successMessageExplanation": "پیام MQTT موفقیت آمیز به نظر نمیرسد",
|
||||
"Create Incident": "یک حادثه را اطلاع دهید",
|
||||
"Switch to Light Theme": "تغییر به حالت روشن",
|
||||
"No monitors available.": "هیچ مانیتوری در دسترس نیست.",
|
||||
"deleteProxyMsg": "آیا مطمئن هستید که میخواهید پروکسی را برای همه مانیتور ها غیرفعال کنید؟",
|
||||
"enableProxyDescription": "این پروکسی تا زمانی که فعال نشود روی درخواست های مانیتور اثری نخواهد داشت. میتوانید با توجه به وضعیت فعالسازی، پروکسی را از همه مانیتورها به طور موقت غیرفعال کنید.",
|
||||
"supportTelegramChatID": "پشتیبانی از چت مستقیم / گروه / کانال",
|
||||
"Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "توکن دسترسی طولانی مدت (Long-Lived Access Token) را می توان با کلیک بر روی نام پروفایل خود (پایین سمت چپ) و اسکرول کردن به پایین و سپس روی Create Token ایجاد کرد. ",
|
||||
"A list of Notification Services can be found in Home Assistant under \"Developer Tools > Services\" search for \"notification\" to find your device/phone name.": "فهرستی از سرویسهای اعلان را میتوانید در هوم اسیستنت در قسمت «ابزارهای برنامهنویس > خدمات» برای «اعلان» جستجو کنید تا نام دستگاه/تلفن خود را پیدا کنید.",
|
||||
"lastDay4": "چهارمین روز آخر ماه",
|
||||
"dnsCacheDescription": "ممکن است در برخی از محیط های IPv6 کار نکند، اگر با مشکلی مواجه شدید آن را غیرفعال کنید.",
|
||||
"Maintenance Time Window of a Day": "صفحه نگه داری در روز",
|
||||
"Messaging API": "API پیام (Messaging API)",
|
||||
"wayToGetLineChannelToken": "ابتدا به {0} دسترسی پیدا کنید، یک ارائه دهنده و کانال ایجاد کنید (API پیام)، سپس می توانید رمز توکن کانال و آیدی کاربری را از آیتم های منوی ذکر شده در بالا دریافت کنید.",
|
||||
"aboutMattermostChannelName": "میتوانید با وارد کردن نام کانال در قسمت «نام کانال»، کانال پیشفرضی را که وب هوک به آن پست میکند لغو کنید. این باید در تنظیمات Mattermost Webhook فعال شود. مثال: #other-channel",
|
||||
"dnsPortDescription": "پورت سرور DNS. پیش فرض ۵۳. می توانید این عدد را در هر زمانی عوض کنید.",
|
||||
"affectedStatusPages": "نمایش این پیام تعمیر و نگه داری در صفحات استاتوس انتخاب شده",
|
||||
"octopushSMSSender": "نام فرستنده پیامک: 3-11 الفبای انگلیسی، حروف و فاصله (a-zA-Z0-9)",
|
||||
"Lowcost": "کم هزینه",
|
||||
"You can divide numbers with": "می توانید اعداد را با آن تقسیم کنید",
|
||||
"goAlertInfo": "GoAlert یک برنامه اوپن سورس برای زمانبندی تماس، افزایش خودکار و اعلانها (مانند پیامک یا تماسهای صوتی) است. به طور خودکار شخص مناسب، راه درست و در زمان مناسب را درگیر کنید! {0}",
|
||||
"API Keys": "کلید های API",
|
||||
"Expiry": "انقضا",
|
||||
"Expiry date": "انقضا در تاریخ",
|
||||
"Don't expire": "بدون انقضا (منقضی نمی شود)",
|
||||
"For safety, must use secret key": "برای امنیت، میببایستی از SecretKey استفاده کنید",
|
||||
"promosmsTypeFlash": "SMS FLASH - پیام به طور خودکار در دستگاه گیرنده نشان داده می شود. فقط به گیرندگان لهستانی محدود می شود.",
|
||||
"promosmsTypeFull": "SMS FULL - پیامک پریموم، می توانید از نام فرستنده خود استفاده کنید (ابتدا باید نام خود را ثبت کنید). قابل اعتماد برای هشدار.",
|
||||
"matrixHomeserverURL": "URL هوم سرور (با http(s):// و پورت اختیاری)",
|
||||
"matrixDesc1": "با مراجعه به بخش پیشرفته تنظیمات اتاق در کلاینت Matrix خود می توانید آیدی داخلی اتاق را بیابید. باید شبیه \"!QMdRCpUIfLwsfjxye6:home.server\" باشد.",
|
||||
"wayToGetPagerDutyKey": "با رفتن به Service -> Service Directory -> (Select a Service) -> Integrations -> Add integration می توانید این مورد را دریافت کنید. در اینجا می توانید \"Events API V2\" را جستجو کنید. اطلاعات بیشتر در {0}",
|
||||
"smseagleRecipientType": "نوع گیرنده",
|
||||
"smseagleEncoding": "ارسال به صورت یونیکد",
|
||||
"Leave blank to use a shared sender number.": "برای استفاده از شماره فرستنده مشترک، آن را خالی بگذارید.",
|
||||
"onebotSafetyTips": "برای امنیت، میبایستی توکن دسترسی اضافه کنید",
|
||||
"Custom Monitor Type": "نوع مانیتور سفارشی",
|
||||
"apiKeyAddedMsg": "کلید API شما اضافه شده است. لطفاً آن را یادداشت کنید زیرا دیگر نمایش داده نخواهد شد.",
|
||||
"deleteAPIKeyMsg": "آیا مطمئن هستید که می خواهید این کلید API را غیرفعال کنید؟",
|
||||
"twilioAccountSID": "SID حساب",
|
||||
"twilioAuthToken": "توکن اعتبارسنجی",
|
||||
"appriseNotInstalled": "Apprise نصب نشده است. {0}",
|
||||
"trustProxyDescription": "به هدرهای «X-Forwarded-*» اعتماد کن. اگر میخواهید IP مشتری صحیح را دریافت کنید و آپتایم کومای شما پشت پروکسی مانند Nginx یا Apache قرار دارد، باید این گزینه را فعال کنید.",
|
||||
"matrixDesc2": "اکیداً توصیه میشود که یک کاربر جدید ایجاد کنید و از رمز دسترسی کاربر Matrix خود استفاده نکنید زیرا امکان دسترسی کامل به حساب شما و تمام اتاقهایی را که به آنها ملحق شدهاید میدهد. در عوض، یک کاربر جدید ایجاد کنید و فقط او را به اتاقی دعوت کنید که میخواهید اعلان را دریافت کنید. میتوانید با اجرای {0} توکن دسترسی را دریافت کنید",
|
||||
"Certificate Chain": "زنجیره گواهی (Certificate Chain)",
|
||||
"telegramMessageThreadID": "(اختیاری) آیدی Thread پیام",
|
||||
"telegramMessageThreadIDDescription": "(اختیاری) شناسه منحصر به فرد برای موضوع پیام هدف در انجمن. فقط برای سوپر گروه های انجمن",
|
||||
"Channel Name": "نام کانال",
|
||||
"auto acknowledged": "تصدیق خودکار",
|
||||
"needPushEvery": "هر {0} ثانیه، URL زیر را صدا بزن.",
|
||||
"pushOptionalParams": "پارامترهای اختیاری: {0}",
|
||||
"Affected Monitors": "مانیتورهای تحت تأثیر",
|
||||
"Pick Affected Monitors...": "انتخاب مانیتورهای تحت تأثیر…",
|
||||
"Start of maintenance": "زمان شروع نگهداری",
|
||||
"All Status Pages": "همه صفحات مشاهده وضعیت",
|
||||
"Select status pages...": "انتخاب صفحه مشاهده وضعیت…",
|
||||
"here": "اینجا",
|
||||
"Required": "اجباری",
|
||||
"Post URL": "URL بعدی",
|
||||
"defaultNotificationName": "هشدار {notification} در ({number})",
|
||||
"Add one": "اضافه کردن",
|
||||
"Page Not Found": "صفحه درخواستی پیدا نشد",
|
||||
"Reverse Proxy": "ریورس پروکسی",
|
||||
"Backup": "پشتیبان گیری",
|
||||
"API Key": "کلید API",
|
||||
"Show update if available": "نمایش بروز رسانی اگر موجود بود",
|
||||
"Check how to config it for WebSocket": "بررسی چگونگی پیکربندی برای وب سوکت",
|
||||
"Steam Game Server": "سرور گیم استیم",
|
||||
"Most likely causes:": "به احتمال زیاد بخاطر:",
|
||||
"The resource is no longer available.": "منبع دیگر در دسترس نیست.",
|
||||
"Docker Container": "کانتینر داکر",
|
||||
"Container Name / ID": "نام / آیدی کانتینر",
|
||||
"Docker Host": "هاست داکر",
|
||||
"Docker Hosts": "هاست های داکر",
|
||||
"Domain": "دامنه",
|
||||
"Clone Monitor": "تکثیر",
|
||||
"Clone": "تکثیر",
|
||||
"cloneOf": "تکثیر {0}",
|
||||
"Prefix Custom Message": "پیشوند پیام سفارشی",
|
||||
"enableGRPCTls": "امکان ارسال درخواست gRPC با اتصال TLS",
|
||||
"pushoversounds classical": "کلاسیک",
|
||||
"smtpDkimSettings": "تنظیمات DKIM",
|
||||
"aboutChannelName": "اگر میخواهید کانال وب هوک را دور بزنید، نام کانال را در قسمت {0} نام کانال وارد کنید. مثال: #other-channel",
|
||||
"aboutKumaURL": "اگر قسمت URL آپتایم کوما را خالی بگذارید، به طور پیشفرض به صفحه پروژه گیت هاب تبدیل میشود.",
|
||||
"smtpDkimDesc": "لطفاً برای استفاده به Nodemailer DKIM {0} مراجعه کنید.",
|
||||
"alertaApiEndpoint": "اند پوینت API",
|
||||
"serwersmsAPIUser": "نام کاربری API (شامل پیشوند webapi_)",
|
||||
"serwersmsAPIPassword": "رمز عبور API",
|
||||
"serwersmsPhoneNumber": "شماره موبایل",
|
||||
"serwersmsSenderName": "نام فرستنده پیامک (ثبت شده از طریق پورتال مشتری)",
|
||||
"alertaRecoverState": "حالت ریکاور (Recover State)",
|
||||
"smseagleToken": "توکن دسترسی API",
|
||||
"Google Analytics ID": "آیدی گوگل آنالیتیکس",
|
||||
"pagertreeLow": "کم",
|
||||
"pagertreeMedium": "متوسط",
|
||||
"pagertreeHigh": "زیاد",
|
||||
"pagertreeCritical": "حساس - خیلی مهم",
|
||||
"pagertreeIntegrationUrl": "URL یکپارچه سازی",
|
||||
"pagertreeUrgency": "اهمیت",
|
||||
"pagertreeSilent": "بی صدا",
|
||||
"pagertreeResolve": "Resolve اتوماتیک",
|
||||
"pagertreeDoNothing": "هیچ کاری نکن",
|
||||
"wayToGetPagerTreeIntegrationURL": "پس از ایجاد ادغام آپتایم کوما در PagerTree، اند پوینت را کپی کنید. مشاهده جزئیات کامل در {0}",
|
||||
"telegramProtectContent": "محافظت از ارسال/ذخیره",
|
||||
"telegramProtectContentDescription": "در صورت فعال بودن، پیامهای ربات در تلگرام از ارسال و ذخیره محافظت میشوند.",
|
||||
"wayToGetTelegramChatID": "برای مشاهده chat_id می توانید شناسه چت خود را با ارسال یک پیام به ربات و رفتن به این URL دریافت کنید:",
|
||||
"YOUR BOT TOKEN HERE": "شناسه ربات خود را اینجا وارد کنید",
|
||||
"chatIDNotFound": "شناسه چت یافت نشد. لطفا ابتدا به ربات پیام دهید",
|
||||
"disableCloudflaredNoAuthMsg": "شما در حالت بدون احراز هویت هستید، رمز عبور در این حالت لازم نیست.",
|
||||
"Trigger type:": "نوع راه اندازی:",
|
||||
"DateTime Range": "محدوده تاریخ",
|
||||
"loadingError": "نمی توان داده ها را دریافت کرد، لطفاً بعداً دوباره امتحان کنید.",
|
||||
"High": "زیاد",
|
||||
"Retry": "تلاش مجدد",
|
||||
"Topic": "موضوع",
|
||||
"Integration Key": "کلید یکپارچه سازی",
|
||||
"Edit Tag": "ویرایش تگ",
|
||||
"Server Address": "آدرس سرور",
|
||||
"Learn More": "بیشتر بدانید",
|
||||
"Customize": "شخصی سازی",
|
||||
"Custom Footer": "فوتر اختصاصی",
|
||||
"No Proxy": "بدون پروکسی",
|
||||
"Authentication": "اعتبارسنجی",
|
||||
"steamApiKeyDescription": "برای مانیتورینگ یک سرور استیم، شما نیاز به یک \"Steam Web-API key\" دارید. برای دریافت کلید میتوانید از اینجا اقدام کنید: ",
|
||||
"No Monitors": "بدون مانیتور",
|
||||
"Untitled Group": "دسته بنده نشده",
|
||||
"Services": "سرویس ها",
|
||||
"Discard": "دست کشیدن",
|
||||
"Cancel": "انصراف",
|
||||
"About": "درباره آپتایم کوما",
|
||||
"wayToGetCloudflaredURL": "(دریافت Cloudflared از {0})",
|
||||
"cloudflareWebsite": "وب سایت کلادفلر",
|
||||
"shrinkDatabaseDescription": "تریگر VACUUM برای SQLite. اگر دیتابیس شما بعد از 1.10.0 ایجاد شده باشد، AUTO_VACUUM قبلاً فعال شده است و لازم نیست این عمل انجام شود. (Trigger database VACUUM for SQLite. If your database is created after 1.10.0, AUTO_VACUUM is already enabled and this action is not needed.).",
|
||||
"Message:": "پیام:",
|
||||
"HTTP Headers": "هدر های HTTP",
|
||||
"Bot Token": "توکن بات",
|
||||
"SecretKey": "کلید محرمانه (SecretKey)",
|
||||
"telegramSendSilently": "ارسال بی صدا",
|
||||
"telegramSendSilentlyDescription": "پیام را بی صدا ارسال کن. در این حالت کاربران یک اعلان بدون صدا دریافت خواهند کرد.",
|
||||
"install": "نصب",
|
||||
"Icon URL": "URL آیکون",
|
||||
"Steam API Key": "کلید API استیم",
|
||||
"Security": "امنیت",
|
||||
"light": "روشن",
|
||||
"Query": "کوئری",
|
||||
"Effective Date Range": "محدوده تاریخ مورد تاثیر (اختیاری)",
|
||||
"statusPageRefreshIn": "بارگذاری مجدد در هر: {0}",
|
||||
"Content Type": "نوع محتوا (Content Type)",
|
||||
"Server URL": "آدرس سرور",
|
||||
"Priority": "اهمیت",
|
||||
"emojiCheatSheet": "چیت شیت ایموجی ها: {0}",
|
||||
"Read more": "بیشتر بدانید",
|
||||
"webhookJsonDesc": "{0} برای هر HTTP سرور جدیدی مانند Express.js مناسب است",
|
||||
"Method": "متد",
|
||||
"Headers": "هدر ها",
|
||||
"PushUrl": "URL پوش",
|
||||
"HeadersInvalidFormat": "هدر ریکوئست یک JSON درست نیست: ",
|
||||
"BodyInvalidFormat": "هدر ریکوئست یک JSON درست نیست: ",
|
||||
"Monitor History": "گزارش مانیتورینگ",
|
||||
"clearDataOlderThan": "گزارشات مانیتورینگ را برای {0} روز نگه دار.",
|
||||
"PasswordsDoNotMatch": "رمز عبور وارد شده درست نیست.",
|
||||
"topic": "موضوع",
|
||||
"topicExplanation": "موضوع MQTT برای مانیتور",
|
||||
"successMessage": "پیام موفقیت آمیز",
|
||||
"recent": "اخیر",
|
||||
"Done": "انجام شده",
|
||||
"Shrink Database": "فشرده سازی دیتابیس",
|
||||
"Pick a RR-Type...": "یک تایپ RR انتخاب کنید…",
|
||||
"Pick Accepted Status Codes...": "یک استاتوس کد قابل قبول انتخاب کنید…",
|
||||
"Default": "پیش فرض",
|
||||
"HTTP Options": "آپشن های HTTP",
|
||||
"Title": "عنوان",
|
||||
"Content": "محتوا",
|
||||
"primary": "اولیه",
|
||||
"dark": "تیره",
|
||||
"Post": "اطلاع بده",
|
||||
"Please input title and content": "لطفا یک عنوان و محتوا وارد کنید",
|
||||
"Created": "ساخته شده در",
|
||||
"Last Updated": "ویرایش شده در",
|
||||
"Unpin": "برداشتن",
|
||||
"Switch to Dark Theme": "تغییر به حالت تیره",
|
||||
"Show Tags": "نمایش تگ ها",
|
||||
"Hide Tags": "مخفی سازی تگ ها",
|
||||
"Description": "توضحیات",
|
||||
"Custom CSS": "CSS اختصاصی",
|
||||
"deleteStatusPageMsg": "آیا بابت حذف این استاتوس پیچ مطمئن هستید؟",
|
||||
"Proxies": "پروکسی ها",
|
||||
"appriseInstalled": "Apprise نصب شده است.",
|
||||
"Body": "متن",
|
||||
"Start": "شروع",
|
||||
"Stop": "توقف",
|
||||
"Add New Status Page": "افزودن صفحه استاتوس جدید",
|
||||
"Slug": "لینک",
|
||||
"Accept characters:": "کاراکتر های مورد تایید:",
|
||||
"startOrEndWithOnly": "شروع یا پایان فقط با {0}",
|
||||
"No consecutive dashes": "بدون خط تیره متوالی",
|
||||
"Next": "بعدی",
|
||||
"The slug is already taken. Please choose another slug.": "این لینک قبلا گرفته شده است. لطفا لینک دیگری را انتخاب کنید.",
|
||||
"New Status Page": "صفحه استاتوس جدید",
|
||||
"Don't know how to get the token? Please read the guide:": "نمی دانید توکن خود را چگونه دریافت کنید؟ لطفا این راهنما را بخوانید:",
|
||||
"The current connection may be lost if you are currently connecting via Cloudflare Tunnel. Are you sure want to stop it? Type your current password to confirm it.": "اگر در حال حاضر از طریق تونل به کلادفلر متصل می شوید، ممکن است اتصال فعلی قطع شود. آیا مطمئن هستید که می خواهید کلادفلر را متوقف کنید؟ رمز عبور خود را برای تایید این دستور تایپ کنید.",
|
||||
"Trust Proxy": "پروکسی مورد اعتماد",
|
||||
"Other Software": "برنامه های دیگر",
|
||||
"For example: nginx, Apache and Traefik.": "برای مثال: Nginx ،Apache و Traefik.",
|
||||
"signedInDispDisabled": "اعتبارسنجی غیرفعال شده است.",
|
||||
"RadiusCallingStationIdDescription": "شناسه دستگاه تماس گیرنده",
|
||||
"Certificate Expiry Notification": "اطلاعیه انقضای گواهی",
|
||||
"RadiusSecret": "کلید Radius",
|
||||
"API Username": "نام کاربری API",
|
||||
"Also check beta release": "همچنین برای نسخه های بتا نیز جستجو کن",
|
||||
"Using a Reverse Proxy?": "استفاده از ریورس پروکسی؟",
|
||||
"There might be a typing error in the address.": "ممکن است یک خطای تایپ در آدرس وجود داشته باشد.",
|
||||
"What you can try:": "آنچه می توانید امتحان کنید:",
|
||||
"Go back to the previous page.": "بازگشت به صفحه قبلی.",
|
||||
"Coming Soon": "به زودی",
|
||||
"Connection String": "رشته اتصال (Connection String)",
|
||||
"settingsCertificateExpiry": "انقضای گواهی TLS",
|
||||
"certificationExpiryDescription": "مانیتور های HTTPS راه اندازی میشود زمانی که گواهی TLS منقضی شود در:",
|
||||
"Retype the address.": "آدرس را دوباره تایپ کنید.",
|
||||
"Setup Docker Host": "راه اندازی هاست داکر",
|
||||
"Connection Type": "نوع اتصال",
|
||||
"Docker Daemon": "Daemon داکر",
|
||||
"deleteDockerHostMsg": "آیا مطمئن هستید که می خواهید این هاست داکر را برای همه مانیتورها حذف کنید؟",
|
||||
"Workstation": "محل کار (Workstation)",
|
||||
"Packet Size": "سایز پکت",
|
||||
"wayToGetTelegramToken": "شما میتوانید توکن خود را از {0} دریافت کنید.",
|
||||
"Chat ID": "آیدی چت",
|
||||
"wayToGetLineNotifyToken": "میتوانید یک توکن جهت دسترسی از {0} دریافت کنید",
|
||||
"Examples": "مثال ها",
|
||||
"Home Assistant URL": "URL هوم اسیستنت شما",
|
||||
"Long-Lived Access Token": "توکن دسترسی طولانی مدت",
|
||||
"Notification Service": "سرویس اطلاع رسانی",
|
||||
"default: notify all devices": "پیش فرض: اطلاع به همه دستگاه ها",
|
||||
"Automations can optionally be triggered in Home Assistant:": "اتوماسیون ها می توانند به صورت اختیاری در هوم اسیستنت فعال شوند:",
|
||||
"Event type:": "نوع ایونت:",
|
||||
"Event data:": "نوع دیتا:",
|
||||
"Then choose an action, for example switch the scene to where an RGB light is red.": "سپس یک عمل را انتخاب کنید، برای مثال صحنه را به جایی که نور RGB قرمز است تغییر دهید.",
|
||||
"Optional": "اختیاری",
|
||||
"recurringInterval": "وقفه",
|
||||
"Recurring": "مکرر",
|
||||
"strategyManual": "فعال/غیرفعال سازی به صورت دستی",
|
||||
"warningTimezone": "این از منطقه زمانی سرور استفاده می کند",
|
||||
"weekdayShortMon": "دوشنبه",
|
||||
"weekdayShortTue": "سه شنبه",
|
||||
"weekdayShortWed": "چهارشنبه",
|
||||
"weekdayShortThu": "پنجشنبه",
|
||||
"weekdayShortFri": "جمعه",
|
||||
"weekdayShortSat": "شنبه",
|
||||
"weekdayShortSun": "یکشنبه",
|
||||
"dayOfWeek": "روز های هفته",
|
||||
"dayOfMonth": "روز های ماه",
|
||||
"lastDay": "روز آخر",
|
||||
"lastDay1": "روز آخر ماه",
|
||||
"lastDay2": "دومین روز آخر ماه",
|
||||
"lastDay3": "سومین روز آخر ماه",
|
||||
"Enable": "فعال سازی",
|
||||
"Single Maintenance Window": "تعمیر و نگه داری تک صفحه",
|
||||
"Schedule Maintenance": "زمانبندی تعمیر و نگهداری",
|
||||
"Date and Time": "زمان و تاریخ",
|
||||
"plugin": "پلاگین | پلاگین ها",
|
||||
"installing": "در حال نصب",
|
||||
"uninstall": "حذف از نصب",
|
||||
"uninstalling": "درحال حذف",
|
||||
"confirmUninstallPlugin": "آیا مطمئن هستید که می خواهید این پلاگین را حذف از نصب کنید؟",
|
||||
"notificationRegional": "منطقه ای",
|
||||
"secureOptionNone": "None / STARTTLS (25, 587)",
|
||||
"secureOptionTLS": "TLS (465)",
|
||||
"Ignore TLS Error": "خطای TLS را نادیده بگیر",
|
||||
"From Email": "از ایمیل",
|
||||
"emailCustomSubject": "موضوع سفارشی",
|
||||
"To Email": "به ایمیل",
|
||||
"smtpBCC": "BCC",
|
||||
"Discord Webhook URL": "URL وب هوک دیسکورد",
|
||||
"Bot Display Name": "نام نمایشی ربات",
|
||||
"Hello @everyone is...": "سلام {'@'} همه…",
|
||||
"wayToGetTeamsURL": "میتوانید نحوه ایجاد وب هوک را در {0} بیاموزید.",
|
||||
"wayToGetZohoCliqURL": "میتوانید نحوه ایجاد وب هوک را در {0} بیاموزید.",
|
||||
"needSignalAPI": "شما باید یک Signal Client با REST API داشته باشید.",
|
||||
"wayToCheckSignalURL": "برای مشاهده نحوه تنظیم آن می توانید این URL را بررسی کنید:",
|
||||
"Number": "عدد",
|
||||
"Recipients": "گیرندگان",
|
||||
"Channel access token": "توکن دسترسی به کانال",
|
||||
"Line Developers Console": "کنسول توسعه دهندگان لاین (Line Developers Console)",
|
||||
"lineDevConsoleTo": "کنسول توسعه دهندگان لاین (Line Developers Console) - {0}",
|
||||
"Basic Settings": "تنظیمات پایه",
|
||||
"User ID": "آیدی کاربر",
|
||||
"aboutIconURL": "میتوانید پیوندی به یک عکس در \"URL آیکون \" ارائه دهید تا عکس نمایه پیشفرض را لغو کنید. اگر نماد Emoji تنظیم شده باشد، این مورد استفاده نخواهد شد.",
|
||||
"dataRetentionTimeError": "دوره نگهداری باید 0 یا بیشتر باشد",
|
||||
"wayToGetDiscordURL": "شما می توانید این را با رفتن به تنظیمات سرور -> ادغام -> مشاهده وب هوک -> وب هوک جدید (Settings -> Integrations -> View Webhooks -> New Webhook) دریافت کنید",
|
||||
"infiniteRetention": "برای دوره بی نهایت 0 را وارد تنظیم کنید.",
|
||||
"confirmDeleteTagMsg": "آیا مطمئن هستید که می خواهید این تگ را حذف کنید؟ مانیتورهای مرتبط با این تگ حذف نخواهند شد.",
|
||||
"grpcMethodDescription": "نام روش تبدیل به فرمت cammelCase مانند sayHello، check و غیره.",
|
||||
"deleteMaintenanceMsg": "آیا مطمئن هستید که می خواهید این تعمیر و نگهداری را حذف کنید؟",
|
||||
"recurringIntervalMessage": "یکبار اجرا برای هر روز | یکبار اجرا در هر {0} روز",
|
||||
"affectedMonitorsDescription": "مانیتورهایی را انتخاب کنید که تحت تأثیر تعمیر و نگهداری فعلی هستند",
|
||||
"atLeastOneMonitor": "حداقل یک مانیتور مورد تاثیر را انتخاب کنید",
|
||||
"octopushAPIKey": "\"کلید API\" از اعتبارنامه های HTTP API در کنترل پنل",
|
||||
"octopushLogin": "\"ورود\" از اعتبار HTTP API در کنترل پنل",
|
||||
"promosmsLogin": "نام ورود API",
|
||||
"pushoversounds cashregister": "صندوق فروش",
|
||||
"pushoversounds falling": "رها کردن",
|
||||
"pushoversounds incoming": "ورودی",
|
||||
"pushoversounds intermission": "وقفه",
|
||||
"pushoversounds magic": "سحر آمیز",
|
||||
"pushoversounds mechanical": "مکانیکی",
|
||||
"pushoversounds pianobar": "پیانو بار",
|
||||
"pushoversounds siren": "آژیر",
|
||||
"pushoversounds spacealarm": "هشدار فضایی",
|
||||
"pushoversounds gamelan": "گیم لن (Gamelan)",
|
||||
"Current User": "کاربر فعلی",
|
||||
"pushoversounds none": "بی صدا",
|
||||
"pushoversounds tugboat": "قایق یدک کش",
|
||||
"pushoversounds alien": "هشدار بیگانه (طولانی)",
|
||||
"pushoversounds climb": "صعود (طولانی)",
|
||||
"pushoversounds persistent": "پایدار (طولانی)",
|
||||
"pushoversounds echo": "اکو (طولانی)",
|
||||
"pushoversounds updown": "بالا پایین (طولانی)",
|
||||
"pushoversounds vibrate": "فقط ویبره",
|
||||
"pushyToken": "توکن دستگاه",
|
||||
"GoogleChat": "Google Chat (فقط Google Workspace)",
|
||||
"wayToGetKookBotToken": "یک برنامه ایجاد کنید و توکن ربات خود را از {0} دریافت کنید",
|
||||
"User Key": "کلید کاربر",
|
||||
"Message Title": "عنوان پیام",
|
||||
"Notification Sound": "صدای اعلان",
|
||||
"More info on:": "اطلاعات بیشتر در مورد: {0}",
|
||||
"pushoverDesc1": "اولویت اضطراری (2) دارای وقفه پیشفرض 30 ثانیه بین تلاشهای مجدد است و پس از 1 ساعت منقضی میشود.",
|
||||
"pushoverDesc2": "اگر میخواهید اعلانها را به دستگاههای مختلف ارسال کنید، قسمت دستگاه را پر کنید.",
|
||||
"pushyAPIKey": "کلید Secret API",
|
||||
"wayToGetKookGuildID": "«حالت توسعهدهنده» را در تنظیمات کوک روشن کنید و روی انجمن کلیک راست کنید تا شناسه آن را دریافت کنید",
|
||||
"Guild ID": "گیلد آیدی (Guild ID)",
|
||||
"SMS Type": "نوع پیامک",
|
||||
"octopushTypePremium": "پرمیوم (سریع - پیشنهاد شده برای هشدار ها)",
|
||||
"octopushTypeLowCost": "کم هزینه (آهسته - گاهی اوقات توسط اپراتور مسدود می شود)",
|
||||
"checkPrice": "بررسی قیمتهای {0} :",
|
||||
"apiCredentials": "اطلاعات API",
|
||||
"octopushLegacyHint": "آیا از نسخه قدیمی Octopush (1387-1400) استفاده می کنید یا از نسخه جدید؟",
|
||||
"octopushPhoneNumber": "شماره تلفن (حالت بین المللی مانند 989121234567+) ",
|
||||
"LunaSea Device ID": "شناسه دستگاه LunaSea",
|
||||
"Apprise URL": "آدرس Apprise",
|
||||
"Example:": "مثال: {0}",
|
||||
"Read more:": "بیشتر بخوانید: {0}",
|
||||
"Free Mobile User Identifier": "شناسه کاربری Free Mobile",
|
||||
"Free Mobile API Key": "کلید API در Free Mobile",
|
||||
"Enable TLS": "فعال کردن TLS",
|
||||
"Proto Service Name": "نام Proto Service",
|
||||
"Proto Method": "متد Proto",
|
||||
"Proto Content": "محتوای Proto",
|
||||
"Economy": "اقتصاد",
|
||||
"high": "زیاد",
|
||||
"SMSManager API Docs": "مستندات SMSManager API ",
|
||||
"Gateway Type": "نوع Gateway",
|
||||
"Base URL": "URL پایه",
|
||||
"goAlertIntegrationKeyInfo": "کلید ادغام API عمومی را برای سرویس در این قالب دریافت کنید \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeee\" معمولاً مقدار پارامتر توکن URL کپی شده است.",
|
||||
"AccessKeyId": "آیدی AccessKey",
|
||||
"PhoneNumbers": "شماره های موبایل",
|
||||
"TemplateCode": "کد تمپلیت",
|
||||
"Sms template must contain parameters: ": "قالب پیامک باید دارای پارامترهای زیر باشد: ",
|
||||
"Bark Endpoint": "اند پوینت Bark",
|
||||
"Bark Group": "گروه Bark",
|
||||
"Bark Sound": "صدای Bark",
|
||||
"WebHookUrl": "آدرس وب هوک",
|
||||
"Device Token": "توکن دستگاه",
|
||||
"Platform": "پلتفرم",
|
||||
"Check octopush prices": "بررسی قیمت های octopush {0}.",
|
||||
"SendKey": "کلید ارسال (SendKey)",
|
||||
"SecretAccessKey": "کلید دسترسی مخفی (AccessKey Secret)",
|
||||
"SignName": "نام امضا (SignName)",
|
||||
"Android": "اندروید",
|
||||
"Huawei": "هواوی",
|
||||
"WeCom Bot Key": "کلید ربات WeCom",
|
||||
"Setup Proxy": "تنظیم پروکسی",
|
||||
"Proxy Protocol": "پروتکل پروکسی",
|
||||
"Proxy Server": "پروتکل سرور",
|
||||
"promosmsTypeEco": "SMS ECO - ارزان اما کند و اغلب بارگذاری شده است. فقط به گیرندگان لهستانی محدود می شود.",
|
||||
"promosmsTypeSpeed": "SPEED SMS - بالاترین اولویت در سیستم. بسیار سریع و قابل اعتماد اما پرهزینه (حدود دو برابر قیمت SMS FULL).",
|
||||
"promosmsPhoneNumber": "شماره تلفن (برای گیرنده لهستانی می توانید کدهای منطقه را نادیده بگیرید)",
|
||||
"promosmsSMSSender": "نام فرستنده پیامک: نام از پیش ثبت شده یا یکی از پیش فرض ها: InfoSMS، SMS Info، MaxSMS، INFO، SMS",
|
||||
"promosmsAllowLongSMS": "اجازه برای پیامک طولانی",
|
||||
"Feishu WebHookUrl": "آدرس وب هوک Feishu",
|
||||
"Internal Room Id": "آیدی اتاق داخلی",
|
||||
"Uptime Kuma URL": "URL آپتایم کوما",
|
||||
"signalImportant": "مهم: شما نمی توانید گروه ها و اعداد را در گیرندگان ترکیب کنید!",
|
||||
"aboutWebhooks": "اطلاعات بیشتر درباره وب هوک در: {0}",
|
||||
"documentation": "مستندات",
|
||||
"smtpDkimDomain": "نام دامنه",
|
||||
"smtpDkimHashAlgo": "الگوریتم رمزگذاری (اختیاری)",
|
||||
"smtpDkimheaderFieldNames": "کلیدهای هدر برای امضا (اختیاری)",
|
||||
"smtpDkimskipFields": "کلیدهای هدر برای عدم امضا (اختیاری)",
|
||||
"Integration URL": "URL یکپارچه سازی",
|
||||
"smtpDkimKeySelector": "انتخابگر کلید (SecretKey)",
|
||||
"smtpDkimPrivateKey": "کلید محرمانه (Private Key)",
|
||||
"socket": "سوکت",
|
||||
"do nothing": "هیچ کاری نکن",
|
||||
"auto resolve": "حل خودکار",
|
||||
"alertaEnvironment": "محیط",
|
||||
"alertaApiKey": "کلید API",
|
||||
"alertaAlertState": "وضعیت هشدار",
|
||||
"smseagleTo": "شماره تلفن(ها)",
|
||||
"smseagleGroup": "نام(های) گروه دفترچه تلفن",
|
||||
"smseagleContact": "نام(های) تماس دفترچه تلفن",
|
||||
"smseagleRecipient": "گیرنده(های) (چند مورد باید با کاما از هم جدا شوند)",
|
||||
"smseagleUrl": "URL دستگاه SMSEagle شما",
|
||||
"smseaglePriority": "اولویت پیام (0-9، پیش فرض = 0)",
|
||||
"Recipient Number": "شماره گیرنده",
|
||||
"From Name/Number": "از نام/شماره",
|
||||
"Octopush API Version": "نسخه Octopush API",
|
||||
"ntfy Topic": "موضوع ntfy",
|
||||
"onebotHttpAddress": "آدرس HTTP OneBot",
|
||||
"onebotMessageType": "نوع پیام OneBot",
|
||||
"onebotGroupMessage": "گروه",
|
||||
"onebotPrivateMessage": "خصوصی",
|
||||
"onebotUserOrGroupId": "آیدی گروه/کاربر",
|
||||
"PushDeer Key": "کلید PushDeer",
|
||||
"wayToGetClickSendSMSToken": "میتوانید نام کاربری و کلید API را از {0} دریافت کنید.",
|
||||
"Continue": "ادامه",
|
||||
"Add Another": "افزودن یکی دیگر",
|
||||
"Key Added": "کلید API اضافه شد",
|
||||
"Add API Key": "افزودن کلید API",
|
||||
"No API Keys": "بدون کلید API",
|
||||
"apiKey-active": "فعال",
|
||||
"apiKey-expired": "منقضی شده",
|
||||
"apiKey-inactive": "غیرفعال",
|
||||
"Expires": "انقضا",
|
||||
"disableAPIKeyMsg": "آیا مطمئن هستید که می خواهید این کلید API را غیرفعال کنید؟",
|
||||
"Generate": "ایجاد یک کلید API جدید",
|
||||
"lunaseaTarget": "هدف",
|
||||
"lunaseaDeviceID": "آيدی دستگاه",
|
||||
"lunaseaUserID": "آیدی کاربر",
|
||||
"Auto resolve or acknowledged": "حل خودکار یا اعلام اطلاع یافته (Auto resolve or acknowledged)",
|
||||
"Legacy Octopush-DM": "(Legacy Octopush-DM)",
|
||||
"smtpCC": "ارسال نسخه به",
|
||||
"promosmsPassword": "رمز عبور API",
|
||||
"pushoversounds pushover": "Pushover (پیشفرض)",
|
||||
"pushoversounds bike": "دوچرخه",
|
||||
"pushoversounds bugle": "بوق",
|
||||
"pushoversounds cosmic": "کیهانی",
|
||||
"resendEveryXTimes": "پیام را هر {0} بار دوباره ارسال کن",
|
||||
"resendDisabled": "ارسال مجدد غیرفعال است",
|
||||
"Push URL": "URL پوش",
|
||||
"Schedule maintenance": "زمانبندی نگهداری (غیرفعال سازی دستی)",
|
||||
"webhookFormDataDesc": "{multipart} برای PHP مناسب است. آرایه JSON نیاز است تا به این شکل باز شود {decodeFunction}",
|
||||
"webhookAdditionalHeadersTitle": "هدر اضافی",
|
||||
"webhookAdditionalHeadersDesc": "تنظیم هدر های اضافی که نیاز است با وب هوک ارسال شود.",
|
||||
"Webhook URL": "آدرس وب هوک",
|
||||
"Application Token": "توکن اپلیکیشن",
|
||||
"Style": "حالت ها",
|
||||
"info": "اطلاعات",
|
||||
"warning": "هشدار",
|
||||
"danger": "خطر",
|
||||
"error": "خطا",
|
||||
"critical": "اهمیت ویژه",
|
||||
"HTTP Basic Auth": "حالت پایه احراز هویت (HTTP Basic Auth)",
|
||||
"RadiusSecretDescription": "اشتراک گذاری Secret بین کاربر و سرور",
|
||||
"RadiusCalledStationId": "نام Station Id",
|
||||
"RadiusCalledStationIdDescription": "شناسه دستگاه فراخوانی شده",
|
||||
"RadiusCallingStationId": "آیدی ایستگاه تماس (Calling Station Id)",
|
||||
"tcp": "TCP / HTTP",
|
||||
"Frontend Version": "نسخه فرانت اند",
|
||||
"Frontend Version do not match backend version!": "نسخه فرانت اند با نسخه بک اند مطابقت ندارد!",
|
||||
"backupOutdatedWarning": "منسوخ شده: از آنجایی که بسیاری از ویژگی ها اضافه شده اند و این ویژگی پشتیبان گیری کمی حفظ نشده است، نمی تواند یک نسخه پشتیبان کامل ایجاد یا بازیابی شود.",
|
||||
"backupRecommend": "لطفاً مستقیماً از Volume یا پوشه داده (./data/) نسخه پشتیبان تهیه کنید.",
|
||||
"No Maintenance": "بدون تعمیر و نگهداری",
|
||||
"pauseMaintenanceMsg": "آیا مطمئن هستید که می خواهید توقف کنید؟",
|
||||
"maintenanceStatus-under-maintenance": "تحت تعمیر و نگهداری",
|
||||
"maintenanceStatus-inactive": "غیرفعال",
|
||||
"maintenanceStatus-scheduled": "برنامه ریزی شده",
|
||||
"maintenanceStatus-ended": "پایان یافته",
|
||||
"maintenanceStatus-unknown": "ناشناخته",
|
||||
"Display Timezone": "منطقه زمانی برای نمایش",
|
||||
"Server Timezone": "منطقه زمانی در سرور",
|
||||
"statusPageMaintenanceEndDate": "پایان",
|
||||
"IconUrl": "URL آیکون",
|
||||
"Enable DNS Cache": "فعال سازی کش DNS",
|
||||
"Access Token": "توکن دسترسی",
|
||||
"smtp": "ایمیل (SMTP)",
|
||||
"Device": "دستگاه",
|
||||
"Proxy server has authentication": "پروکسی سرور دارای اعتبارسنجی است",
|
||||
"Add New Tag": "اضافه کردن تگ جدید",
|
||||
"Custom": "غیره",
|
||||
"default": "پیش فرض",
|
||||
"enabled": "فعال",
|
||||
"setAsDefault": "ذخیره به عنوان پیش فرض",
|
||||
"proxyDescription": "پروکسی برای راه اندازی این مانیتور اجباری است.",
|
||||
"setAsDefaultProxyDescription": "این پروکسی به طور پیش فرض برای مانیتورهای جدید فعال می شود. همچنان می توانید پروکسی را به طور جداگانه برای هر مانیتور غیرفعال کنید.",
|
||||
"Valid": "درست",
|
||||
"Invalid": "نادرست",
|
||||
"User": "کاربر",
|
||||
"Installed": "نصب شده",
|
||||
"Not installed": "نصب نشده",
|
||||
"Running": "در حال اجرا",
|
||||
"Not running": "اجرا نشده",
|
||||
"Remove Token": "حذف توکن",
|
||||
"Please read": "لطفا بخوانید",
|
||||
"Subject:": "موضوع:",
|
||||
"Valid To:": "معتبر تا:",
|
||||
"Days Remaining:": "روز های باقی مانده:",
|
||||
"Fingerprint:": "اثرانگشت (Fingerprint):",
|
||||
"No status pages": "بدون صفحات استاتوس",
|
||||
"Domain Name Expiry Notification": "اعلان انقضای نام دامنه",
|
||||
"Issuer:": "صادرکننده:",
|
||||
"Date Created": "ایجاد شده در",
|
||||
"Footer Text": "متن فوتر",
|
||||
"Show Powered By": "نمایش قدرت گرفته از آپتایم کوما",
|
||||
"Domain Names": "نام دامنه ها",
|
||||
"Proxy": "پروکسی",
|
||||
"signedInDisp": "وارد شده به عنوان {0}",
|
||||
"or": "یا",
|
||||
"Disable": "غیرفعال سازی",
|
||||
"endpoint": "نقطه پایانی",
|
||||
"Status:": "وضعیت: {0}",
|
||||
"Strategy": "استراتژی",
|
||||
"Icon Emoji": "ایموجی آیکون",
|
||||
"sameAsServerTimezone": "مشابه با منطقه زمانی سرور",
|
||||
"startDateTime": "ساعت/روز شروع",
|
||||
"endDateTime": "ساعت/روز پایان",
|
||||
"cronSchedule": "برنامه زمانی: ",
|
||||
"invalidCronExpression": "حالت کرون نامعتبر است: {0}",
|
||||
"cronExpression": "حالت کرون",
|
||||
"ntfyAuthenticationMethod": "روش اعتبارسنجی",
|
||||
"ntfyUsernameAndPassword": "نام کاربری و رمز عبور",
|
||||
"pushoverMessageTtl": "TTL پیام (ثانیه)",
|
||||
"Show Clickable Link": "نمایش لینک های قابل کلیک",
|
||||
"Open Badge Generator": "باز کردن نشان ساز (Badge Generator)",
|
||||
"Badge Generator": "نشان ساز (Badge Generator) {0}",
|
||||
"Badge Type": "نوع نشان",
|
||||
"Badge Duration": "مدت نشان",
|
||||
"Badge Label": "برچسب نشان",
|
||||
"Badge Prefix": "پیشوند نشان",
|
||||
"Badge Suffix": "پسوند نشان",
|
||||
"Badge Label Color": "رنگ برچسب نشان",
|
||||
"Badge Color": "رنگ نشان",
|
||||
"Badge Label Prefix": "پیشوند برچسب نشان",
|
||||
"Badge Label Suffix": "پسوند برچسب نشان",
|
||||
"Badge Down Color": "رنگ نشان زمانی که مانیتور دچار قطعی و Down شده است",
|
||||
"Badge Maintenance Color": "رنگ نشان برای زمانی که مانیتور در حالت نگهداری است",
|
||||
"Badge Warn Color": "رنگ نشان زمانی که مانیتور در حالت هشدار است",
|
||||
"Badge Down Days": "روز هایی که مانیتور دچار قطعی شده است",
|
||||
"Badge Style": "حالت نشان",
|
||||
"Badge value (For Testing only.)": "مقدار نشان (فقط برای تست.)",
|
||||
"Badge URL": "آدرس نشان",
|
||||
"Monitor Setting": "تنظیمات مانتیور {0}",
|
||||
"Show Clickable Link Description": "اگر انتخاب شود، همه کسانی که به این صفحه وضعیت دسترسی دارند میتوانند به صفحه مانیتور نیز دسترسی داشته باشند.",
|
||||
"Badge Up Color": "رنگ نشان زمانی که مانیتور بدون مشکل و بالا است",
|
||||
"Badge Pending Color": "رنگ نشان زمانی که مانیتور در حال انتظار است",
|
||||
"Badge Warn Days": "روزهایی که مانیتور در حالت هشدار است"
|
||||
}
|
||||
|
@@ -59,7 +59,7 @@
|
||||
"Add New Monitor": "Ajouter une nouvelle sonde",
|
||||
"Quick Stats": "Résumé",
|
||||
"Up": "En ligne",
|
||||
"Down": "Hors ligne",
|
||||
"Down": "Bas",
|
||||
"Pending": "En attente",
|
||||
"Unknown": "Inconnu",
|
||||
"Pause": "En pause",
|
||||
@@ -73,7 +73,7 @@
|
||||
"Delete": "Supprimer",
|
||||
"Current": "Actuellement",
|
||||
"Uptime": "Disponibilité",
|
||||
"Cert Exp.": "Expiration SSL",
|
||||
"Cert Exp.": "Expiration Cert SSL",
|
||||
"day": "jour | jours",
|
||||
"-day": "-jour",
|
||||
"hour": "heure",
|
||||
@@ -329,7 +329,7 @@
|
||||
"Body": "Corps",
|
||||
"Headers": "En-têtes",
|
||||
"PushUrl": "URL Push",
|
||||
"HeadersInvalidFormat": "Les en-têtes de la requête ne sont pas dans un format JSON valide : ",
|
||||
"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": "Conserver l'historique des données de la sonde durant {0} jours.",
|
||||
@@ -338,7 +338,7 @@
|
||||
"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 : ",
|
||||
"Current User": "Utilisateur actuel",
|
||||
"topic": "Topic",
|
||||
"topic": "Sujet",
|
||||
"topicExplanation": "Topic MQTT à surveiller",
|
||||
"successMessage": "Message de réussite",
|
||||
"successMessageExplanation": "Message MQTT qui sera considéré comme un succès",
|
||||
@@ -658,7 +658,7 @@
|
||||
"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",
|
||||
"Effective Date Range": "Plage de dates d'effet (facultatif)",
|
||||
"Schedule Maintenance": "Créer une maintenance",
|
||||
"Date and Time": "Date et heure",
|
||||
"DateTime Range": "Plage de dates et d'heures",
|
||||
@@ -699,7 +699,7 @@
|
||||
"Edit Tag": "Modifier l'étiquette",
|
||||
"Body Encoding": "Encodage du corps",
|
||||
"telegramMessageThreadID": "(Facultatif) ID du fil de message",
|
||||
"telegramMessageThreadIDDescription": "(Facultatif) Identifiant unique pour le fil de discussion cible (sujet) du forum; pour les supergroupes du forum uniquement",
|
||||
"telegramMessageThreadIDDescription": "(Facultatif) Identifiant unique pour le fil de discussion ciblé (sujet) du forum; pour les supergroupes du forum uniquement",
|
||||
"telegramProtectContent": "Protéger le transfert/l'enregistrement",
|
||||
"telegramProtectContentDescription": "S'il est activé, les messages du robot dans Telegram seront protégés contre le transfert et l'enregistrement.",
|
||||
"telegramSendSilently": "Envoyer silencieusement",
|
||||
@@ -738,5 +738,42 @@
|
||||
"lunaseaDeviceID": "Identifiant de l'appareil",
|
||||
"lunaseaUserID": "Identifiant de l'utilisateur",
|
||||
"Add New Tag": "Ajouter une étiquette",
|
||||
"lunaseaTarget": "Cible"
|
||||
"lunaseaTarget": "Cible",
|
||||
"statusPageRefreshIn": "Actualisation dans : {0}",
|
||||
"twilioFromNumber": "Du Nombre",
|
||||
"twilioToNumber": "Au Nombre",
|
||||
"twilioAccountSID": "ID du compte",
|
||||
"twilioAuthToken": "Jeton d'authentification",
|
||||
"sameAsServerTimezone": "Identique au fuseau horaire du serveur",
|
||||
"startDateTime": "Date/heure de début",
|
||||
"endDateTime": "Date/heure de fin",
|
||||
"cronExpression": "Expression cron",
|
||||
"cronSchedule": "Calendrier : ",
|
||||
"invalidCronExpression": "Expression Cron non valide : {0}",
|
||||
"ntfyUsernameAndPassword": "Nom d'utilisateur et mot de passe",
|
||||
"ntfyAuthenticationMethod": "Méthode d'authentification",
|
||||
"pushoverMessageTtl": "TTL Message (Secondes)",
|
||||
"Show Clickable Link": "Afficher le lien cliquable",
|
||||
"Show Clickable Link Description": "Si cette case est cochée, tous ceux qui ont accès à cette page d'état peuvent accéder à l'URL du moniteur.",
|
||||
"Open Badge Generator": "Ouvrir le générateur de badges",
|
||||
"Badge Type": "Type de badge",
|
||||
"Badge Duration": "Durée du badge",
|
||||
"Badge Prefix": "Préfixe de badge",
|
||||
"Badge Suffix": "Suffixe de badge",
|
||||
"Badge Label Color": "Couleur de l'étiquette du badge",
|
||||
"Badge Color": "Couleur du badge",
|
||||
"Badge Label Prefix": "Préfixe d'étiquette de badge",
|
||||
"Badge Label Suffix": "Suffixe d'étiquette de badge",
|
||||
"Badge Up Color": "Couleur du badge en ligne",
|
||||
"Badge Down Color": "Couleur du badge hors ligne",
|
||||
"Badge Pending Color": "Couleur du badge en attente",
|
||||
"Badge Maintenance Color": "Couleur du badge maintenance",
|
||||
"Badge Warn Color": "Couleur du badge d'avertissement",
|
||||
"Badge Warn Days": "Jours d'avertissement de badge",
|
||||
"Badge Style": "Style de badge",
|
||||
"Badge value (For Testing only.)": "Valeur du badge (Pour les tests uniquement.)",
|
||||
"Monitor Setting": "Réglage de la sonde {0}",
|
||||
"Badge Generator": "Générateur de badges {0}",
|
||||
"Badge Label": "Étiquette de badge",
|
||||
"Badge URL": "URL du badge"
|
||||
}
|
||||
|
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"languageName": "日本語",
|
||||
"checkEverySecond": "{0}秒ごとにチェックします",
|
||||
"retriesDescription": "サービスがダウンとしてマークされ、通知が送信されるまでの最大リトライ数",
|
||||
"retriesDescription": "サービスが完全に停止したと判断し、通知を送信する前に再接続を試みる最大回数",
|
||||
"ignoreTLSError": "HTTPS ウェブサイトの TLS/SSL エラーを無視する",
|
||||
"upsideDownModeDescription": "ステータスの扱いを逆にします。サービスに到達可能な場合は、DOWNとなる。",
|
||||
"maxRedirectDescription": "フォローするリダイレクトの最大数。リダイレクトを無効にするには0を設定する。",
|
||||
"upsideDownModeDescription": "稼働ステータスを反転して扱います。サービスに接続可能な場合は、停止として扱います。",
|
||||
"maxRedirectDescription": "必要な場合にリダイレクトする最大回数です。リダイレクトを無効にしたい場合は、0に設定してください。",
|
||||
"acceptedStatusCodesDescription": "成功した応答とみなされるステータスコードを選択する。",
|
||||
"passwordNotMatchMsg": "繰り返しのパスワードが一致しません。",
|
||||
"notificationDescription": "監視を機能させるには、監視に通知を割り当ててください。",
|
||||
@@ -21,15 +21,15 @@
|
||||
"Language": "言語",
|
||||
"Appearance": "外観",
|
||||
"Theme": "テーマ",
|
||||
"General": "全般的",
|
||||
"General": "全般",
|
||||
"Version": "バージョン",
|
||||
"Check Update On GitHub": "GitHubでアップデートを確認する",
|
||||
"List": "一覧",
|
||||
"Add": "追加",
|
||||
"Add New Monitor": "監視の追加",
|
||||
"Quick Stats": "統計",
|
||||
"Up": "Up",
|
||||
"Down": "Down",
|
||||
"Up": "正常",
|
||||
"Down": "停止",
|
||||
"Pending": "中止",
|
||||
"Unknown": "不明",
|
||||
"Pause": "一時停止",
|
||||
@@ -42,12 +42,12 @@
|
||||
"Edit": "編集",
|
||||
"Delete": "削除",
|
||||
"Current": "現在",
|
||||
"Uptime": "起動時間",
|
||||
"Uptime": "稼働時間",
|
||||
"Cert Exp.": "証明書有効期限",
|
||||
"day": "日 | 日間",
|
||||
"-day": "-日",
|
||||
"hour": "時間",
|
||||
"-hour": "-時間",
|
||||
"-hour": "時間",
|
||||
"Response": "レスポンス",
|
||||
"Ping": "Ping",
|
||||
"Monitor Type": "監視タイプ",
|
||||
@@ -57,19 +57,19 @@
|
||||
"Hostname": "ホスト名",
|
||||
"Port": "ポート",
|
||||
"Heartbeat Interval": "監視間隔",
|
||||
"Retries": "Retries",
|
||||
"Advanced": "Advanced",
|
||||
"Upside Down Mode": "Upside Down Mode",
|
||||
"Retries": "再試行回数",
|
||||
"Advanced": "詳細設定",
|
||||
"Upside Down Mode": "反転モード",
|
||||
"Max. Redirects": "最大リダイレクト数",
|
||||
"Accepted Status Codes": "正常なステータスコード",
|
||||
"Save": "保存",
|
||||
"Notifications": "通知",
|
||||
"Not available, please setup.": "利用できません。設定してください。",
|
||||
"Not available, please setup.": "利用できません。設定が必要です。",
|
||||
"Setup Notification": "通知設定",
|
||||
"Light": "Light",
|
||||
"Dark": "Dark",
|
||||
"Auto": "Auto",
|
||||
"Theme - Heartbeat Bar": "Theme - Heartbeat Bar",
|
||||
"Light": "ライト",
|
||||
"Dark": "ダーク",
|
||||
"Auto": "自動",
|
||||
"Theme - Heartbeat Bar": "テーマ - 監視バー",
|
||||
"Normal": "通常",
|
||||
"Bottom": "下部",
|
||||
"None": "なし",
|
||||
@@ -120,7 +120,7 @@
|
||||
"Also apply to existing monitors": "既存のモニターにも適用する",
|
||||
"Export": "エクスポート",
|
||||
"Import": "インポート",
|
||||
"backupDescription": "すべての監視と通知方法をJSONファイルにできます。",
|
||||
"backupDescription": "すべての監視と通知設定をJSONファイルとしてバックアップすることができます。",
|
||||
"backupDescription2": "※ 履歴と統計のデータはバックアップされません。",
|
||||
"backupDescription3": "通知に使用するトークンなどの機密データも含まれています。注意して扱ってください。",
|
||||
"alertNoFile": "インポートするファイルを選択してください。",
|
||||
@@ -171,7 +171,7 @@
|
||||
"Shrink Database": "データベースの縮小",
|
||||
"Start": "始める",
|
||||
"Retry": "リトライ",
|
||||
"Please read": "読んでください",
|
||||
"Please read": "次のリンクを参考にしてください",
|
||||
"Orange": "橙",
|
||||
"Gateway Type": "ゲートウェイの種類",
|
||||
"Game": "ゲーム",
|
||||
@@ -240,7 +240,7 @@
|
||||
"Unpin": "ピンを外す",
|
||||
"Switch to Light Theme": "ライトテーマに切り替える",
|
||||
"Hide Tags": "タグを隠す",
|
||||
"Description": "概要",
|
||||
"Description": "メモ",
|
||||
"Untitled Group": "名前の無いグループ",
|
||||
"Services": "サービス",
|
||||
"Discard": "破棄",
|
||||
@@ -258,7 +258,7 @@
|
||||
"proxyDescription": "プロキシはモニターに割り当てられていないと機能しません。",
|
||||
"setAsDefaultProxyDescription": "このプロキシは、新しいモニターに対してデフォルトで有効になっています。モニターごとに個別にプロキシを無効にすることができます。",
|
||||
"Remove Token": "Tokenを削除",
|
||||
"Stop": "止める",
|
||||
"Stop": "停止",
|
||||
"Add New Status Page": "新しいステータスページを追加",
|
||||
"Next": "次へ",
|
||||
"No Proxy": "プロキシなし",
|
||||
@@ -500,7 +500,7 @@
|
||||
"default: notify all devices": "デフォルト:すべてのデバイスに通知する",
|
||||
"Trigger type:": "トリガータイプ:",
|
||||
"Event data:": "イベントデータ:",
|
||||
"backupOutdatedWarning": "非推奨:多くの機能が追加され、このバックアップ機能は少しメンテナンスされていないため、完全なバックアップの生成や復元はできません。",
|
||||
"backupOutdatedWarning": "非推奨: 多くの機能に変更があり、バックアップ機能の開発が一部滞っているため、完全なバックアップの作成や復元ができません。",
|
||||
"backupRecommend": "代わりにボリュームまたはデータフォルダ(./data/)を直接バックアップしてください。",
|
||||
"recurringInterval": "インターバル",
|
||||
"Recurring": "繰り返し",
|
||||
@@ -512,5 +512,9 @@
|
||||
"Device Token": "デバイストークン",
|
||||
"recurringIntervalMessage": "毎日1回実行する|{0} 日に1回実行する",
|
||||
"Add New Tag": "新しいタグを追加",
|
||||
"statusPageMaintenanceEndDate": "終了日"
|
||||
"statusPageMaintenanceEndDate": "終了日",
|
||||
"Body Encoding": "ボディエンコード",
|
||||
"Learn More": "さらに詳しく",
|
||||
"infiniteRetention": "保持期間を無制限にしたい場合は、0に設定してください。",
|
||||
"Display Timezone": "表示タイムゾーン"
|
||||
}
|
||||
|
@@ -660,7 +660,7 @@
|
||||
"Disable": "비활성화",
|
||||
"Single Maintenance Window": "단일 점검",
|
||||
"Maintenance Time Window of a Day": "점검 시간",
|
||||
"Effective Date Range": "유효 날짜 범위",
|
||||
"Effective Date Range": "유효 날짜 범위 (옵션)",
|
||||
"Schedule Maintenance": "점검 예약하기",
|
||||
"Date and Time": "날짜 및 시간",
|
||||
"DateTime Range": "날짜 시간 범위",
|
||||
@@ -699,7 +699,7 @@
|
||||
"cloneOf": "{0}의 복제본",
|
||||
"Clone Monitor": "모니터링 복제",
|
||||
"telegramProtectContent": "포워딩/저장 보호",
|
||||
"telegramProtectContentDescription": "활성화 시, 텔레그램 봇 메시지는 포워딩 및 저장으로부터 보호됩니다.",
|
||||
"telegramProtectContentDescription": "활성화 할경우 텔레그램 봇 메시지는 포워딩 및 저장으로부터 보호됩니다.",
|
||||
"telegramSendSilentlyDescription": "조용히 메시지를 보냅니다. 사용자들은 무음으로 알림을 받습니다.",
|
||||
"telegramSendSilently": "무음 알림",
|
||||
"Add New Tag": "태그 추가",
|
||||
@@ -719,5 +719,35 @@
|
||||
"notificationRegional": "지역별",
|
||||
"Google Analytics ID": "Google Analytics ID",
|
||||
"Add API Key": "API 키 추가",
|
||||
"apiKeyAddedMsg": "API 키가 추가되었습니다. 다시 표시되지 않을 것이므로 메모해 두세요."
|
||||
"apiKeyAddedMsg": "API 키가 추가되었습니다. 다시 표시되지 않을 것이므로 메모해 두세요.",
|
||||
"pagertreeCritical": "긴급",
|
||||
"apiKey-active": "사용 가능",
|
||||
"lunaseaUserID": "사용자 ID",
|
||||
"apiKey-expired": "만료됨",
|
||||
"Expires": "만료일",
|
||||
"twilioAuthToken": "인증 토큰",
|
||||
"twilioFromNumber": "번호에서",
|
||||
"twilioToNumber": "번호에서",
|
||||
"twilioAccountSID": "계정 SID",
|
||||
"pagertreeUrgency": "긴급",
|
||||
"sameAsServerTimezone": "서버 시간대로 설정하기",
|
||||
"startDateTime": "시작 시간",
|
||||
"endDateTime": "종료 시간",
|
||||
"cronExpression": "Cron 값",
|
||||
"cronSchedule": "스케줄: ",
|
||||
"invalidCronExpression": "알수없는 Cron 값입니다: {0}",
|
||||
"Add Another": "다른 항목 추가",
|
||||
"apiKey-inactive": "비활성화",
|
||||
"pagertreeIntegrationUrl": "Integration 링크",
|
||||
"pagertreeLow": "낮음",
|
||||
"pagertreeMedium": "중간",
|
||||
"pagertreeHigh": "높음",
|
||||
"pagertreeResolve": "자동으로 해결하기",
|
||||
"pagertreeDoNothing": "아무것도 하지 않음",
|
||||
"wayToGetPagerTreeIntegrationURL": "PagerTree에서 Uptime Kuma 통합을 생성한 후 Endpoint를 복사합니다. 전체 세부 정보 보기 {0}",
|
||||
"lunaseaTarget": "대상",
|
||||
"lunaseaDeviceID": "기기 ID",
|
||||
"statusPageRefreshIn": "{0} 후 새로고침",
|
||||
"telegramMessageThreadIDDescription": "포럼의 대상 메시지 쓰레드(주제)에 대한 선택적 고유 식별인, 포럼 관리자 그룹에만 해당",
|
||||
"pagertreeSilent": "없음"
|
||||
}
|
||||
|
28
src/lang/ms.json
Normal file
28
src/lang/ms.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"Help": "Bantuan",
|
||||
"New Update": "Kemaskini baharu",
|
||||
"Appearance": "Penampilan",
|
||||
"Theme": "Tema",
|
||||
"General": "Umum",
|
||||
"Game": "Permainan",
|
||||
"Primary Base URL": "URL Pangkalan Utama",
|
||||
"Version": "Versi",
|
||||
"Add": "Menambah",
|
||||
"Quick Stats": "Statistik ringkas",
|
||||
"Up": "Dalam talian",
|
||||
"Down": "Luar talian",
|
||||
"Pending": "Belum selesai",
|
||||
"statusMaintenance": "Membaiki",
|
||||
"Maintenance": "Membaiki",
|
||||
"Unknown": "Tidak ketahui",
|
||||
"General Monitor Type": "Jenis monitor umum",
|
||||
"Check Update On GitHub": "Semak kemas kini dalam GitHub",
|
||||
"List": "Senarai",
|
||||
"Specific Monitor Type": "Jenis monitor spesifik",
|
||||
"markdownSupported": "Sintaks markdown disokong",
|
||||
"languageName": "Bahasa inggeris",
|
||||
"Dashboard": "Papan pemuka",
|
||||
"Language": "Bahasa",
|
||||
"Add New Monitor": "Tambah monitor baharu",
|
||||
"Passive Monitor Type": "Jenis monitor pasif"
|
||||
}
|
@@ -536,11 +536,11 @@
|
||||
"pushoversounds cosmic": "Kosmiczny",
|
||||
"pushoversounds falling": "Spadek",
|
||||
"pushoversounds gamelan": "Gamelan",
|
||||
"pushoversounds incoming": "Incoming",
|
||||
"pushoversounds intermission": "Intermission",
|
||||
"pushoversounds incoming": "Przychodzące",
|
||||
"pushoversounds intermission": "Przerwa",
|
||||
"pushoversounds magic": "Magia",
|
||||
"pushoversounds mechanical": "Mechaniczny",
|
||||
"pushoversounds pianobar": "Piano Bar",
|
||||
"pushoversounds pianobar": "fortepianowy klawisz",
|
||||
"pushoversounds siren": "Syrena",
|
||||
"pushoversounds spacealarm": "Alarm kosmiczny",
|
||||
"pushoversounds tugboat": "Holownik",
|
||||
@@ -608,7 +608,7 @@
|
||||
"backupRecommend": "Zamiast tego należy wykonać bezpośrednią kopię zapasową woluminu lub folderu danych (./data/).",
|
||||
"Optional": "Opcjonalne",
|
||||
"squadcast": "Squadcast",
|
||||
"SendKey": "SendKey",
|
||||
"SendKey": "Przycisk Wyślij",
|
||||
"SMSManager API Docs": "Dokumentacja API SMSManager ",
|
||||
"Gateway Type": "Typ bramy",
|
||||
"SMSManager": "SMSManager",
|
||||
@@ -663,7 +663,7 @@
|
||||
"IconUrl": "URL ikony",
|
||||
"Enable DNS Cache": "Włącz pamięć podręczną DNS",
|
||||
"Single Maintenance Window": "Pojedyncze okno konserwacji",
|
||||
"Effective Date Range": "Zakres dat obowiązywania",
|
||||
"Effective Date Range": "Zakres dat obowiązywania (opcjonalnie)",
|
||||
"Schedule Maintenance": "Planowanie konserwacji",
|
||||
"DateTime Range": "Zakres czasowy",
|
||||
"Maintenance Time Window of a Day": "Okno czasowe konserwacji na dzień",
|
||||
@@ -727,5 +727,29 @@
|
||||
"Key Added": "Klucz dodany",
|
||||
"pagertreeDoNothing": "Nie rób nic",
|
||||
"wayToGetPagerTreeIntegrationURL": "Po utworzeniu integracji Uptime Kuma w PagerTree, należy skopiować Endpoint. Zobacz pełne szczegóły {0}",
|
||||
"notificationRegional": "Regionalne"
|
||||
"notificationRegional": "Regionalne",
|
||||
"twilioFromNumber": "Z numeru",
|
||||
"twilioToNumber": "Do numeru",
|
||||
"lunaseaTarget": "Cel",
|
||||
"twilioAccountSID": "SID konta",
|
||||
"twilioAuthToken": "Token autoryzacyjny",
|
||||
"apiKeyAddedMsg": "Twój klucz API został dodany. Prosimy o zanotowanie go, ponieważ nie będzie on już więcej pokazywany.",
|
||||
"telegramMessageThreadID": "(Opcjonalne) ID wątku wiadomości",
|
||||
"telegramMessageThreadIDDescription": "Opcjonalny Unikalny identyfikator dla docelowego wątku wiadomości (tematu) forum; tylko dla supergrup forum",
|
||||
"telegramProtectContent": "Ochrona przekazywania/zapisywania",
|
||||
"telegramProtectContentDescription": "Jeśli włączona, wiadomości bota w Telegramie będą chronione przed przekazywaniem i zapisywaniem.",
|
||||
"telegramSendSilently": "Wyślij po cichu",
|
||||
"telegramSendSilentlyDescription": "Wysyła wiadomość po cichu. Użytkownicy otrzymają powiadomienie bez dźwięku.",
|
||||
"statusPageRefreshIn": "Odświeżenie w ciągu: {0}",
|
||||
"lunaseaDeviceID": "ID urządzenia",
|
||||
"lunaseaUserID": "ID użytkownika",
|
||||
"Add New Tag": "Dodaj nowy tag",
|
||||
"startDateTime": "Data/godzina rozpoczęcia",
|
||||
"cronSchedule": "Harmonogram: ",
|
||||
"invalidCronExpression": "Nieprawidłowe sformułowanie Cron: {0}",
|
||||
"sameAsServerTimezone": "Tak jak strefa czasowa serwera",
|
||||
"endDateTime": "Data/godzina zakończenia",
|
||||
"cronExpression": "Wyrażenie Cron",
|
||||
"ntfyAuthenticationMethod": "Metoda Uwierzytelnienia",
|
||||
"ntfyUsernameAndPassword": "Nazwa użytkownika i hasło"
|
||||
}
|
||||
|
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"languageName": "Português (Brasileiro)",
|
||||
"languageName": "Português (Brasil)",
|
||||
"checkEverySecond": "Verificar a cada {0} segundos",
|
||||
"retryCheckEverySecond": "Tentar novamente a cada {0} segundos",
|
||||
"retriesDescription": "Máximo de tentativas antes que o serviço seja marcado como inativo e uma notificação seja enviada",
|
||||
"ignoreTLSError": "Ignorar erros TLS/SSL para sites HTTPS",
|
||||
"upsideDownModeDescription": "Inverta o status de cabeça para baixo. Se o serviço estiver acessível, ele está OFFLINE.",
|
||||
"upsideDownModeDescription": "Inverta o status. Se o serviço estiver acessível, ele está DESLIGADO.",
|
||||
"maxRedirectDescription": "Número máximo de redirecionamentos a seguir. Defina como 0 para desativar redirecionamentos.",
|
||||
"acceptedStatusCodesDescription": "Selecione os códigos de status que são considerados uma resposta bem-sucedida.",
|
||||
"passwordNotMatchMsg": "A senha repetida não corresponde.",
|
||||
@@ -27,7 +27,7 @@
|
||||
"confirmEnableTwoFAMsg": "Tem certeza de que deseja habilitar 2FA?",
|
||||
"confirmDisableTwoFAMsg": "Tem certeza de que deseja desativar 2FA?",
|
||||
"Settings": "Configurações",
|
||||
"Dashboard": "Dashboard",
|
||||
"Dashboard": "Painel",
|
||||
"New Update": "Nova Atualização",
|
||||
"Language": "Linguagem",
|
||||
"Appearance": "Aparência",
|
||||
@@ -39,8 +39,8 @@
|
||||
"Add": "Adicionar",
|
||||
"Add New Monitor": "Adicionar novo monitor",
|
||||
"Quick Stats": "Estatísticas rápidas",
|
||||
"Up": "On",
|
||||
"Down": "Off",
|
||||
"Up": "Ligado",
|
||||
"Down": "Desligado",
|
||||
"Pending": "Pendente",
|
||||
"Unknown": "Desconhecido",
|
||||
"Pause": "Pausar",
|
||||
@@ -49,12 +49,12 @@
|
||||
"DateTime": "Data hora",
|
||||
"Message": "Mensagem",
|
||||
"No important events": "Nenhum evento importante",
|
||||
"Resume": "Resumo",
|
||||
"Resume": "Retomar",
|
||||
"Edit": "Editar",
|
||||
"Delete": "Deletar",
|
||||
"Delete": "Apagar",
|
||||
"Current": "Atual",
|
||||
"Uptime": "Tempo de atividade",
|
||||
"Cert Exp.": "Cert Exp.",
|
||||
"Cert Exp.": "Expiração Do Certificado",
|
||||
"day": "dia | dias",
|
||||
"-day": "-dia",
|
||||
"hour": "hora",
|
||||
@@ -71,9 +71,9 @@
|
||||
"Retries": "Novas tentativas",
|
||||
"Heartbeat Retry Interval": "Intervalo de repetição de Heartbeat",
|
||||
"Advanced": "Avançado",
|
||||
"Upside Down Mode": "Modo de cabeça para baixo",
|
||||
"Upside Down Mode": "Modo Invertido",
|
||||
"Max. Redirects": "Redirecionamentos Máx",
|
||||
"Accepted Status Codes": "Status Code Aceitáveis",
|
||||
"Accepted Status Codes": "Códigos HTTP Aceitáveis",
|
||||
"Save": "Salvar",
|
||||
"Notifications": "Notificações",
|
||||
"Not available, please setup.": "Não disponível, por favor configure.",
|
||||
@@ -131,7 +131,7 @@
|
||||
"Create": "Criar",
|
||||
"Clear Data": "Limpar Dados",
|
||||
"Events": "Eventos",
|
||||
"Heartbeats": "Heartbeats",
|
||||
"Heartbeats": "Batimentos Cardíacos",
|
||||
"Auto Get": "Obter Automático",
|
||||
"backupDescription": "Você pode fazer backup de todos os monitores e todas as notificações em um arquivo JSON.",
|
||||
"backupDescription2": "OBS: Os dados do histórico e do evento não estão incluídos.",
|
||||
@@ -187,17 +187,17 @@
|
||||
"Select status pages...": "Selecionar status pages…",
|
||||
"Game": "Jogo",
|
||||
"Passive Monitor Type": "Tipo de monitoramento passivo",
|
||||
"Specific Monitor Type": "Especificar tipo de monitoramento",
|
||||
"Specific Monitor Type": "Tipo de monitoramento específico",
|
||||
"Monitor": "Monitoramento | Monitoramentos",
|
||||
"needPushEvery": "Você deve chamar esta URL a cada {0} segundos.",
|
||||
"Push URL": "Push URL",
|
||||
"Push URL": "URL de push",
|
||||
"Custom": "Personalizado",
|
||||
"here": "aqui",
|
||||
"Required": "Requerido",
|
||||
"webhookJsonDesc": "{0} é bom para qualquer servidor HTTP moderno, como Express.js",
|
||||
"webhookAdditionalHeadersTitle": "Cabeçalhos Adicionais",
|
||||
"webhookAdditionalHeadersDesc": "Define cabeçalhos adicionais enviados com o webhook.",
|
||||
"Webhook URL": "Webhook URL",
|
||||
"Webhook URL": "URL Do Webhook",
|
||||
"Priority": "Prioridade",
|
||||
"Read more": "Ver mais",
|
||||
"appriseInstalled": "Apprise está instalado.",
|
||||
@@ -270,15 +270,319 @@
|
||||
"All Status Pages": "Todas as Status Pages",
|
||||
"Method": "Método",
|
||||
"General Monitor Type": "Tipo de monitoramento geral",
|
||||
"markdownSupported": "Sintaxe Markdown suportada",
|
||||
"emojiCheatSheet": "Folha de dicas de emojis: {0}",
|
||||
"topic": "Tema",
|
||||
"markdownSupported": "Markdown suportado",
|
||||
"emojiCheatSheet": "Dicas de Emojis",
|
||||
"topic": "Tópico",
|
||||
"topicExplanation": "Tópico MQTT para monitorar",
|
||||
"successMessageExplanation": "Mensagem MQTT que será considerada como sucesso",
|
||||
"Content Type": "Tipo de Conteúdo",
|
||||
"Content Type": "Tipo do Conteúdo",
|
||||
"Shrink Database": "Encolher Banco de Dados",
|
||||
"Content": "Conteúdo",
|
||||
"Pick a RR-Type...": "Escolha um tipo RR…",
|
||||
"Pick Accepted Status Codes...": "Escolha Códigos de Status Aceitos…",
|
||||
"Pick Affected Monitors...": "Escolher Monitores Afetados…"
|
||||
"Pick a RR-Type...": "Selecione um RR-Type…",
|
||||
"Pick Accepted Status Codes...": "Selecione Os Códigos de Status Aceitos…",
|
||||
"Pick Affected Monitors...": "Selecione os Monitores Afetados…",
|
||||
"Channel Name": "Nome Do Canal",
|
||||
"Don't know how to get the token? Please read the guide:": "Não sabe com pegar o token? Por favor, leia o guia:",
|
||||
"smtpDkimheaderFieldNames": "Chaves Do Cabeçalho para assinar (Opcional)",
|
||||
"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.": "A conexão atual pode ser perdida se você estiver se conectando pelo túnel da Cloudflare. Você tem certeza que deseja pará-lo? Digite a sua senha para confirmar.",
|
||||
"shrinkDatabaseDescription": "Acionar a limpeza do banco de dados para o SQLite. Se o seu banco de dados foi criado depois de 1.10.0, a limpeza automática(AUTO_VACUUM) já é habilitada por padrão e essa ação não é necessária.",
|
||||
"Powered by": "Fornecido por",
|
||||
"deleteProxyMsg": "Você tem certeza que deseja deletar este proxy para todos os monitores?",
|
||||
"proxyDescription": "Os proxies devem ser atribuídos a um monitor para funcionar.",
|
||||
"Certificate Chain": "Cadeia De Certificados",
|
||||
"Domain Name Expiry Notification": "Notificação De Expiração Do Nome Do Domínio",
|
||||
"Proxy": "Proxy",
|
||||
"wayToGetTelegramChatID": "Você pode pegar o Chat ID enviando uma mensagem marcando o bot no grupo e indo nessa URL para ver o chat_id:",
|
||||
"wayToGetLineNotifyToken": "Você pode pegar o token de acesso de {0}",
|
||||
"disableCloudflaredNoAuthMsg": "Você está no modo sem autenticação, a senha não é necessária.",
|
||||
"Frontend Version do not match backend version!": "Versão do frontend é diferente da versão do backend!",
|
||||
"strategyManual": "Ativar/Desativar Manualmente",
|
||||
"weekdayShortThu": "Qui",
|
||||
"Basic Settings": "Configurações Básicas",
|
||||
"User ID": "ID Do Usuário",
|
||||
"Line Developers Console": "Linha Do Terminal De Desenvolvimento",
|
||||
"lineDevConsoleTo": "Linha Do Terminal De Desenvolvimento- {0}",
|
||||
"smseagleToken": "Token De Acesso Da API",
|
||||
"Notification Service": "Serviço De Notificação",
|
||||
"default: notify all devices": "padrão: notificar todos os dispositivos",
|
||||
"Trigger type:": "Tipo Do Acionamento:",
|
||||
"Then choose an action, for example switch the scene to where an RGB light is red.": "",
|
||||
"Enable": "Habilitado",
|
||||
"Disable": "Desabilitado",
|
||||
"IconUrl": "URL Do Ícone",
|
||||
"Enable DNS Cache": "Habilitar Cache Do DNS",
|
||||
"Single Maintenance Window": "Janela Única De Manutenção",
|
||||
"dnsCacheDescription": "Pode não funcionar em alguns ambientes com IPv6, desabita caso encontre qualquer problema.",
|
||||
"Messaging API": "API Da Mensageira",
|
||||
"Icon URL": "URL Do Ícone",
|
||||
"Clone Monitor": "Clonar Monitoramento",
|
||||
"Clone": "Clonar",
|
||||
"cloneOf": "Clone do {0}",
|
||||
"deleteMaintenanceMsg": "Você tem certeza que deseja apagar essa manutenção?",
|
||||
"sameAsServerTimezone": "O mesmo do servidor de fuso-horário",
|
||||
"startDateTime": "Início Data/Horário",
|
||||
"endDateTime": "Fim Data/Horário",
|
||||
"cronExpression": "Expressão Do Cron",
|
||||
"cronSchedule": "Agendar: ",
|
||||
"invalidCronExpression": "Expressão Cron inválida: {0}",
|
||||
"Display Timezone": "Mostrar Fuso-horário",
|
||||
"Server Timezone": "Servidor De Fuso-horário",
|
||||
"statusPageMaintenanceEndDate": "Fim",
|
||||
"Schedule Maintenance": "Agendar Manutenção",
|
||||
"Date and Time": "Data E Horário",
|
||||
"DateTime Range": "Intervalo De Tempo",
|
||||
"Maintenance Time Window of a Day": "Janela de tempo de manutenção de um dia",
|
||||
"uninstalling": "Desinstalando",
|
||||
"confirmUninstallPlugin": "Você tem certeza were quer desinstalar esse plugin?",
|
||||
"notificationRegional": "Região",
|
||||
"dnsPortDescription": "Porta do servidor DNS. O padrão é 53. Você pode mudar a porta em qualquer momento.",
|
||||
"affectedMonitorsDescription": "Selecione os monitores afetados pela manutenção atual",
|
||||
"Icon Emoji": "Ícone Do Emoji",
|
||||
"wayToGetKookBotToken": "Criar aplicação e pegar o token do bot em {0}",
|
||||
"Notification Sound": "Som De Notificação",
|
||||
"More info on:": "Mais informações em: {0}",
|
||||
"SMS Type": "Tipo Do SMS",
|
||||
"Internal Room Id": "ID Interno Da Sala",
|
||||
"Platform": "Plataforma",
|
||||
"serwersmsAPIPassword": "Senha Da API",
|
||||
"serwersmsPhoneNumber": "Número Do Telefone",
|
||||
"documentation": "documentação",
|
||||
"smtpDkimDomain": "Nome Do Domínio",
|
||||
"smtpDkimKeySelector": "Chave Selecionadora",
|
||||
"smtpDkimPrivateKey": "Chave Privada",
|
||||
"smtpDkimHashAlgo": "Algoritmo Hash (Opcional)",
|
||||
"smtpDkimskipFields": "Chaves Do Cabeçalho para não assinar (Opcional)",
|
||||
"alertaEnvironment": "Ambiente",
|
||||
"alertaRecoverState": "Estado De Recuperação",
|
||||
"smseagleEncoding": "Enviar como Unicode",
|
||||
"onebotGroupMessage": "Grupo",
|
||||
"onebotPrivateMessage": "Privado",
|
||||
"onebotUserOrGroupId": "ID do Grupo/Usuário",
|
||||
"No Maintenance": "Sem Manutenção",
|
||||
"telegramProtectContentDescription": "Se ativado, a mensagens do bot do Telegram serão protegidas contra encaminhamentos e salvamento.",
|
||||
"telegramProtectContent": "Proteger Contra Encaminhamento/Salvamento",
|
||||
"affectedStatusPages": "Mostrar essa mensagem de manutenção nas páginas de status selecionadas",
|
||||
"loadingError": "Não foi possível pegar os dados, por favor tente novamente.",
|
||||
"Bot Display Name": "Nome Visível Do Bot",
|
||||
"Access Token": "Token De Acesso",
|
||||
"Unpin": "Desfixar",
|
||||
"telegramSendSilently": "Enviar Silenciosamente",
|
||||
"telegramSendSilentlyDescription": "Enviar a mensagem silenciosamente. Os usuários não receberam uma notificação com som.",
|
||||
"YOUR BOT TOKEN HERE": "O SEU TOKEN DO BOT VAI AQUI",
|
||||
"warningTimezone": "Está usando os servidores de fuso-horários",
|
||||
"dayOfWeek": "Dia Da Semana",
|
||||
"dayOfMonth": "Dia Do Mês",
|
||||
"lastDay": "Último Dia",
|
||||
"lastDay1": "Último Dia Do Mês",
|
||||
"lastDay2": "Penúltimo Dia Do Mês",
|
||||
"lastDay3": "Antepenúltimo Dia Do Mês",
|
||||
"lastDay4": "Quarto Último Dia Do Mês",
|
||||
"weekdayShortMon": "Seg",
|
||||
"weekdayShortTue": "Ter",
|
||||
"weekdayShortWed": "Qua",
|
||||
"weekdayShortFri": "Sex",
|
||||
"weekdayShortSat": "Sab",
|
||||
"weekdayShortSun": "Dom",
|
||||
"wayToGetTeamsURL": "Você pode aprender a como criar a URL do webhook {0}.",
|
||||
"Hello @everyone is...": "Olá {'@'}everyone é…",
|
||||
"Number": "Número",
|
||||
"install": "Instalar",
|
||||
"installing": "Instalando",
|
||||
"uninstall": "Desinstalar",
|
||||
"Ignore TLS Error": "Ignorar Erro De TLS",
|
||||
"Discord Webhook URL": "URL Do Webhook Do Discord",
|
||||
"emailCustomSubject": "Assunto Personalizado",
|
||||
"Prefix Custom Message": "Prefixo Personalizado Da Mensagem",
|
||||
"wayToGetZohoCliqURL": "Você pode aprender a como criar uma URL de Webhook {0}.",
|
||||
"Channel access token": "Canal do token de acesso",
|
||||
"promosmsPassword": "Senha Da API",
|
||||
"promosmsLogin": "Nome Do Login Da API",
|
||||
"atLeastOneMonitor": "Selecione pelo menos um monitoramento afetado",
|
||||
"apiCredentials": "Credenciais Da API",
|
||||
"For safety, must use secret key": "Para segurança deve se usar uma chave secreta",
|
||||
"Device Token": "Token Do Dispositivo",
|
||||
"Retry": "Tentar Novamente",
|
||||
"Topic": "Tópico",
|
||||
"Setup Proxy": "Configuração Do Proxy",
|
||||
"Proxy Protocol": "Protocolo Do Proxy",
|
||||
"Proxy Server": "Servidor Proxy",
|
||||
"Proxy server has authentication": "O servidor proxy tem autenticação",
|
||||
"aboutWebhooks": "Mais informações sobre Webhooks em: {0}",
|
||||
"Integration Key": "Chave De Integração",
|
||||
"Integration URL": "URL De Integração",
|
||||
"do nothing": "fazendo nada",
|
||||
"onebotSafetyTips": "Por segurança deve adicionar o token de acesso",
|
||||
"Subject:": "Assunto:",
|
||||
"Valid To:": "Válido para:",
|
||||
"For example: nginx, Apache and Traefik.": "Por exemplo: Nginx, Apache e Traefik.",
|
||||
"Please read": "Por favor, leia",
|
||||
"RadiusCallingStationIdDescription": "Identificador do dispositivo de chamada",
|
||||
"certificationExpiryDescription": "O monitoramento por HTTPS envia a notificação quando o certificado TLS expirar em:",
|
||||
"or": "ou",
|
||||
"Effective Date Range": "Intervalo Efetivo De Data (Opcional)",
|
||||
"recurringIntervalMessage": "Rodar diariamente | Rodar a cada {0} dias",
|
||||
"Status:": "Status: {0}",
|
||||
"smtpDkimSettings": "Configurações DKIM",
|
||||
"alertaApiKey": "Chave Da API",
|
||||
"alertaAlertState": "Estado Do Alerta",
|
||||
"statusPageRefreshIn": "Atualizando em: {0}",
|
||||
"Untitled Group": "Grupo Sem Título",
|
||||
"primary": "primário",
|
||||
"setAsDefaultProxyDescription": "Este proxy será habilitado por padrão em todos os novos monitores. Você pode desabilitar o proxy individualmente para cada monitor.",
|
||||
"Valid": "Válido",
|
||||
"Invalid": "Inválido",
|
||||
"User": "Usuário",
|
||||
"Installed": "Instalado",
|
||||
"Not installed": "Não instalado",
|
||||
"enableProxyDescription": "Este proxy não afetará as solicitações do monitor até que seja ativado. Você pode controlar temporariamente a desativação do proxy de todos os monitores pelo status de ativação.",
|
||||
"Not running": "Desabilitado",
|
||||
"Remove Token": "Remover Token",
|
||||
"Start": "Iniciar",
|
||||
"Stop": "Parar",
|
||||
"Add New Status Page": "Adicionar Nova Página De Status",
|
||||
"Accept characters:": "Caracteres aceitos:",
|
||||
"Running": "Habilitado",
|
||||
"startOrEndWithOnly": "Apenas iniciar ou parar com {0}",
|
||||
"No consecutive dashes": "Sem traços consecutivos",
|
||||
"Next": "Próximo",
|
||||
"No Proxy": "Sem Proxy",
|
||||
"Authentication": "Autenticação",
|
||||
"HTTP Basic Auth": "Autenticação Básica No HTTP",
|
||||
"New Status Page": "Nova Página De Status",
|
||||
"Page Not Found": "Página Não Encontrada",
|
||||
"Reverse Proxy": "Proxy Reverso",
|
||||
"About": "Sobre",
|
||||
"Message:": "Mensagem:",
|
||||
"HTTP Headers": "Cabeçalhos HTTP",
|
||||
"Trust Proxy": "Proxy Confiável",
|
||||
"Other Software": "Outros Programas",
|
||||
"Days Remaining:": "Dias Restantes:",
|
||||
"No status pages": "Sem página de status",
|
||||
"Date Created": "Data De Criação",
|
||||
"Backup": "Cópia de Segurança",
|
||||
"wayToGetCloudflaredURL": "(Baixe o CloudFlareD de {0})",
|
||||
"cloudflareWebsite": "Site Da CloudaFlare",
|
||||
"Issuer:": "Emissor:",
|
||||
"Fingerprint:": "Impressão Digital:",
|
||||
"Footer Text": "Texto Do Rodapé",
|
||||
"Domain Names": "Nome Dos Domínios",
|
||||
"signedInDispDisabled": "Autenticação Desabilitada.",
|
||||
"RadiusSecretDescription": "Compartilhe o Segredo entre o cliente e o servidor",
|
||||
"Certificate Expiry Notification": "Notificação De Certificado Expirado",
|
||||
"The resource is no longer available.": "O recurso não está mais disponível.",
|
||||
"There might be a typing error in the address.": "Pode ter um erro de digitação no endereço.",
|
||||
"Retype the address.": "Digitar novamente o endereço.",
|
||||
"Go back to the previous page.": "Voltar para a página anterior.",
|
||||
"Query": "Query",
|
||||
"settingsCertificateExpiry": "O Certificado TLS Expira",
|
||||
"Connection Type": "Tipo Da Conexão",
|
||||
"signedInDisp": "Assinado como {0}",
|
||||
"RadiusCallingStationId": "ID Da Estação De Chamada",
|
||||
"RadiusCalledStationIdDescription": "Identificador do dispositivo de chamada",
|
||||
"Coming Soon": "Em Breve",
|
||||
"Connection String": "String De Conexão",
|
||||
"Docker Daemon": "Daemon Do Docker",
|
||||
"Show Powered By": "Mostrar Distribuído Por",
|
||||
"RadiusSecret": "Segredo Radius",
|
||||
"RadiusCalledStationId": "ID Da Estação Chamada",
|
||||
"deleteDockerHostMsg": "Você tem certeza que quer deletar esse host do Docker para todos os monitores?",
|
||||
"tcp": "TCP / HTTP",
|
||||
"Docker Container": "Container Docker",
|
||||
"Container Name / ID": "Nome / ID do Container",
|
||||
"Domain": "Domínio",
|
||||
"Workstation": "Estação De Trabalho",
|
||||
"Packet Size": "Tamanho Do Pacote",
|
||||
"Bot Token": "Token do Bot",
|
||||
"wayToGetTelegramToken": "Você pode pegar o token de {0}.",
|
||||
"chatIDNotFound": "Chat ID não encontrado; por favor envia uma mensagem para o bot primeiro",
|
||||
"Chat ID": "Chat ID",
|
||||
"Docker Hosts": "Hosts Do Docker",
|
||||
"Docker Host": "Host Do Docker",
|
||||
"Examples": "Exemplos",
|
||||
"maintenanceStatus-under-maintenance": "Em Manutenção",
|
||||
"Long-Lived Access Token": "Token De Acesso De Longa Duração",
|
||||
"Home Assistant URL": "URL Do Home Assinant",
|
||||
"Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "O token de acessos de longa duração pode ser criado clicando no nome do seu perfil, com o botão esquerdo, ir até o final da lista e clicar em Criar Token. ",
|
||||
"Event type:": "Tipo Do Evento:",
|
||||
"Event data:": "Dados Do Evento:",
|
||||
"Frontend Version": "Versão Do Frontend",
|
||||
"backupRecommend": "Por favor faça uma cópia do volume ou da pasta com dados(./data/) diretamente ao invés.",
|
||||
"Optional": "Opcional",
|
||||
"recurringInterval": "Intervalo",
|
||||
"Recurring": "Recorrente",
|
||||
"pauseMaintenanceMsg": "Você tem certeza que quer pausar?",
|
||||
"maintenanceStatus-inactive": "Inativo",
|
||||
"maintenanceStatus-scheduled": "Agendado",
|
||||
"maintenanceStatus-ended": "Terminando",
|
||||
"maintenanceStatus-unknown": "Desconhecido",
|
||||
"enableGRPCTls": "Permita para enviar requisições gRPC com conexões TLS",
|
||||
"confirmDeleteTagMsg": "Você tem certeza que deseja apagar essa tag? Monitores associados a essa tag não serão apagados.",
|
||||
"grpcMethodDescription": "O nome do método é convertido para o formato cammelCase, exemplos: enviarBomDia, verificar, etc.",
|
||||
"infiniteRetention": "Defina como 0 para um tempo infinito de retenção.",
|
||||
"octopushLegacyHint": "Você usa a versão legada do Octopush (2011-2020) ou a nova versão?",
|
||||
"Example:": "Exemplo: {0}",
|
||||
"Read more:": "Leia mais em: {0}",
|
||||
"promosmsAllowLongSMS": "Permitir SMS grandes",
|
||||
"Android": "Android",
|
||||
"Huawei": "Huawei",
|
||||
"smseagleTo": "Números Dos Telefones",
|
||||
"smseaglePriority": "Prioridade da mensagem (0-9, padrão=0)",
|
||||
"dataRetentionTimeError": "O período de retenção tem que ser maior ou igual a 0",
|
||||
"User Key": "Chave Do Usuário",
|
||||
"Device": "Dispositivo",
|
||||
"Message Title": "Título Da Mensagem",
|
||||
"defaultNotificationName": "Minha {notification} Alerta({number})",
|
||||
"light": "claro",
|
||||
"socket": "Soquete",
|
||||
"Add New Tag": "Adicionar Nova Tag",
|
||||
"API Username": "Usuário Da API",
|
||||
"API Key": "Chave Da API",
|
||||
"Show update if available": "Mostrar atualização se disponível",
|
||||
"Also check beta release": "Também verificar lançamentos em beta",
|
||||
"Using a Reverse Proxy?": "Está usando um Proxy Reverso?",
|
||||
"Check how to config it for WebSocket": "Verifique como configurar para o WebSocket",
|
||||
"Steam Game Server": "Servidor De Jogo Da Steam",
|
||||
"Most likely causes:": "Causas mais prováveis:",
|
||||
"What you can try:": "O que você pode tentar:",
|
||||
"apiKey-active": "Ativa",
|
||||
"Expiry": "Expiração",
|
||||
"endpoint": "endpoint",
|
||||
"pagertreeIntegrationUrl": "URL de Integração",
|
||||
"pagertreeUrgency": "Urgência",
|
||||
"telegramMessageThreadID": "(Opcional) Message Thread ID",
|
||||
"Edit Tag": "Editar Etiqueta",
|
||||
"Server Address": "Endereço do Servidor",
|
||||
"Learn More": "Aprender Mais",
|
||||
"needSignalAPI": "Você precisa de um cliente Signal com API REST.",
|
||||
"Generate": "Gerar",
|
||||
"deleteAPIKeyMsg": "Você tem certeza de que quer apagar essa chave de API?",
|
||||
"plugin": "Plugin | Plugins",
|
||||
"Expiry date": "Data de expiração",
|
||||
"Don't expire": "Não expira",
|
||||
"Continue": "Continuar",
|
||||
"Add Another": "Adicionar Outro",
|
||||
"Key Added": "Chave Adicionada",
|
||||
"Add API Key": "Adicionar chave de API",
|
||||
"No API Keys": "Sem chaves de API",
|
||||
"apiKey-expired": "Expirada",
|
||||
"apiKey-inactive": "Inativa",
|
||||
"Expires": "Expira",
|
||||
"disableAPIKeyMsg": "Você tem certeza de que quer desativar essa chave de API?",
|
||||
"smtp": "Email (SMTP)",
|
||||
"secureOptionTLS": "TLS (465)",
|
||||
"From Email": "Email De",
|
||||
"smtpCC": "CC",
|
||||
"smtpBCC": "CCO",
|
||||
"To Email": "Email Para",
|
||||
"Recipients": "Destinatários",
|
||||
"Google Analytics ID": "ID Google Analytics",
|
||||
"Post": "Post",
|
||||
"Slug": "Slug",
|
||||
"The slug is already taken. Please choose another slug.": "Esse slug já foi utilizado. Por favor escolha outro slug.",
|
||||
"Setup Docker Host": "Configurar Host Docker",
|
||||
"trustProxyDescription": "Confiar nos cabeçalhos 'X-Forwarded-*'. Se você quer obter o endereço IP do cliente correto no seu Uptime Kuma que está por trás de um proxy como Nginx ou Apache, você deve ativar isso.",
|
||||
"Automations can optionally be triggered in Home Assistant:": "Automações podem opcionalmente ser disparadas no Home Assistant:",
|
||||
"secureOptionNone": "Nenhum / STARTTLS (25, 587)",
|
||||
"apiKeyAddedMsg": "Sua chave de API foi adicionada. Por favor anote essa chave, ela não será mostrada novamente.",
|
||||
"Show Clickable Link": "Mostrar Link Clicável"
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@
|
||||
"acceptedStatusCodesDescription": "Выберите коды статусов для определения доступности сервиса.",
|
||||
"passwordNotMatchMsg": "Повтор пароля не совпадает.",
|
||||
"notificationDescription": "Привяжите уведомления к мониторам.",
|
||||
"keywordDescription": "Поиск слова в чистом HTML или в JSON-ответе (чувствительно к регистру)",
|
||||
"keywordDescription": "Поиск слова в чистом HTML или в JSON-ответе (чувствительно к регистру).",
|
||||
"pauseDashboardHome": "Пауза",
|
||||
"deleteMonitorMsg": "Вы действительно хотите удалить данный монитор?",
|
||||
"deleteNotificationMsg": "Вы действительно хотите удалить это уведомление для всех мониторов?",
|
||||
@@ -45,9 +45,9 @@
|
||||
"Uptime": "Аптайм",
|
||||
"Cert Exp.": "Сертификат истекает.",
|
||||
"day": "день | дней",
|
||||
"-day": " дней",
|
||||
"-day": "-дней",
|
||||
"hour": "час",
|
||||
"-hour": " часа",
|
||||
"-hour": "-часа",
|
||||
"Response": "Ответ",
|
||||
"Ping": "Пинг",
|
||||
"Monitor Type": "Тип монитора",
|
||||
@@ -124,12 +124,12 @@
|
||||
"Also apply to existing monitors": "Применить к существующим мониторам",
|
||||
"Export": "Экспорт",
|
||||
"Import": "Импорт",
|
||||
"backupDescription": "Вы можете сохранить резервную копию всех мониторов и уведомлений в виде JSON-файла",
|
||||
"backupDescription2": "P.S. История и события сохранены не будут",
|
||||
"backupDescription3": "Важные данные, такие как токены уведомлений, добавляются при экспорте, поэтому храните файлы в безопасном месте",
|
||||
"backupDescription": "Вы можете сохранить резервную копию всех мониторов и уведомлений в виде JSON-файла.",
|
||||
"backupDescription2": "Важно: история и события сохранены не будут.",
|
||||
"backupDescription3": "Важные данные, такие как токены уведомлений, добавляются при экспорте, поэтому храните файлы в безопасном месте.",
|
||||
"alertNoFile": "Выберите файл для импорта.",
|
||||
"alertWrongFileType": "Выберите JSON-файл.",
|
||||
"twoFAVerifyLabel": "Пожалуйста, введите свой токен, чтобы проверить работу 2FA",
|
||||
"twoFAVerifyLabel": "Пожалуйста, введите свой токен, чтобы проверить работу 2FA:",
|
||||
"tokenValidSettingsMsg": "Токен действителен! Теперь вы можете сохранить настройки 2FA.",
|
||||
"confirmEnableTwoFAMsg": "Вы действительно хотите включить 2FA?",
|
||||
"confirmDisableTwoFAMsg": "Вы действительно хотите выключить 2FA?",
|
||||
@@ -441,14 +441,14 @@
|
||||
"Accept characters:": "Принимаемые символы:",
|
||||
"startOrEndWithOnly": "Начинается или кончается только {0}",
|
||||
"No consecutive dashes": "Без последовательных тире",
|
||||
"The slug is already taken. Please choose another slug.": "The slug is already taken. Please choose another slug.",
|
||||
"The slug is already taken. Please choose another slug.": "Слово уже занято. Пожалуйста, выберите другой вариант.",
|
||||
"Page Not Found": "Страница не найдена",
|
||||
"wayToGetCloudflaredURL": "(Скачать cloudflared с {0})",
|
||||
"cloudflareWebsite": "Cloudflare Website",
|
||||
"cloudflareWebsite": "Веб-сайт Cloudflare",
|
||||
"Message:": "Сообщение:",
|
||||
"Don't know how to get the token? Please read the guide:": "Don't know how to get the token? Please read the guide:",
|
||||
"The current connection may be lost if you are currently connecting via Cloudflare Tunnel. Are you sure want to stop it? Type your current password to confirm it.": "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.",
|
||||
"HTTP Headers": "HTTP заголовки",
|
||||
"Don't know how to get the token? Please read the guide:": "Не знаете, как получить токен? Пожалуйста, прочтите руководство:",
|
||||
"The current connection may be lost if you are currently connecting via Cloudflare Tunnel. Are you sure want to stop it? Type your current password to confirm it.": "Текущее соединение может быть потеряно, если вы в данный момент подключаетесь через туннель Cloudflare. Вы уверены, что хотите это остановить? Введите свой текущий пароль, чтобы подтвердить это.",
|
||||
"HTTP Headers": "заголовки HTTP",
|
||||
"Trust Proxy": "Доверять прокси",
|
||||
"Other Software": "Другое программное обеспечение",
|
||||
"For example: nginx, Apache and Traefik.": "К примеру: nginx, Apache и Traefik.",
|
||||
@@ -463,13 +463,13 @@
|
||||
"Proxy": "Прокси",
|
||||
"Date Created": "Дата создания",
|
||||
"HomeAssistant": "Home Assistant",
|
||||
"onebotHttpAddress": "OneBot HTTP Address",
|
||||
"onebotMessageType": "OneBot Message Type",
|
||||
"onebotHttpAddress": "HTTP-адрес OneBot",
|
||||
"onebotMessageType": "Тип сообщения OneBot",
|
||||
"onebotGroupMessage": "Группа",
|
||||
"onebotPrivateMessage": "Private",
|
||||
"onebotUserOrGroupId": "ID группы или пользователя",
|
||||
"onebotSafetyTips": "В целях безопасности необходимо установить токен доступа",
|
||||
"PushDeer Key": "PushDeer Key",
|
||||
"PushDeer Key": "ключ PushDeer",
|
||||
"Footer Text": "Текст нижнего колонтитула",
|
||||
"Show Powered By": "Показывать на чем создано",
|
||||
"Domain Names": "Доменные имена",
|
||||
@@ -488,40 +488,40 @@
|
||||
"From Name/Number": "Имя/номер отправителя",
|
||||
"Leave blank to use a shared sender number.": "Оставьте пустым, чтобы использовать общий номер отправителя.",
|
||||
"Octopush API Version": "Версия API Octopush",
|
||||
"Legacy Octopush-DM": "Legacy Octopush-DM",
|
||||
"endpoint": "endpoint",
|
||||
"Legacy Octopush-DM": "устаревший Octopush-DM",
|
||||
"endpoint": "конечная точка",
|
||||
"octopushAPIKey": "\"API key\" из учетных данных HTTP API в панели управления",
|
||||
"octopushLogin": "\"Login\" из учетных данных HTTP API в панели управления",
|
||||
"promosmsLogin": "Логин API",
|
||||
"promosmsPassword": "Пароль API",
|
||||
"pushoversounds pushover": "Pushover (default)",
|
||||
"pushoversounds bike": "Bike",
|
||||
"pushoversounds bugle": "Bugle",
|
||||
"pushoversounds cashregister": "Cash Register",
|
||||
"pushoversounds pushover": "Pushover (по умолчанию)",
|
||||
"pushoversounds bike": "Велосипед",
|
||||
"pushoversounds bugle": "Горн",
|
||||
"pushoversounds cashregister": "Кассовый аппарат",
|
||||
"pushoversounds classical": "Classical",
|
||||
"pushoversounds cosmic": "Cosmic",
|
||||
"pushoversounds falling": "Falling",
|
||||
"pushoversounds gamelan": "Gamelan",
|
||||
"pushoversounds incoming": "Incoming",
|
||||
"pushoversounds intermission": "Intermission",
|
||||
"pushoversounds magic": "Magic",
|
||||
"pushoversounds mechanical": "Mechanical",
|
||||
"pushoversounds pianobar": "Piano Bar",
|
||||
"pushoversounds siren": "Siren",
|
||||
"pushoversounds spacealarm": "Space Alarm",
|
||||
"pushoversounds tugboat": "Tug Boat",
|
||||
"pushoversounds alien": "Alien Alarm (long)",
|
||||
"pushoversounds climb": "Climb (long)",
|
||||
"pushoversounds persistent": "Persistent (long)",
|
||||
"pushoversounds echo": "Pushover Echo (long)",
|
||||
"pushoversounds updown": "Up Down (long)",
|
||||
"pushoversounds vibrate": "Vibrate Only",
|
||||
"pushoversounds none": "None (silent)",
|
||||
"pushyAPIKey": "Secret API Key",
|
||||
"pushoversounds cosmic": "Космический",
|
||||
"pushoversounds falling": "Падающий",
|
||||
"pushoversounds gamelan": "Гамелан",
|
||||
"pushoversounds incoming": "Входящий",
|
||||
"pushoversounds intermission": "Антракт",
|
||||
"pushoversounds magic": "Магия",
|
||||
"pushoversounds mechanical": "Механический",
|
||||
"pushoversounds pianobar": "Пиано-бар",
|
||||
"pushoversounds siren": "Сирена",
|
||||
"pushoversounds spacealarm": "Космическая сигнализация",
|
||||
"pushoversounds tugboat": "Буксирное судно",
|
||||
"pushoversounds alien": "Инопланетная тревога (долгое)",
|
||||
"pushoversounds climb": "Подъем (долгое)",
|
||||
"pushoversounds persistent": "Стойкий (долгое)",
|
||||
"pushoversounds echo": "Pushover Эхо (долгое)",
|
||||
"pushoversounds updown": "Вверх вниз (долгое)",
|
||||
"pushoversounds vibrate": "Только вибрация",
|
||||
"pushoversounds none": "Нет (тихо)",
|
||||
"pushyAPIKey": "Секретный ключ API",
|
||||
"pushyToken": "Токен устройства",
|
||||
"Using a Reverse Proxy?": "Используете обратный прокси?",
|
||||
"Check how to config it for WebSocket": "Проверьте, как настроить его для WebSocket",
|
||||
"Steam Game Server": "Steam Game Server",
|
||||
"Steam Game Server": "Игровой сервер Steam",
|
||||
"Most likely causes:": "Наиболее вероятные причины:",
|
||||
"The resource is no longer available.": "Ресурс больше не доступен.",
|
||||
"There might be a typing error in the address.": "В адресе может быть опечатка.",
|
||||
@@ -536,24 +536,24 @@
|
||||
"certificationExpiryDescription": "HTTPS Мониторы инициируют уведомление, когда срок действия сертификата TLS истечет:",
|
||||
"Setup Docker Host": "Настроить Docker Host",
|
||||
"Connection Type": "Тип соединения",
|
||||
"Docker Daemon": "Docker Daemon",
|
||||
"deleteDockerHostMsg": "Are you sure want to delete this docker host for all monitors?",
|
||||
"socket": "Socket",
|
||||
"Docker Daemon": "Демон Docker",
|
||||
"deleteDockerHostMsg": "Вы уверены, что хотите удалить этот узел docker для всех мониторов?",
|
||||
"socket": "Сокет",
|
||||
"tcp": "TCP / HTTP",
|
||||
"Docker Container": "Docker контейнер",
|
||||
"Container Name / ID": "Название контейнера / ID",
|
||||
"Docker Host": "Docker Host",
|
||||
"Docker Hosts": "Docker Hosts",
|
||||
"ntfy Topic": "ntfy Topic",
|
||||
"Docker Host": "Хост Docker",
|
||||
"Docker Hosts": "Хосты Docker",
|
||||
"ntfy Topic": "тема ntfy",
|
||||
"Domain": "Домен",
|
||||
"Workstation": "Workstation",
|
||||
"Workstation": "Рабочая станция",
|
||||
"disableCloudflaredNoAuthMsg": "Вы находитесь в режиме без авторизации, пароль не требуется.",
|
||||
"trustProxyDescription": "Доверять заголовкам 'X-Forwarded-*'. Если вы хотите получить правильный IP-адрес клиента, а ваш Uptime Kuma находится под Nginx или Apache, вам следует включить этот параметр.",
|
||||
"wayToGetLineNotifyToken": "Вы можете получить токен доступа в {0}",
|
||||
"Examples": "Примеры",
|
||||
"Home Assistant URL": "Home Assistant URL",
|
||||
"Home Assistant URL": "URL-адрес Home Assistant",
|
||||
"Long-Lived 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. ": "Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create 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. ": "Токен доступа с длительным сроком действия можно создать, нажав на имя вашего профиля (внизу слева) и прокрутив его вниз, затем нажмите Создать токен. ",
|
||||
"Notification Service": "Служба уведомлений",
|
||||
"default: notify all devices": "по стандарту: уведомлять все устройства",
|
||||
"A list of Notification Services can be found in Home Assistant under \"Developer Tools > Services\" search for \"notification\" to find your device/phone name.": "Список служб уведомлений можно найти в Home Assistant в разделе \"Инструменты разработчика > Службы\", выполнив поиск по слову \"уведомление\", чтобы найти название вашего устройства/телефона.",
|
||||
@@ -565,7 +565,7 @@
|
||||
"Frontend Version": "Версия интерфейса",
|
||||
"Frontend Version do not match backend version!": "Версия интерфейса не соответствует версии серверной части!",
|
||||
"Base URL": "Базовый URL",
|
||||
"goAlertInfo": "GoAlert is a An open source application for on-call scheduling, automated escalations and notifications (like SMS or voice calls). Automatically engage the right person, the right way, and at the right time! {0}",
|
||||
"goAlertInfo": "GoAlert — это приложение с открытым исходным кодом для составления расписания вызовов, автоматической эскалации и уведомлений (например, SMS или голосовых звонков). Автоматически привлекайте нужного человека, нужным способом и в нужное время! {0}",
|
||||
"goAlertIntegrationKeyInfo": "Получить общий ключ интеграции API для сервиса в этом формате \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\" обычно значение параметра токена скопированного URL.",
|
||||
"goAlert": "GoAlert",
|
||||
"backupOutdatedWarning": "Устарело: поскольку добавлено множество функций, а эта функция резервного копирования немного не поддерживается, она не может создать или восстановить полную резервную копию.",
|
||||
@@ -618,7 +618,7 @@
|
||||
"Custom CSS": "Пользовательские CSS",
|
||||
"weekdayShortTue": "Вт",
|
||||
"dayOfWeek": "День недели",
|
||||
"confirmDeleteTagMsg": "Вы уверены, что хотите удалить этот тег? Мониторы, связанные с этим тегом не будут удалены.",
|
||||
"confirmDeleteTagMsg": "Вы уверены, что хотите удалить этот тег? Мониторы, связанные с этим тегом не будут удалены.",
|
||||
"loadingError": "Невозможно получить данные, пожалуйста попробуйте позже.",
|
||||
"Packet Size": "Размер пакета",
|
||||
"warningTimezone": "Используется часовой пояс сервера",
|
||||
@@ -669,17 +669,17 @@
|
||||
"smseagle": "SMSEagle",
|
||||
"Google Analytics ID": "ID Google Аналитики",
|
||||
"wayToGetZohoCliqURL": "Вы можете узнать как создать webhook URL тут {0}.",
|
||||
"Effective Date Range": "Даты действия",
|
||||
"Effective Date Range": "Даты действия (Опционально)",
|
||||
"wayToGetKookGuildID": "Включите \"Режим разработчика\" в настройках Kook, а затем нажмите правой кнопкой по гильдии чтобы скопировать её ID",
|
||||
"Enable TLS": "Включить TLS",
|
||||
"Integration Key": "Ключ интеграции",
|
||||
"Integration URL": "URL интеграции",
|
||||
"do nothing": "ничего не делать",
|
||||
"smseagleTo": "Номер(а) телефона",
|
||||
"smseagleGroup": "Имена групп в телефонной книжке",
|
||||
"smseagleGroup": "Название(я) групп телефонной книги",
|
||||
"smseagleContact": "Имена контактов из телефонной книжки",
|
||||
"smseagleRecipientType": "Тип получателя",
|
||||
"smseagleRecipient": "Получатель (через запятую, если несколько)",
|
||||
"smseagleRecipient": "Получатель(я) (через запятую, если необходимо указать несколько)",
|
||||
"smseagleToken": "Токен доступа API",
|
||||
"smseagleUrl": "URL вашего SMSEagle устройства",
|
||||
"smseagleEncoding": "Отправить в юникоде",
|
||||
@@ -687,9 +687,9 @@
|
||||
"Server Address": "Адрес сервера",
|
||||
"Learn More": "Узнать больше",
|
||||
"topicExplanation": "MQTT топик для мониторинга",
|
||||
"Guild ID": "Guild ID",
|
||||
"Guild ID": "Идентификатор гильдии",
|
||||
"Kook": "Kook",
|
||||
"wayToGetKookBotToken": "Создайте приложение и получите токен вашего бота тут {0}.",
|
||||
"wayToGetKookBotToken": "Создайте приложение и получите токен бота по адресу {0}",
|
||||
"Resend Notification if Down X times consecutively": "Повторная отправка уведомления при падении несколько раз",
|
||||
"telegramProtectContent": "Запретить пересылку/сохранение",
|
||||
"telegramProtectContentDescription": "Если включено, сообщения бота в Telegram будут запрещены для пересылки и сохранения.",
|
||||
@@ -700,5 +700,63 @@
|
||||
"Clone": "Копия",
|
||||
"cloneOf": "Копия {0}",
|
||||
"notificationRegional": "Региональный",
|
||||
"Add New Tag": "Добавить тег"
|
||||
"Add New Tag": "Добавить тег",
|
||||
"Body Encoding": "Тип содержимого запроса.(JSON or XML)",
|
||||
"Strategy": "Стратегия",
|
||||
"Free Mobile User Identifier": "Бесплатный идентификатор мобильного пользователя",
|
||||
"Auto resolve or acknowledged": "Автоматическое разрешение или подтверждение",
|
||||
"auto acknowledged": "автоматическое подтверждение",
|
||||
"auto resolve": "автоматическое разрешение",
|
||||
"API Keys": "Ключи API",
|
||||
"Expiry": "Истекает",
|
||||
"Expiry date": "Дата окончания действия",
|
||||
"Don't expire": "Не истекает",
|
||||
"Continue": "Продолжать",
|
||||
"Add Another": "Добавьте еще один",
|
||||
"Key Added": "Ключ добавлен",
|
||||
"Add API Key": "Добавить ключ API",
|
||||
"No API Keys": "Нет API ключей",
|
||||
"apiKey-active": "Активный",
|
||||
"apiKey-expired": "Истёк",
|
||||
"apiKey-inactive": "Неактивный",
|
||||
"Expires": "Истекает",
|
||||
"disableAPIKeyMsg": "Вы уверены, что хотите отключить этот ключ?",
|
||||
"Generate": "Сгенерировать",
|
||||
"pagertreeResolve": "Автоматическое разрешение",
|
||||
"pagertreeDoNothing": "ничего не делать",
|
||||
"lunaseaTarget": "Цель",
|
||||
"lunaseaDeviceID": "Идентификатор устройства",
|
||||
"lunaseaUserID": "Идентификатор пользователя",
|
||||
"Lowcost": "Низкая стоимость",
|
||||
"pagertreeIntegrationUrl": "URL-адрес интеграции",
|
||||
"pagertreeUrgency": "Срочность",
|
||||
"pagertreeSilent": "Тихий",
|
||||
"pagertreeLow": "Низкий",
|
||||
"pagertreeMedium": "Средний",
|
||||
"pagertreeHigh": "Высокий",
|
||||
"pagertreeCritical": "Критический",
|
||||
"high": "высокий",
|
||||
"promosmsAllowLongSMS": "Разрешить длинные SMS-сообщения",
|
||||
"Economy": "Экономия",
|
||||
"wayToGetPagerDutyKey": "Вы можете получить это, перейдя в службу -> Каталог служб -> (Выберите службу) -> Интеграции -> Добавить интеграцию. Здесь вы можете выполнить поиск по \"Events API V2\". Дополнительная информация {0}",
|
||||
"apiKeyAddedMsg": "Ваш API ключ был добавлен. Пожалуйста, запишите это, так как оно больше не будет показан.",
|
||||
"deleteAPIKeyMsg": "Вы уверены, что хотите удалить этот ключ API?",
|
||||
"wayToGetPagerTreeIntegrationURL": "После создания интеграции Uptime Kuma в PagerTree, скопируйте конечную точку. Смотрите полную информацию {0}",
|
||||
"telegramMessageThreadIDDescription": "Необязательный уникальный идентификатор для цепочки сообщений (темы) форума; только для форумов-супергрупп",
|
||||
"grpcMethodDescription": "Название метода - преобразовать в формат cammelCase, такой как sayHello, check и т.д.",
|
||||
"Proto Service Name": "название службы Proto",
|
||||
"Proto Method": "Метод Proto",
|
||||
"Proto Content": "Содержание Proto",
|
||||
"telegramMessageThreadID": "(Необязательно) ID цепочки сообщений",
|
||||
"statusPageRefreshIn": "Обновлять каждые: {0}",
|
||||
"twilioAccountSID": "SID учетной записи",
|
||||
"twilioAuthToken": "Токен авторизации",
|
||||
"twilioFromNumber": "С номера",
|
||||
"twilioToNumber": "На номер",
|
||||
"sameAsServerTimezone": "Аналогично часовому поясу сервера",
|
||||
"startDateTime": "Начальная дата и время",
|
||||
"endDateTime": "Конечная дата и время",
|
||||
"cronExpression": "Выражение для Cron",
|
||||
"cronSchedule": "Расписание: ",
|
||||
"invalidCronExpression": "Неверное выражение Cron: {0}"
|
||||
}
|
||||
|
124
src/lang/sk.json
124
src/lang/sk.json
@@ -64,7 +64,7 @@
|
||||
"Up": "Dostupné",
|
||||
"Down": "Nedostupné",
|
||||
"Unknown": "Neznáme",
|
||||
"markdownSupported": "Podpora Markdown syntaxe",
|
||||
"markdownSupported": "Podpora Markdown syntaxu",
|
||||
"Name": "Názov",
|
||||
"DateTime": "Dátum a čas",
|
||||
"Resume": "Pokračovať",
|
||||
@@ -81,5 +81,125 @@
|
||||
"maxRedirectDescription": "Maximálny počet presmerovaní. Hodnota 0 vypne presmerovania.",
|
||||
"needPushEvery": "Tuto adresu by ste mali volať každých {0} sekúnd.",
|
||||
"pushOptionalParams": "Voliteľné parametre: {0}",
|
||||
"Theme - Heartbeat Bar": "Téma - Heartbeat riadok"
|
||||
"Theme - Heartbeat Bar": "Téma - Heartbeat riadok",
|
||||
"Game": "Hra",
|
||||
"Search Engine Visibility": "Viditeľnosť vyhľadávačmi",
|
||||
"Allow indexing": "Povoliť indexovanie",
|
||||
"Change Password": "Zmeniť heslo",
|
||||
"Current Password": "Aktuálne heslo",
|
||||
"New Password": "Nové heslo",
|
||||
"Repeat New Password": "Zopakovať nové heslo",
|
||||
"Update Password": "Aktualizovať heslo",
|
||||
"Disable Auth": "Vypnúť autentifikáciu",
|
||||
"Enable Auth": "Zapnúť autentifikáciu",
|
||||
"Please use this option carefully!": "Túto možnosť používajte opatrne!",
|
||||
"Logout": "Odhlásiť sa",
|
||||
"Leave": "Odísť",
|
||||
"I understand, please disable": "Rozumiem, vypnite to",
|
||||
"Yes": "Áno",
|
||||
"No": "Nie",
|
||||
"Username": "Používateľské meno",
|
||||
"Password": "Heslo",
|
||||
"Login": "Prihlásiť sa",
|
||||
"No Monitors, please": "Žiadne sledovanie, prosím",
|
||||
"add one": "pridať jeden",
|
||||
"Notification Type": "Typ notifikácie",
|
||||
"Email": "E-mail",
|
||||
"Test": "Test",
|
||||
"Certificate Info": "Informácie o certifikáte",
|
||||
"Resolver Server": "DNS server",
|
||||
"Last Result": "Posledný výsledok",
|
||||
"Repeat Password": "Zopakovať heslo",
|
||||
"Import Backup": "Importovať zálohu",
|
||||
"Export Backup": "Exportovať zálohu",
|
||||
"Export": "Exportovať",
|
||||
"Import": "Importovať",
|
||||
"respTime": "Čas odozvy (ms)",
|
||||
"notAvailableShort": "Nie je číslo",
|
||||
"Default enabled": "Predvolene povolené",
|
||||
"Create": "Vytvoriť",
|
||||
"Clear Data": "Vyčistiť dáta",
|
||||
"Events": "Udalosti",
|
||||
"Heartbeats": "Odpovede",
|
||||
"Auto Get": "Získať automaticky",
|
||||
"Schedule maintenance": "Naplánovať údržbu",
|
||||
"Affected Monitors": "Dotknuté sledovania",
|
||||
"Pick Affected Monitors...": "Vybrať dotknuté sledovania…",
|
||||
"Start of maintenance": "Začiatok údržby",
|
||||
"All Status Pages": "Všetky stavové stránky",
|
||||
"Select status pages...": "Vybrať stránky stavu…",
|
||||
"alertNoFile": "Vyberte súbor na import.",
|
||||
"alertWrongFileType": "Vyberte súbor JSON.",
|
||||
"Clear all statistics": "Vymazať všetky štatistiky",
|
||||
"Skip existing": "Preskočiť existujúce",
|
||||
"Overwrite": "Prepísať",
|
||||
"Options": "Možnosti",
|
||||
"Keep both": "Ponechať obe",
|
||||
"Setup 2FA": "Nastavenie 2FA",
|
||||
"Disable 2FA": "Zakázať 2FA",
|
||||
"2FA Settings": "Nastavenia 2FA",
|
||||
"Two Factor Authentication": "Dvojfaktorová autentifikácia",
|
||||
"Inactive": "Neaktívne",
|
||||
"Token": "Token",
|
||||
"Show URI": "Zobraziť URI",
|
||||
"Tags": "Značky",
|
||||
"Add New below or Select...": "Pridať novú nižšie alebo vybrať…",
|
||||
"Tag with this value already exist.": "Značka s touto hodnotou už existuje.",
|
||||
"color": "Farba",
|
||||
"value (optional)": "hodnota (voliteľné)",
|
||||
"Gray": "Šedá",
|
||||
"Red": "Červená",
|
||||
"Orange": "Oranžová",
|
||||
"Green": "Zelená",
|
||||
"Indigo": "Indigo",
|
||||
"Purple": "Fialová",
|
||||
"Pink": "Ružová",
|
||||
"Custom": "Vlastná",
|
||||
"Avg. Ping": "Priemerný ping",
|
||||
"Avg. Response": "Priemerný čas odpovede",
|
||||
"Entry Page": "Vstupná stránka",
|
||||
"No Services": "Žiadne služby",
|
||||
"All Systems Operational": "Všetky systémy funkčné",
|
||||
"Partially Degraded Service": "Čiastočne zhoršená služba",
|
||||
"Degraded Service": "Degradovaná služba",
|
||||
"Add Group": "Pridať skupinu",
|
||||
"Add a monitor": "Pridať sledovanie",
|
||||
"Edit Status Page": "Upraviť stavovú stránku",
|
||||
"Go to Dashboard": "Prejdite na informačný panel",
|
||||
"Status Page": "Stavová stránka",
|
||||
"Status Pages": "Stavové stránky",
|
||||
"defaultNotificationName": "Moje {notification} upozornenie ({number})",
|
||||
"here": "tu",
|
||||
"Required": "Povinné",
|
||||
"Post URL": "Post URL",
|
||||
"Content Type": "Druh obsahu",
|
||||
"webhookJsonDesc": "{0} je vhodný pre všetky moderné servery HTTP, ako napríklad Express.js",
|
||||
"webhookFormDataDesc": "{multipart} je dobré pre PHP. JSON bude potrebné analyzovať pomocou {decodeFunction}",
|
||||
"Generate": "Generovať",
|
||||
"Discourage search engines from indexing site": "Odradiť vyhľadávacie nástroje od indexovania stránky",
|
||||
"disableauth.message1": "Ste si istý, že chcete <strong>vypnúť autentifikáciu</strong>?",
|
||||
"disableauth.message2": "Je navrhnutý pre scenáre, <strong>kde máte v úmysle implementovať autentifikáciu treťou stranou</strong> pred Uptime Kuma, ako je Cloudflare Access, Authelia alebo iné autentifikačné mechanizmy.",
|
||||
"Confirm": "Potvrdiť",
|
||||
"Remember me": "Zapamätať si ma",
|
||||
"Resource Record Type": "Typ záznamu",
|
||||
"Create your admin account": "Vytvorte si účet administrátora",
|
||||
"Apply on all existing monitors": "Aplikujte na všetky existujúce sledovania",
|
||||
"Verify Token": "Overiť token",
|
||||
"Enable 2FA": "Povoliť 2FA",
|
||||
"Active": "Aktívne",
|
||||
"Add New Tag": "Pridať novú značku",
|
||||
"Tag with this name already exist.": "Značka s týmto názvom už existuje.",
|
||||
"Blue": "Modrá",
|
||||
"Search...": "Hľadať…",
|
||||
"statusPageNothing": "Nič tu nie je, pridajte skupinu alebo sledovanie.",
|
||||
"webhookAdditionalHeadersTitle": "Ďalšie položky",
|
||||
"webhookAdditionalHeadersDesc": "Nastaví ďalšie hlavičky odoslané s webovým hákom.",
|
||||
"Webhook URL": "Webhook URL",
|
||||
"Application Token": "Token aplikácie",
|
||||
"Server URL": "Server URL",
|
||||
"Priority": "Priorita",
|
||||
"statusPageRefreshIn": "Obnovenie za: {0}",
|
||||
"emojiCheatSheet": "Emotikony: {0}",
|
||||
"Read more": "Prečítajte si viac",
|
||||
"appriseInstalled": "Apprise je nainštalovaný."
|
||||
}
|
||||
|
@@ -105,5 +105,37 @@
|
||||
"Last Result": "Senaste resultat",
|
||||
"Create your admin account": "Skapa ditt administratörskonto",
|
||||
"Repeat Password": "Upprepa Lösenord",
|
||||
"respTime": "Svarstid (ms)"
|
||||
"respTime": "Svarstid (ms)",
|
||||
"Specific Monitor Type": "Applikationsspecifika övervakare",
|
||||
"Push URL": "Push URL",
|
||||
"Passive Monitor Type": "Passiva övervakare",
|
||||
"markdownSupported": "Stödjer markdown-syntax",
|
||||
"Heartbeat Retry Interval": "Omprövningsintervall",
|
||||
"needPushEvery": "Hämta denna URL var {0} sekund",
|
||||
"pushOptionalParams": "Valfria parametrar: {0}",
|
||||
"disableauth.message1": "Vill du verkligen <strong>avaktivera autentisering</strong>?",
|
||||
"disableauth.message2": "Det är designat för när en <strong>tredjeparts autentiseringstjänst</strong> såsom Cloudflare Access eller Authelia används framför Uptime Kuma.",
|
||||
"Please use this option carefully!": "Använd denna funktion varsamt!",
|
||||
"Import Backup": "Importera backup",
|
||||
"Affected Monitors": "Påverkade övervakare",
|
||||
"Start of maintenance": "Påbörja underhåll",
|
||||
"All Status Pages": "Alla statussidor",
|
||||
"alertNoFile": "Välj en fil att importera.",
|
||||
"alertWrongFileType": "Välj en JSON-formatterad fil.",
|
||||
"Help": "Hjälp",
|
||||
"Export": "Export",
|
||||
"Import": "Import",
|
||||
"Game": "Spel",
|
||||
"resendEveryXTimes": "Omsänd efter {0} gånger",
|
||||
"Export Backup": "Exportera backup",
|
||||
"Schedule maintenance": "Schemalägg underhåll",
|
||||
"Monitor": "Övervakare | Övervakare",
|
||||
"Resend Notification if Down X times consecutively": "Sänd notis igen om nere X gånger i rad",
|
||||
"Maintenance": "Underhåll",
|
||||
"retryCheckEverySecond": "Ompröva var {0} sekund",
|
||||
"statusMaintenance": "Underhåll",
|
||||
"resendDisabled": "Omsändning inaktiverat",
|
||||
"Pick Affected Monitors...": "Välj påverkade övervakare…",
|
||||
"Select status pages...": "Välj statussidor…",
|
||||
"General Monitor Type": "Allmänna övervakare"
|
||||
}
|
||||
|
@@ -605,5 +605,52 @@
|
||||
"pagertreeCritical": "วิกฤต",
|
||||
"pagertreeDoNothing": "ไม่ต้องทำอะไร",
|
||||
"pagertreeResolve": "แก้ไขอัตโนมัติ",
|
||||
"wayToGetPagerTreeIntegrationURL": "หลังจากสร้างการรวม Uptime Kuma ใน PagerTree แล้ว ให้คัดลอก Endpoint, ดูรายละเอียดทั้งหมด {0}"
|
||||
"wayToGetPagerTreeIntegrationURL": "หลังจากสร้างการรวม Uptime Kuma ใน PagerTree แล้ว ให้คัดลอก Endpoint, ดูรายละเอียดทั้งหมด {0}",
|
||||
"telegramSendSilently": "ส่งอย่างเงียบ ๆ",
|
||||
"maintenanceStatus-inactive": "ไม่ใช้งาน",
|
||||
"telegramProtectContent": "ป้องกันการส่งต่อ/บันทึก",
|
||||
"Add New Tag": "เพิ่มแท็กใหม่",
|
||||
"strategyManual": "ตั่งให้ใช้งาน/ไม่ใช้งานด้วยตนเอง",
|
||||
"warningTimezone": "ใช้เขตเวลาของเซิร์ฟเวอร์",
|
||||
"weekdayShortMon": "จันทร์",
|
||||
"weekdayShortTue": "วันอังคาร",
|
||||
"weekdayShortWed": "พุธ",
|
||||
"weekdayShortThu": "พฤหัสบดี",
|
||||
"weekdayShortFri": "ศุกร์",
|
||||
"weekdayShortSat": "เสาร์",
|
||||
"weekdayShortSun": "อาทิตย์",
|
||||
"dayOfWeek": "วันในสัปดาห์",
|
||||
"dayOfMonth": "วันในเดือน",
|
||||
"maintenanceStatus-under-maintenance": "อยู่ภายใต้การบำรุงรักษา",
|
||||
"maintenanceStatus-scheduled": "กำหนดการ",
|
||||
"maintenanceStatus-ended": "สิ้นสุด",
|
||||
"maintenanceStatus-unknown": "ไม่ทราบ",
|
||||
"Specific Monitor Type": "ประเภทมอนิเตอร์เฉพาะ",
|
||||
"telegramMessageThreadID": "(ตัวเลือก) ไอดีเทรดข้อความ",
|
||||
"telegramMessageThreadIDDescription": "ตัวระบุที่ไม่ซ้ำซึ่งเป็นทางเลือกสำหรับเธรดข้อความเป้าหมาย (หัวข้อ) ของฟอรัม สำหรับฟอรัมซูเปอร์กรุ๊ปเท่านั้น",
|
||||
"sameAsServerTimezone": "เช่นเดียวกับเขตเวลาของเซิร์ฟเวอร์",
|
||||
"startDateTime": "วันที่/เวลาเริ่มต้น",
|
||||
"endDateTime": "วันที่/เวลาสิ้นสุด",
|
||||
"cronSchedule": "กำหนดการ: ",
|
||||
"invalidCronExpression": "นิพจน์ Cron ไม่ถูกต้อง: {0}",
|
||||
"cronExpression": "นิพจน์ Cron",
|
||||
"lastDay": "วันสุดท้าย",
|
||||
"lastDay1": "วันสุดท้ายของเดือน",
|
||||
"lastDay2": "วันที่ 2 สุดท้ายของเดือน",
|
||||
"lastDay3": "วันที่ 3 สุดท้ายของเดือน",
|
||||
"lastDay4": "วันที่ 4 สุดท้ายของเดือน",
|
||||
"No Maintenance": "ไม่มีการบำรุงรักษา",
|
||||
"pauseMaintenanceMsg": "แน่ใจไหมว่าต้องการหยุดชั่วคราว",
|
||||
"Display Timezone": "แสดงเขตเวลา",
|
||||
"statusPageMaintenanceEndDate": "จบ",
|
||||
"Server Timezone": "เขตเวลาเซิร์ฟเวอร์",
|
||||
"statusPageRefreshIn": "รีโหลดใน: {0}",
|
||||
"telegramSendSilentlyDescription": "ส่งข้อความอย่างเงียบๆ ผู้ใช้จะได้รับการแจ้งเตือนโดยไม่มีเสียง",
|
||||
"telegramProtectContentDescription": "หากเปิดใช้งาน ข้อความบอทใน Telegram จะได้รับการปกป้องจากการส่งต่อและการบันทึก",
|
||||
"dnsCacheDescription": "อาจจะทำงานไม่ได้กับ IPv6, ปิดใช้งานถ้าเจอปัญหา",
|
||||
"IconUrl": "URL ไอคอน",
|
||||
"Enable DNS Cache": "เปิดใช้งาน DNS Cache",
|
||||
"Enable": "เปิดใช้งาน",
|
||||
"Disable": "ปิดใช้งาน",
|
||||
"Single Maintenance Window": "หน้าการปรับปรุงเดี่ยว"
|
||||
}
|
||||
|
@@ -58,7 +58,7 @@
|
||||
"Delete": "Sil",
|
||||
"Current": "Şu anda",
|
||||
"Uptime": "Çalışma zamanı",
|
||||
"Cert Exp.": "Sertifika Süresi",
|
||||
"Cert Exp.": "Sertifika Geç. Süresi",
|
||||
"day": "gün | günler",
|
||||
"-day": "-gün",
|
||||
"hour": "saat",
|
||||
@@ -194,7 +194,7 @@
|
||||
"here": "burada",
|
||||
"Required": "Gerekli",
|
||||
"telegram": "Telegram",
|
||||
"Bot Token": "Bot Token",
|
||||
"Bot Token": "Bot Anahtarı",
|
||||
"wayToGetTelegramToken": "{0} adresinden bir token alabilirsiniz.",
|
||||
"Chat ID": "Chat ID",
|
||||
"supportTelegramChatID": "Doğrudan Sohbet / Grup / Kanalın Sohbet Kimliğini Destekleyin",
|
||||
@@ -216,8 +216,8 @@
|
||||
"smtpCC": "CC",
|
||||
"smtpBCC": "BCC",
|
||||
"discord": "Discord",
|
||||
"Discord Webhook URL": "Discord Webhook URL",
|
||||
"wayToGetDiscordURL": "Bunu Sunucu Ayarları -> Entegrasyonlar -> Webhookları Görüntüle -> Yeni Webhook Oluştur adımını izleyerek alabilirsiniz.",
|
||||
"Discord Webhook URL": "Discord Webhook Bağlantısı",
|
||||
"wayToGetDiscordURL": "Bunu Sunucu Ayarları -> Entegrasyonlar -> Webhookları Görüntüle -> Yeni Webhook Oluştur adımını izleyerek alabilirsiniz",
|
||||
"Bot Display Name": "Botun Görünecek Adı",
|
||||
"Prefix Custom Message": "Önek Özel Mesaj",
|
||||
"Hello @everyone is...": "Merhaba {'@'}everyone…",
|
||||
@@ -262,7 +262,7 @@
|
||||
"octopushPhoneNumber": "Telefon numarası (uluslararası biçim, örneğin: +33612345678) ",
|
||||
"octopushSMSSender": "SMS Gönderici Adı : 3-11 alfanümerik karakter ve boşluk (a-zA-Z0-9)",
|
||||
"LunaSea Device ID": "LunaSea Cihaz ID",
|
||||
"Apprise URL": "Apprise URL",
|
||||
"Apprise URL": "Apprise Bağlantısı",
|
||||
"Example:": "Örnek: {0}",
|
||||
"Read more:": "Daha fazla oku: {0}",
|
||||
"Status:": "Durum: {0}",
|
||||
@@ -335,7 +335,7 @@
|
||||
"Please input title and content": "Lütfen başlık ve içerik girin",
|
||||
"Created": "Oluşturuldu",
|
||||
"Last Updated": "Son Güncelleme",
|
||||
"Unpin": "Unpin",
|
||||
"Unpin": "Sabitlemeyi Kaldır",
|
||||
"Switch to Light Theme": "Açık Temaya Geç",
|
||||
"Switch to Dark Theme": "Karanlık Temaya Geç",
|
||||
"Show Tags": "Etiketleri Göster",
|
||||
@@ -395,7 +395,7 @@
|
||||
"Valid": "Geçerli",
|
||||
"Invalid": "Geçersiz",
|
||||
"AccessKeyId": "AccessKey ID",
|
||||
"SecretAccessKey": "AccessKey Secret",
|
||||
"SecretAccessKey": "AccessKey Gizli Anahtarı",
|
||||
"PhoneNumbers": "Telefon numaraları",
|
||||
"TemplateCode": "TemplateCode",
|
||||
"SignName": "SignName",
|
||||
@@ -414,7 +414,7 @@
|
||||
"High": "High",
|
||||
"Retry": "Tekrar",
|
||||
"Topic": "Başlık",
|
||||
"WeCom Bot Key": "WeCom Bot Key",
|
||||
"WeCom Bot Key": "WeCom Bot Anahtarı",
|
||||
"Setup Proxy": "Proxy kur",
|
||||
"Proxy Protocol": "Proxy Protokolü",
|
||||
"Proxy Server": "Proxy Sunucusu",
|
||||
@@ -444,7 +444,7 @@
|
||||
"Backup": "Yedek",
|
||||
"About": "Hakkında",
|
||||
"wayToGetCloudflaredURL": "(Cloudflared'i {0} adresinden indirin)",
|
||||
"cloudflareWebsite": "Cloudflare Website",
|
||||
"cloudflareWebsite": "Cloudflare İnt. Sitesi",
|
||||
"Message:": "Mesaj:",
|
||||
"Don't know how to get the token? Please read the guide:": "Tokeni nasıl alacağınızı bilmiyor musunuz? Lütfen kılavuzu okuyun:",
|
||||
"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.": "Halihazırda Cloudflare Tüneli üzerinden bağlanıyorsanız mevcut bağlantı kesilebilir. Durdurmak istediğinden emin misin? Onaylamak için mevcut şifrenizi yazın.",
|
||||
@@ -475,7 +475,7 @@
|
||||
"Domain Names": "Alan isimleri",
|
||||
"signedInDisp": "{0} olarak oturum açıldı",
|
||||
"signedInDispDisabled": "Yetkilendirme Devre Dışı.",
|
||||
"RadiusSecret": "Radius Secret",
|
||||
"RadiusSecret": "Radius Gizli Anahtar",
|
||||
"RadiusSecretDescription": "İstemci ve sunucu arasında paylaşılan gizli anahtar",
|
||||
"RadiusCalledStationId": "Aranan İstasyon Kimliği",
|
||||
"RadiusCalledStationIdDescription": "Aranan cihazın tanımlayıcısı",
|
||||
@@ -547,13 +547,13 @@
|
||||
"Docker Host": "Docker Ana Bilgisayarı",
|
||||
"Docker Hosts": "Docker Ana Bilgisayarları",
|
||||
"ntfy Topic": "ntfy Konu",
|
||||
"Domain": "Domain",
|
||||
"Domain": "Alan Adı",
|
||||
"Workstation": "İş İstasyonu",
|
||||
"disableCloudflaredNoAuthMsg": "Yetki yok modundasınız, şifre gerekli değil.",
|
||||
"trustProxyDescription": "'X-Forwarded-*' başlıklarına güvenin. Doğru istemci IP'sini almak istiyorsanız ve Uptime Kuma'nız Nginx veya Apache gibi bir proxy'nin arkasındaysa, bunu etkinleştirmelisiniz.",
|
||||
"wayToGetLineNotifyToken": "{0} adresinden bir erişim jetonu alabilirsiniz.",
|
||||
"wayToGetLineNotifyToken": "{0} adresinden bir erişim jetonu alabilirsiniz",
|
||||
"Examples": "Örnekler",
|
||||
"Home Assistant URL": "Home Assistant URL",
|
||||
"Home Assistant URL": "Home Assistant Bağlantısı",
|
||||
"Long-Lived Access Token": "Long-Lived Erişim Anahtarı",
|
||||
"Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "Long-Lived Erişim Anahtarı, profil adınıza (sol altta) tıklayarak ve aşağıya kaydırarak ve ardından Anahtar Oluştur'a tıklayarak oluşturulabilir. ",
|
||||
"Notification Service": "Bildirim Hizmeti",
|
||||
@@ -648,7 +648,7 @@
|
||||
"dnsCacheDescription": "Bazı IPv6 ortamlarında çalışmıyor olabilir, herhangi bir sorunla karşılaşırsanız devre dışı bırakın.",
|
||||
"Single Maintenance Window": "Tek Seferlik Bakım",
|
||||
"Maintenance Time Window of a Day": "Bür Günlük Bakım",
|
||||
"Effective Date Range": "Bakim Tarih Aralığı",
|
||||
"Effective Date Range": "Geçerlilik Tarihi Aralığı (Opsiyonel)",
|
||||
"Schedule Maintenance": "Bakım Planla",
|
||||
"Date and Time": "Tarih ve Saat",
|
||||
"DateTime Range": "Tarih ve Saat Aralığı",
|
||||
@@ -738,5 +738,43 @@
|
||||
"lunaseaTarget": "Hedef",
|
||||
"Add New Tag": "Yeni Etiket Ekle",
|
||||
"lunaseaDeviceID": "Cihaz ID",
|
||||
"lunaseaUserID": "Kullanıcı ID"
|
||||
"lunaseaUserID": "Kullanıcı ID",
|
||||
"statusPageRefreshIn": "{0} içinde yenilenecek",
|
||||
"twilioAuthToken": "Kimlik Doğrulama Jetonu",
|
||||
"twilioFromNumber": "Gönderen Numara",
|
||||
"twilioToNumber": "Alıcı Numara",
|
||||
"twilioAccountSID": "Hesap ID",
|
||||
"sameAsServerTimezone": "Sunucu Saat Dilimi ile aynı",
|
||||
"startDateTime": "Başlangıç Tarihi/Saati",
|
||||
"endDateTime": "Bitiş Tarihi/Saati",
|
||||
"cronExpression": "Cron İfadesi",
|
||||
"cronSchedule": "Zamanlama: ",
|
||||
"invalidCronExpression": "Geçersiz Cron İfadesi: {0}",
|
||||
"ntfyAuthenticationMethod": "Kimlik Doğrulama Yöntemi",
|
||||
"ntfyUsernameAndPassword": "Kullanıcı adı ve şifre",
|
||||
"pushoverMessageTtl": "Mesajın Yaşama Süresi (Saniye)",
|
||||
"Show Clickable Link": "Tıklanabilir Bağlantıyı Göster",
|
||||
"Open Badge Generator": "Rozet Oluşturucuyu Aç",
|
||||
"Badge Generator": "{0} Rozet Oluşturucu",
|
||||
"Badge Type": "Rozet Türü",
|
||||
"Badge Duration": "Rozet Süresi",
|
||||
"Badge Label": "Rozet Etiketi",
|
||||
"Badge Prefix": "Rozet Öneki",
|
||||
"Badge Suffix": "Rozet Eki",
|
||||
"Badge Label Color": "Rozet Etiket Rengi",
|
||||
"Badge Color": "Rozet Rengi",
|
||||
"Badge Label Prefix": "Rozet Etiket Öneki",
|
||||
"Badge Label Suffix": "Rozet Etiket Eki",
|
||||
"Badge Up Color": "Rozet Normal Rengi",
|
||||
"Badge Down Color": "Rozet Hatalı Rengi",
|
||||
"Badge Pending Color": "Rozet Bekleyen Rengi",
|
||||
"Badge Maintenance Color": "Rozet Bakım Rengi",
|
||||
"Badge Warn Color": "Rozet Uyarı Rengi",
|
||||
"Badge Warn Days": "Rozet Uyarı Günleri",
|
||||
"Badge Down Days": "Rozet Hatalı Günleri",
|
||||
"Badge Style": "Rozet Stili",
|
||||
"Badge value (For Testing only.)": "Rozet değeri (Yalnızca Test için.)",
|
||||
"Badge URL": "Rozet URL'i",
|
||||
"Monitor Setting": "{0}'nin Monitör Ayarı",
|
||||
"Show Clickable Link Description": "Eğer işaretlenirse, bu durum sayfasına erişimi olan herkes monitor URL'ine erişebilir."
|
||||
}
|
||||
|
@@ -16,7 +16,7 @@
|
||||
"rrtypeDescription": "Виберіть тип ресурсного запису, який ви хочете відстежувати",
|
||||
"pauseMonitorMsg": "Ви дійсно хочете поставити на паузу?",
|
||||
"Settings": "Налаштування",
|
||||
"Dashboard": "Панель управління",
|
||||
"Dashboard": "Панель керування",
|
||||
"New Update": "Оновлення",
|
||||
"Language": "Мова",
|
||||
"Appearance": "Зовнішній вигляд",
|
||||
@@ -120,7 +120,7 @@
|
||||
"Heartbeats": "Опитування",
|
||||
"Auto Get": "Авто-отримання",
|
||||
"enableDefaultNotificationDescription": "Для кожного нового монітора це сповіщення буде включено за замовчуванням. Ви все ще можете відключити сповіщення в кожному моніторі окремо.",
|
||||
"Default enabled": "Використовувати за промовчанням",
|
||||
"Default enabled": "Використовувати за замовчуванням",
|
||||
"Also apply to existing monitors": "Застосувати до існуючих моніторів",
|
||||
"Export": "Експорт",
|
||||
"Import": "Імпорт",
|
||||
@@ -270,7 +270,7 @@
|
||||
"octopushPhoneNumber": "Номер телефону (між. формат, наприклад: +380123456789) ",
|
||||
"octopushSMSSender": "Ім'я відправника SMS: 3-11 символів алвафіту, цифр та пробілів (a-zA-Z0-9)",
|
||||
"LunaSea Device ID": "ID пристрою LunaSea",
|
||||
"Apprise URL": "Apprise URL",
|
||||
"Apprise URL": "Apprise URL-адреса",
|
||||
"Example:": "Приклад: {0}",
|
||||
"Read more:": "Докладніше: {0}",
|
||||
"Status:": "Статус: {0}",
|
||||
@@ -477,35 +477,35 @@
|
||||
"From Name/Number": "Від Ім'я/Номер",
|
||||
"Leave blank to use a shared sender number.": "Залиште поле порожнім, щоб використовувати спільний номер відправника.",
|
||||
"Octopush API Version": "Octopush API версія",
|
||||
"Legacy Octopush-DM": "Legacy Octopush-DM",
|
||||
"Legacy Octopush-DM": "Застарілий Octopush-DM",
|
||||
"endpoint": "кінцева точка",
|
||||
"octopushAPIKey": "\"Ключ API\" з облікових даних HTTP API в панелі керування",
|
||||
"octopushLogin": "\"Ім'я користувача\" з облікових даних HTTP API на панелі керування",
|
||||
"promosmsLogin": "API Логін",
|
||||
"promosmsPassword": "API Пароль",
|
||||
"pushoversounds pushover": "Pushover (по замовчуванню)",
|
||||
"pushoversounds bike": "Bike",
|
||||
"pushoversounds bugle": "Bugle",
|
||||
"pushoversounds cashregister": "Cash Register",
|
||||
"pushoversounds bike": "Велосипед",
|
||||
"pushoversounds bugle": "Горн",
|
||||
"pushoversounds cashregister": "Касовий апарат",
|
||||
"pushoversounds classical": "Classical",
|
||||
"pushoversounds cosmic": "Cosmic",
|
||||
"pushoversounds falling": "Falling",
|
||||
"pushoversounds gamelan": "Gamelan",
|
||||
"pushoversounds incoming": "Incoming",
|
||||
"pushoversounds intermission": "Intermission",
|
||||
"pushoversounds magic": "Magic",
|
||||
"pushoversounds mechanical": "Mechanical",
|
||||
"pushoversounds pianobar": "Piano Bar",
|
||||
"pushoversounds siren": "Siren",
|
||||
"pushoversounds spacealarm": "Space Alarm",
|
||||
"pushoversounds tugboat": "Tug Boat",
|
||||
"pushoversounds alien": "Alien Alarm (long)",
|
||||
"pushoversounds climb": "Climb (long)",
|
||||
"pushoversounds persistent": "Persistent (long)",
|
||||
"pushoversounds echo": "Pushover Echo (long)",
|
||||
"pushoversounds updown": "Up Down (long)",
|
||||
"pushoversounds vibrate": "Vibrate Only",
|
||||
"pushoversounds none": "None (silent)",
|
||||
"pushoversounds falling": "Падіння",
|
||||
"pushoversounds gamelan": "Гамелан",
|
||||
"pushoversounds incoming": "Вхідний",
|
||||
"pushoversounds intermission": "Антракт",
|
||||
"pushoversounds magic": "Магія",
|
||||
"pushoversounds mechanical": "Механічний",
|
||||
"pushoversounds pianobar": "Піано-бар",
|
||||
"pushoversounds siren": "Сирена",
|
||||
"pushoversounds spacealarm": "Космічна тривога",
|
||||
"pushoversounds tugboat": "Буксирний катер",
|
||||
"pushoversounds alien": "Тривога прибульців (довга)",
|
||||
"pushoversounds climb": "Підйом (довгий)",
|
||||
"pushoversounds persistent": "Стійкий (довгий)",
|
||||
"pushoversounds echo": "Pushover ехо (довгий)",
|
||||
"pushoversounds updown": "Вгору вниз (довгий)",
|
||||
"pushoversounds vibrate": "Тільки вібрація",
|
||||
"pushoversounds none": "Нічого (тиша)",
|
||||
"pushyAPIKey": "Секретний ключ API",
|
||||
"pushyToken": "Токен пристрою",
|
||||
"Using a Reverse Proxy?": "Використовувати зворотній проксі?",
|
||||
@@ -587,7 +587,7 @@
|
||||
"weekdayShortSun": "Нд",
|
||||
"Single Maintenance Window": "Разове технічне обслуговування",
|
||||
"Maintenance Time Window of a Day": "Період доби для технічного обслуговування",
|
||||
"Effective Date Range": "Діапазон дат вступу в силу",
|
||||
"Effective Date Range": "Діапазон дат вступу в силу (необов'язково)",
|
||||
"Schedule Maintenance": "Розклад обслуговування",
|
||||
"DateTime Range": "Діапазон дат і часу",
|
||||
"loadingError": "Не вдалося отримати дані, спробуйте пізніше.",
|
||||
@@ -744,5 +744,43 @@
|
||||
"lunaseaTarget": "Ціль",
|
||||
"Add New Tag": "Додати новий тег",
|
||||
"lunaseaDeviceID": "ID пристрою",
|
||||
"lunaseaUserID": "ID користувача"
|
||||
"lunaseaUserID": "ID користувача",
|
||||
"twilioAccountSID": "SID облікового запису",
|
||||
"twilioAuthToken": "Токен авторизації",
|
||||
"twilioFromNumber": "З номера",
|
||||
"twilioToNumber": "На номер",
|
||||
"sameAsServerTimezone": "Такий самий, як часовий пояс сервера",
|
||||
"startDateTime": "Дата і час початку",
|
||||
"endDateTime": "Дата і час закінчення",
|
||||
"cronExpression": "Cron-вираз",
|
||||
"cronSchedule": "Розклад: ",
|
||||
"invalidCronExpression": "Неправильний Cron-вираз: {0}",
|
||||
"statusPageRefreshIn": "Оновлювати кожні: {0}",
|
||||
"ntfyAuthenticationMethod": "Метод автентифікації",
|
||||
"ntfyUsernameAndPassword": "Ім'я користувача та пароль",
|
||||
"pushoverMessageTtl": "TTL повідомлення (секунди)",
|
||||
"Monitor Setting": "Налаштування монітора {0}",
|
||||
"Show Clickable Link": "Показувати клікабельне посилання",
|
||||
"Show Clickable Link Description": "Якщо позначено, кожен, хто має доступ до цієї сторінки статусу, може мати доступ до URL-адреси моніторингу.",
|
||||
"Open Badge Generator": "Відкрити генератор бейджів",
|
||||
"Badge Generator": "Генератор бейджів {0}",
|
||||
"Badge Type": "Тип бейджа",
|
||||
"Badge Duration": "Тривалість бейджа",
|
||||
"Badge Label": "Ярлик бейджа",
|
||||
"Badge Prefix": "Префікс бейджа",
|
||||
"Badge Suffix": "Суфікс бейджа",
|
||||
"Badge Label Color": "Колір ярлика бейджа",
|
||||
"Badge Color": "Колір бейджа",
|
||||
"Badge Label Prefix": "Префікс ярлика бейджа",
|
||||
"Badge Label Suffix": "Суфікс ярлика бейджа",
|
||||
"Badge Style": "Стиль бейджа",
|
||||
"Badge value (For Testing only.)": "Значення бейджа (тільки для тестування.)",
|
||||
"Badge URL": "URL бейджа",
|
||||
"Badge Up Color": "Колір бейджа \"Доступний\"",
|
||||
"Badge Down Color": "Колір бейджа \"Недоступний\"",
|
||||
"Badge Pending Color": "Колір бейджа \"Очікування\"",
|
||||
"Badge Warn Color": "Колір бейджа \"Попередження\"",
|
||||
"Badge Warn Days": "Бейдж \"Днів попередження\"",
|
||||
"Badge Maintenance Color": "Колір бейджа \"Обслуговування\"",
|
||||
"Badge Down Days": "Бейдж \"Днів недоступний\""
|
||||
}
|
||||
|
@@ -270,7 +270,7 @@
|
||||
"No Monitors": "کوئی مانیٹر نہیں",
|
||||
"Cancel": "منسوخ کریں",
|
||||
"Powered by": "کی طرف سے طاقت",
|
||||
"Custom CSS": "حسب ضرورت سی ایس ایس",
|
||||
"Custom CSS": "اپنی مرضی کے مطابق سی ایس ایس",
|
||||
"deleteProxyMsg": "کیا آپ واقعی اس پراکسی کو تمام مانیٹر کے لیے حذف کرنا چاہتے ہیں؟",
|
||||
"enableProxyDescription": "یہ پراکسی مانیٹر کی درخواستوں پر اس وقت تک اثر نہیں کرے گی جب تک کہ اسے فعال نہ کیا جائے۔ آپ ایکٹیویشن اسٹیٹس کے ذریعے تمام مانیٹرس سے پراکسی کو عارضی طور پر غیر فعال کر سکتے ہیں۔",
|
||||
"setAsDefaultProxyDescription": "یہ پراکسی نئے مانیٹرز کے لیے بطور ڈیفالٹ فعال ہو جائے گی۔ آپ اب بھی ہر مانیٹر کے لیے الگ الگ پراکسی کو غیر فعال کر سکتے ہیں۔",
|
||||
@@ -409,7 +409,7 @@
|
||||
"maintenanceStatus-scheduled": "طے شدہ",
|
||||
"maintenanceStatus-ended": "ختم ہوا",
|
||||
"recurringInterval": "وقفہ",
|
||||
"Recurring": "بار بار چلنے والا",
|
||||
"Recurring": "بار چلنے والا",
|
||||
"strategyManual": "دستی طور پر فعال/غیر فعال",
|
||||
"warningTimezone": "یہ سرور کا ٹائم زون استعمال کر رہا ہے",
|
||||
"weekdayShortMon": "پیر",
|
||||
@@ -427,5 +427,25 @@
|
||||
"lastDay4": "مہینے کا چوتھا آخری دن",
|
||||
"pauseMaintenanceMsg": "کیا آپ واقعی روکنا چاہتے ہیں؟",
|
||||
"No Maintenance": "کوئی دیکھ بھال نہیں",
|
||||
"weekdayShortTue": "منگل"
|
||||
"weekdayShortTue": "منگل",
|
||||
"Add New Tag": "نیا ٹیگ شامل کریں",
|
||||
"Enable DNS Cache": "ڈی این ایس کیشے کو فعال کریں",
|
||||
"Effective Date Range": "مؤثر تاریخ کی حد",
|
||||
"Schedule Maintenance": "شیڈول کی بحالی",
|
||||
"Date and Time": "تاریخ اور وقت",
|
||||
"DateTime Range": "تاریخ کے وقت کی حد",
|
||||
"loadingError": "ڈیٹا حاصل نہیں کیا جا سکتا، براہ کرم بعد میں دوبارہ کوشش کریں۔",
|
||||
"Enable": "فعال",
|
||||
"Disable": "غیر فعال کریں",
|
||||
"dnsCacheDescription": "ہو سکتا ہے یہ کچھ IPv6 ماحول میں کام نہ کر رہا ہو، اگر آپ کو کوئی مسئلہ درپیش ہو تو اسے غیر فعال کر دیں۔",
|
||||
"Single Maintenance Window": "سنگل مینٹیننس ونڈو",
|
||||
"Maintenance Time Window of a Day": "ایک دن کی مینٹیننس ٹائم ونڈو",
|
||||
"plugin": "پلگ ان | پلگ انز",
|
||||
"install": "انسٹال کریں",
|
||||
"statusPageRefreshIn": "اس میں ریفریش کریں: {0}",
|
||||
"maintenanceStatus-unknown": "نامعلوم",
|
||||
"Display Timezone": "ٹائم زون ڈسپلے کریں",
|
||||
"Server Timezone": "سرور ٹائم زون",
|
||||
"statusPageMaintenanceEndDate": "ختم",
|
||||
"IconUrl": "آئیکن یو آر ایل"
|
||||
}
|
||||
|
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"languageName": "Tiếng Việt",
|
||||
"checkEverySecond": "Kiểm tra mỗi {0} giây.",
|
||||
"retryCheckEverySecond": "Thử lại mỗi {0} giây.",
|
||||
"retriesDescription": "Số lần thử lại tối đa trước khi dịch vụ được đánh dấu là down và gửi thông báo.",
|
||||
"ignoreTLSError": "Bỏ qua lỗi TLS/SSL với các web HTTPS.",
|
||||
"upsideDownModeDescription": "Trạng thái đảo ngược, nếu dịch vụ có thể truy cập được nghĩa là DOWN.",
|
||||
"maxRedirectDescription": "Số lần chuyển hướng (redirect) tối đa. Đặt thành 0 để tắt chuyển hướng",
|
||||
"checkEverySecond": "Kiểm tra mỗi {0} giây",
|
||||
"retryCheckEverySecond": "Thử lại mỗi {0} giây",
|
||||
"retriesDescription": "Số lần thử lại tối đa trước khi dịch vụ được đánh dấu là down và gửi thông báo",
|
||||
"ignoreTLSError": "Bỏ qua lỗi TLS/SSL với các web HTTPS",
|
||||
"upsideDownModeDescription": "Chế độ đảo ngược, nếu dịch vụ có thể truy cập được nghĩa là DOWN.",
|
||||
"maxRedirectDescription": "Số lần chuyển hướng (redirect) tối đa. Đặt thành 0 để tắt chuyển hướng.",
|
||||
"acceptedStatusCodesDescription": "Chọn mã trạng thái được coi là phản hồi thành công.",
|
||||
"passwordNotMatchMsg": "Mật khẩu nhập lại không khớp.",
|
||||
"notificationDescription": "Vui lòng chỉ định một kênh thông báo.",
|
||||
@@ -27,7 +27,7 @@
|
||||
"confirmEnableTwoFAMsg": "Bạn chắc chắn muốn bật xác thực 2 lớp (2FA) chứ?",
|
||||
"confirmDisableTwoFAMsg": "Bạn chắc chắn muốn tắt xác thực 2 lớp (2FA) chứ?",
|
||||
"Settings": "Cài đặt",
|
||||
"Dashboard": "Dashboard",
|
||||
"Dashboard": "Trang tổng quan",
|
||||
"New Update": "Bản cập nhật mới",
|
||||
"Language": "Ngôn ngữ",
|
||||
"Appearance": "Giao diện",
|
||||
@@ -102,10 +102,10 @@
|
||||
"Enable Auth": "Bật xác minh",
|
||||
"disableauth.message1": "Bạn có muốn <strong>TẮT XÁC THỰC</strong> không?",
|
||||
"disableauth.message2": "Điều này rất nguy hiểm<strong>BẤT KỲ AI</strong> cũng có thể truy cập và cướp quyền điều khiển.",
|
||||
"Please use this option carefully!": "Vui lòng <strong>cẩn thận</strong>.",
|
||||
"Please use this option carefully!": "Vui lòng <strong>cẩn thận</strong>!",
|
||||
"Logout": "Đăng xuất",
|
||||
"Leave": "Rời",
|
||||
"I understand, please disable": "Tôi hiểu, làm ơn hãy tắt!",
|
||||
"I understand, please disable": "Tôi hiểu, làm ơn hãy tắt",
|
||||
"Confirm": "Xác nhận",
|
||||
"Yes": "Có",
|
||||
"No": "Không",
|
||||
@@ -158,11 +158,11 @@
|
||||
"Token": "Token",
|
||||
"Show URI": "Hiển thị URI",
|
||||
"Tags": "Tags",
|
||||
"Add New below or Select...": "Thêm mới ở dưới hoặc Chọn...",
|
||||
"Tag with this name already exist.": "Tag với tên đã tồn tại.",
|
||||
"Tag with this value already exist.": "Tag với value đã tồn tại.",
|
||||
"Add New below or Select...": "Thêm mới ở dưới hoặc Chọn…",
|
||||
"Tag with this name already exist.": "Tag với tên này đã tồn tại.",
|
||||
"Tag with this value already exist.": "Tag với giá trị này đã tồn tại.",
|
||||
"color": "Màu sắc",
|
||||
"value (optional)": "Value (tuỳ chọn)",
|
||||
"value (optional)": "Giá trị (tuỳ chọn)",
|
||||
"Gray": "Xám",
|
||||
"Red": "Đỏ",
|
||||
"Orange": "Cam",
|
||||
@@ -171,7 +171,7 @@
|
||||
"Indigo": "Chàm",
|
||||
"Purple": "Tím",
|
||||
"Pink": "Hồng",
|
||||
"Search...": "Tìm kiếm...",
|
||||
"Search...": "Tìm kiếm…",
|
||||
"Avg. Ping": "Ping trung bình",
|
||||
"Avg. Response": "Phản hồi trung bình",
|
||||
"Entry Page": "Entry Page",
|
||||
@@ -459,5 +459,37 @@
|
||||
"onebotGroupMessage": "Group",
|
||||
"onebotPrivateMessage": "Private",
|
||||
"onebotUserOrGroupId": "Group/User ID",
|
||||
"onebotSafetyTips": "Để đảm bảo an toàn, hãy thiết lập access token"
|
||||
"onebotSafetyTips": "Để đảm bảo an toàn, hãy thiết lập access token",
|
||||
"Custom": "Tùy chỉnh",
|
||||
"Add New Tag": "Thêm thẻ mới",
|
||||
"webhookAdditionalHeadersDesc": "Đặt header bổ sung được gửi cùng với webhook.",
|
||||
"error": "lỗi",
|
||||
"HTTP Headers": "HTTP Headers",
|
||||
"recurringIntervalMessage": "Chạy một lần mỗi ngày | Chạy một lần mỗi {0} ngày",
|
||||
"Retype the address.": "Nhập lại địa chỉ.",
|
||||
"enableGRPCTls": "Cho phép gửi yêu cầu gRPC với kết nối TLS",
|
||||
"affectedMonitorsDescription": "Chọn kênh theo dõi bị ảnh hưởng bởi lịch bảo trì này",
|
||||
"statusMaintenance": "Bảo trì",
|
||||
"Maintenance": "Bảo trì",
|
||||
"Affected Monitors": "Kênh theo dõi bị ảnh hưởng",
|
||||
"Schedule maintenance": "Thêm lịch bảo trì",
|
||||
"markdownSupported": "Có hỗ trợ Markdown",
|
||||
"Start of maintenance": "Bắt đầu bảo trì",
|
||||
"All Status Pages": "Tất cả các trang trạng thái",
|
||||
"Select status pages...": "Chọn trang trạng thái…",
|
||||
"Certificate Expiry Notification": "Thông báo hết hạn chứng chỉ",
|
||||
"Show update if available": "Hiển thị cập nhật (nếu có)",
|
||||
"What you can try:": "Bạn có thể thử:",
|
||||
"trustProxyDescription": "Tin tưởng các header 'X-Forwarded-*'. Nếu bạn muốn lấy đúng IP máy khách và Uptime Kuma của bạn đứng sau một proxy như Nginx hoặc Apache, bạn nên kích hoạt tính năng này.",
|
||||
"webhookAdditionalHeadersTitle": "Header bổ sung",
|
||||
"Help": "Trợ giúp",
|
||||
"Game": "Trò chơi",
|
||||
"Pick Affected Monitors...": "Chọn kênh theo dõi…",
|
||||
"statusPageRefreshIn": "Làm mới trong: {0}",
|
||||
"Authentication": "Xác thực",
|
||||
"Using a Reverse Proxy?": "Bạn đang sử dụng Reverse Proxy?",
|
||||
"Check how to config it for WebSocket": "Kiểm tra cách cấu hình nó cho WebSocket",
|
||||
"Go back to the previous page.": "Quay trở lại trang trước.",
|
||||
"wayToGetLineNotifyToken": "Bạn có thể lấy access token từ {0}",
|
||||
"Resend Notification if Down X times consecutively": "Gửi lại thông báo nếu Down X lần liên tiếp"
|
||||
}
|
||||
|
1
src/lang/xh.json
Normal file
1
src/lang/xh.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
@@ -60,7 +60,7 @@
|
||||
"Quick Stats": "状态速览",
|
||||
"Up": "正常",
|
||||
"Down": "故障",
|
||||
"Pending": "检测中",
|
||||
"Pending": "重试中",
|
||||
"Unknown": "未知",
|
||||
"Pause": "暂停",
|
||||
"Name": "名称",
|
||||
@@ -235,7 +235,7 @@
|
||||
"smtpBCC": "密送",
|
||||
"discord": "Discord",
|
||||
"Discord Webhook URL": "Discord Webhook 网址",
|
||||
"wayToGetDiscordURL": "可在服务器设置 -> 整合 -> 创建 Webhook中获取",
|
||||
"wayToGetDiscordURL": "可在服务器设置 -> 整合 -> Webhook -> 创建 Webhook 中获取",
|
||||
"Bot Display Name": "机器人显示名称",
|
||||
"Prefix Custom Message": "自定义消息前缀",
|
||||
"Hello @everyone is...": "{'@'}everyone,……",
|
||||
@@ -395,7 +395,7 @@
|
||||
"smseagleContact": "通讯录联系人",
|
||||
"smseagleRecipientType": "收信人类型",
|
||||
"smseagleRecipient": "收信人(多个需用半角逗号分隔)",
|
||||
"smseagleToken": "API访问令牌",
|
||||
"smseagleToken": "API 访问令牌",
|
||||
"smseagleUrl": "您的 SMSEagle 设备 URL",
|
||||
"smseagleEncoding": "以 Unicode 发送",
|
||||
"smseaglePriority": "消息优先级(0-9,默认为 0)",
|
||||
@@ -423,7 +423,7 @@
|
||||
"alerta": "Alerta",
|
||||
"alertaApiEndpoint": "API 接入点",
|
||||
"alertaEnvironment": "环境参数",
|
||||
"alertaApiKey": "API Key",
|
||||
"alertaApiKey": "API 密钥",
|
||||
"alertaAlertState": "报警时的严重性",
|
||||
"alertaRecoverState": "恢复后的严重性",
|
||||
"deleteStatusPageMsg": "您确认要删除此状态页吗?",
|
||||
@@ -515,7 +515,7 @@
|
||||
"onebotPrivateMessage": "私聊",
|
||||
"onebotUserOrGroupId": "群组/用户 ID",
|
||||
"onebotSafetyTips": "出于安全原因,请务必设置 AccessToken",
|
||||
"PushDeer Key": "PushDeer Key",
|
||||
"PushDeer Key": "PushDeer 密钥",
|
||||
"Footer Text": "底部自定义文本",
|
||||
"Show Powered By": "显示 Powered By",
|
||||
"Domain Names": "域名",
|
||||
@@ -528,8 +528,8 @@
|
||||
"RadiusCallingStationId": "呼叫方号码(Calling Station Id)",
|
||||
"RadiusCallingStationIdDescription": "发出请求的设备的标识",
|
||||
"Certificate Expiry Notification": "证书到期时通知",
|
||||
"API Username": "API Username",
|
||||
"API Key": "API Key",
|
||||
"API Username": "API 用户名",
|
||||
"API Key": "API 密钥",
|
||||
"Recipient Number": "收件人手机号码",
|
||||
"From Name/Number": "发件人名称/手机号码",
|
||||
"Leave blank to use a shared sender number.": "留空以使用平台共享的发件人手机号码。",
|
||||
@@ -546,7 +546,7 @@
|
||||
"pushoversounds cashregister": "Cash Register",
|
||||
"pushoversounds classical": "Classical",
|
||||
"pushoversounds cosmic": "Cosmic",
|
||||
"pushoversounds falling": "下落",
|
||||
"pushoversounds falling": "Falling",
|
||||
"pushoversounds gamelan": "Gamelan",
|
||||
"pushoversounds incoming": "Incoming",
|
||||
"pushoversounds intermission": "Intermission",
|
||||
@@ -592,7 +592,7 @@
|
||||
"Container Name / ID": "容器名称 / ID",
|
||||
"Docker Host": "Docker 宿主",
|
||||
"Docker Hosts": "Docker 宿主",
|
||||
"ntfy Topic": "ntfy Topic",
|
||||
"ntfy Topic": "ntfy 主题",
|
||||
"Domain": "域名",
|
||||
"Workstation": "工作站",
|
||||
"disableCloudflaredNoAuthMsg": "您现在正处于 No Auth 模式,无需输入密码。",
|
||||
@@ -661,12 +661,12 @@
|
||||
"dnsCacheDescription": "可能无法在某些 IPv6 环境工作,如果遇到问题请禁用。",
|
||||
"Single Maintenance Window": "单一时间窗口",
|
||||
"Maintenance Time Window of a Day": "每日维护时间窗口",
|
||||
"Effective Date Range": "生效日期范围",
|
||||
"Effective Date Range": "生效日期范围(可选)",
|
||||
"Schedule Maintenance": "计划维护",
|
||||
"Date and Time": "日期时间",
|
||||
"DateTime Range": "日期时间范围",
|
||||
"Strategy": "策略",
|
||||
"Free Mobile User Identifier": "Free Mobile User Identifier",
|
||||
"Free Mobile User Identifier": "Free Mobile 用户 ID",
|
||||
"Free Mobile API Key": "Free Mobile API Key",
|
||||
"Enable TLS": "启用 TLS",
|
||||
"Proto Service Name": "Proto 服务名称",
|
||||
@@ -682,7 +682,7 @@
|
||||
"Monitor": "监控项",
|
||||
"Custom": "自定义",
|
||||
"promosmsAllowLongSMS": "允许长的短信",
|
||||
"confirmDeleteTagMsg": "你确定你要删除这个标签?与此标签关联的监视器不会被删除。",
|
||||
"confirmDeleteTagMsg": "您确定要删除这个标签?与此标签关联的监控项不会被删除。",
|
||||
"infiniteRetention": "设为0表示无限保留期。",
|
||||
"Help": "帮助",
|
||||
"Game": "游戏",
|
||||
@@ -720,13 +720,13 @@
|
||||
"apiKey-expired": "已过期",
|
||||
"Expires": "过期时间",
|
||||
"apiKey-inactive": "已禁用",
|
||||
"disableAPIKeyMsg": "你确定要禁用这个 API 密钥?",
|
||||
"deleteAPIKeyMsg": "你确定要删除这个 API 密钥?",
|
||||
"disableAPIKeyMsg": "您确定要禁用这个 API 密钥?",
|
||||
"deleteAPIKeyMsg": "您确定要删除这个 API 密钥?",
|
||||
"Generate": "生成",
|
||||
"API Keys": "API 密钥",
|
||||
"Don't expire": "从不过期",
|
||||
"Key Added": "API 密钥已生成",
|
||||
"apiKeyAddedMsg": "你的 API 密钥已生成。此页只会显示一次,请妥当保存。",
|
||||
"apiKeyAddedMsg": "您的 API 密钥已生成。此页只会显示一次,请妥当保存。",
|
||||
"pagertreeUrgency": "紧急程度",
|
||||
"pagertreeLow": "低",
|
||||
"pagertreeCritical": "严重",
|
||||
@@ -738,7 +738,45 @@
|
||||
"pagertreeDoNothing": "什么都不做",
|
||||
"wayToGetPagerTreeIntegrationURL": "在 PagerTree 中创建 Uptime Kuma 集成后,复制端点 URL 到此处。在 {0} 查看详情",
|
||||
"Add New Tag": "添加新标签",
|
||||
"lunaseaDeviceID": "设备ID",
|
||||
"lunaseaDeviceID": "设备 ID",
|
||||
"lunaseaTarget": "目标",
|
||||
"lunaseaUserID": "用户ID"
|
||||
"lunaseaUserID": "用户 ID",
|
||||
"statusPageRefreshIn": "将于 {0} 后刷新",
|
||||
"twilioAccountSID": "账户 SID",
|
||||
"twilioAuthToken": "验证 Token",
|
||||
"twilioFromNumber": "发信号码",
|
||||
"twilioToNumber": "收信号码",
|
||||
"sameAsServerTimezone": "使用服务器时区",
|
||||
"startDateTime": "开始日期/时间",
|
||||
"invalidCronExpression": "无效的 Cron 表达式:{0}",
|
||||
"endDateTime": "结束日期/时间",
|
||||
"cronExpression": "Cron 表达式",
|
||||
"cronSchedule": "计划: ",
|
||||
"ntfyAuthenticationMethod": "鉴权方式",
|
||||
"ntfyUsernameAndPassword": "用户名和密码",
|
||||
"pushoverMessageTtl": "消息存活时间(秒)",
|
||||
"Monitor Setting": "{0} 监控项设置",
|
||||
"Badge Color": "徽章内容颜色",
|
||||
"Badge Suffix": "徽章内容后缀",
|
||||
"Badge Prefix": "徽章内容前缀",
|
||||
"Badge Label": "徽章标签",
|
||||
"Badge Duration": "徽章显示时段",
|
||||
"Badge Type": "徽章类型",
|
||||
"Badge Generator": "{0} 徽章生成器",
|
||||
"Open Badge Generator": "打开徽章生成器",
|
||||
"Badge Style": "徽章样式",
|
||||
"Badge Down Days": "徽章证书到期故障天数",
|
||||
"Badge Warn Days": "徽章证书到期警告天数",
|
||||
"Badge Warn Color": "警告状态下徽章颜色",
|
||||
"Badge Maintenance Color": "维护状态下徽章颜色",
|
||||
"Badge Down Color": "故障状态下徽章颜色",
|
||||
"Badge Up Color": "正常状态下徽章颜色",
|
||||
"Badge Label Suffix": "徽章标签后缀",
|
||||
"Badge URL": "徽章网址",
|
||||
"Badge value (For Testing only.)": "徽章内容(仅供测试)",
|
||||
"Badge Pending Color": "重试中状态下徽章颜色",
|
||||
"Badge Label Prefix": "徽章标签前缀",
|
||||
"Badge Label Color": "徽章标签颜色",
|
||||
"Show Clickable Link Description": "勾选后所有能访问本状态页的访客均可查看该监控项网址。",
|
||||
"Show Clickable Link": "显示可点击的监控项链接"
|
||||
}
|
||||
|
@@ -228,7 +228,7 @@
|
||||
"smtpCC": "CC",
|
||||
"smtpBCC": "BCC",
|
||||
"Discord Webhook URL": "Discord Webhook 網址",
|
||||
"wayToGetDiscordURL": "您可以前往伺服器設定 -> 整合 -> Webhook -> 新 Webhook 以取得",
|
||||
"wayToGetDiscordURL": "您可以前往 伺服器設定 -> 整合 -> Webhook -> 新 Webhook 以取得",
|
||||
"Bot Display Name": "機器人顯示名稱",
|
||||
"Prefix Custom Message": "前綴自訂訊息",
|
||||
"Webhook URL": "Webhook 網址",
|
||||
@@ -549,7 +549,7 @@
|
||||
"confirmUninstallPlugin": "你確定要解除安裝?",
|
||||
"dataRetentionTimeError": "保留限期必需為 0 或正數",
|
||||
"infiniteRetention": "設定為 0 以作無限期保留。",
|
||||
"Effective Date Range": "有效日期範圍",
|
||||
"Effective Date Range": "有效日期範圍 (可選)",
|
||||
"Hello @everyone is...": "Hello {'@'}everyone is…",
|
||||
"Packet Size": "Packet 大小",
|
||||
"Event type:": "事件類型:",
|
||||
@@ -706,5 +706,18 @@
|
||||
"Add New Tag": "加新標籤",
|
||||
"Economy": "經濟",
|
||||
"Lowcost": "平價",
|
||||
"high": "高價"
|
||||
"high": "高價",
|
||||
"statusPageRefreshIn": "將於 {0} 後重新整理",
|
||||
"SendKey": "SendKey",
|
||||
"SMSManager API Docs": "SMSManager API 文件 ",
|
||||
"startDateTime": "開始時間",
|
||||
"pagertreeLow": "低",
|
||||
"endDateTime": "結束時間",
|
||||
"cronExpression": "Cron 表達式",
|
||||
"cronSchedule": "排程: ",
|
||||
"invalidCronExpression": "無效 Cron 表達式:{0}",
|
||||
"sameAsServerTimezone": "使用伺服器時區",
|
||||
"WeCom Bot Key": "WeCom 機器人 Key",
|
||||
"pagertreeMedium": "中",
|
||||
"pagertreeHigh": "高"
|
||||
}
|
||||
|
@@ -234,7 +234,7 @@
|
||||
"smtpBCC": "BCC",
|
||||
"discord": "Discord",
|
||||
"Discord Webhook URL": "Discord Webhook 網址",
|
||||
"wayToGetDiscordURL": "您可以前往伺服器設定 -> 整合 -> Webhook -> 新 Webhook 以取得",
|
||||
"wayToGetDiscordURL": "您可以前往伺服器設定 (Server Settings) -> 整合 (Integrations) -> 檢視 Webhooks (View Webhooks) -> 新 Webhook (New Webhook) 以取得新的 Webhook",
|
||||
"Bot Display Name": "機器人顯示名稱",
|
||||
"Prefix Custom Message": "前綴自訂訊息",
|
||||
"Hello @everyone is...": "Hello {'@'}everyone is…",
|
||||
@@ -607,7 +607,7 @@
|
||||
"goAlertInfo": "GoAlert 是用於待命排程、升級自動化,以及通知 (如簡訊或語音通話) 的開源應用程式。自動在正確的時間、用洽當的方法、聯絡合適的人! {0}",
|
||||
"goAlertIntegrationKeyInfo": "取得服務的通用 API 整合金鑰,格式為 \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\"。通常是已複製的網址的權杖參數值。",
|
||||
"goAlert": "GoAlert",
|
||||
"backupOutdatedWarning": "過時:由於新功能的增加,且未妥善維護,故此備份功能無法產生或復原完整備份。",
|
||||
"backupOutdatedWarning": "即將棄用:由於專案新增了大量新功能,且備份功能未被妥善維護,故此功能無法產生或復原完整備份。",
|
||||
"backupRecommend": "請直接備份磁碟區或 ./data/ 資料夾。",
|
||||
"Optional": "選填",
|
||||
"squadcast": "Squadcast",
|
||||
@@ -652,7 +652,7 @@
|
||||
"dnsCacheDescription": "在某些 IPv6 環境可能會無法運作,如果您遇到任何問題,請停用。",
|
||||
"Single Maintenance Window": "單一維護時段",
|
||||
"Maintenance Time Window of a Day": "每日的維護時段",
|
||||
"Effective Date Range": "有效的日期範圍",
|
||||
"Effective Date Range": "有效的日期範圍(可選)",
|
||||
"Schedule Maintenance": "排程維護",
|
||||
"Date and Time": "時間和日期",
|
||||
"DateTime Range": "DateTime 範圍",
|
||||
@@ -674,5 +674,37 @@
|
||||
"Game": "遊戲",
|
||||
"Help": "幫助",
|
||||
"Monitor": "監測器 | 監測器",
|
||||
"Custom": "自訂"
|
||||
"Custom": "自訂",
|
||||
"sameAsServerTimezone": "使用服務器時區",
|
||||
"cronExpression": "Cron 表達式",
|
||||
"telegramSendSilently": "靜默發送到 Telegram",
|
||||
"telegramSendSilentlyDescription": "靜默地發送消息。消息發布後用戶會收到無聲通知。",
|
||||
"pagertreeDoNothing": "什麼都不做",
|
||||
"Add New Tag": "添加新標籤",
|
||||
"telegramMessageThreadIDDescription": "(可選) Telegram 話題描述",
|
||||
"telegramMessageThreadID": "(可選)話題 ID",
|
||||
"startDateTime": "開始日期/時間",
|
||||
"endDateTime": "結束日期/時間",
|
||||
"cronSchedule": "計劃: ",
|
||||
"invalidCronExpression": "無效的 Cron 表達式:{0}",
|
||||
"telegramProtectContent": "阻止轉發/保存",
|
||||
"telegramProtectContentDescription": "如果啟用,Telegram 中的機器人消息將受到保護,不會被轉發和保存。",
|
||||
"installing": "安裝中",
|
||||
"uninstall": "卸載",
|
||||
"loadingError": "無法獲取數據, 請重試",
|
||||
"markdownSupported": "支持Markdown語法",
|
||||
"Packet Size": "數據包大小",
|
||||
"statusPageRefreshIn": "將於 {0} 後刷新",
|
||||
"confirmUninstallPlugin": "是否要卸載這個插件?",
|
||||
"Key Added": "已創建金鑰",
|
||||
"Clone Monitor": "複製監控項目",
|
||||
"Clone": "複製",
|
||||
"cloneOf": "從 {0} 複製",
|
||||
"uninstalling": "移除中",
|
||||
"notificationRegional": "地區限定",
|
||||
"wayToGetZohoCliqURL": "您可以前往此頁面以了解如何建立 webhook 網址 {0}。",
|
||||
"wayToGetKookBotToken": "到 {0} 創建應用程式並取得 bot token",
|
||||
"dataRetentionTimeError": "保留期限必須為 0 或正數",
|
||||
"infiniteRetention": "設定為 0 以作無限期保留。",
|
||||
"confirmDeleteTagMsg": "你確定你要刪除此標籤?相關的監測器不會被刪除。"
|
||||
}
|
||||
|
@@ -95,7 +95,7 @@
|
||||
</main>
|
||||
|
||||
<!-- Mobile Only -->
|
||||
<div v-if="$root.isMobile" style="width: 100%; height: 60px;" />
|
||||
<div v-if="$root.isMobile" style="width: 100%; height: calc(60px + env(safe-area-inset-bottom));" />
|
||||
<nav v-if="$root.isMobile && $root.loggedIn" class="bottom-nav">
|
||||
<router-link to="/dashboard" class="nav-link">
|
||||
<div><font-awesome-icon icon="tachometer-alt" /></div>
|
||||
@@ -182,14 +182,14 @@ export default {
|
||||
z-index: 1000;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
height: 60px;
|
||||
height: calc(60px + env(safe-area-inset-bottom));
|
||||
width: 100%;
|
||||
left: 0;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 15px 47px 0 rgba(0, 0, 0, 0.05), 0 5px 14px 0 rgba(0, 0, 0, 0.05);
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
padding: 0 10px;
|
||||
padding: 0 10px env(safe-area-inset-bottom);
|
||||
|
||||
a {
|
||||
text-align: center;
|
||||
|
@@ -39,6 +39,9 @@ export default {
|
||||
}
|
||||
|
||||
if (this.path.startsWith("/status-page") || this.path.startsWith("/status")) {
|
||||
if (this.statusPageTheme === "auto") {
|
||||
return this.system;
|
||||
}
|
||||
return this.statusPageTheme;
|
||||
} else {
|
||||
if (this.userTheme === "auto") {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user