mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-02-26 13:35:56 +00:00
Compare commits
88 commits
1c8db70fb3
...
e8f3e93fc7
Author | SHA1 | Date | |
---|---|---|---|
|
e8f3e93fc7 | ||
|
459fb138f2 | ||
|
8f950a5145 | ||
|
4d779cfc69 | ||
|
2470451f6d | ||
|
79a26180af | ||
|
7a82ae039c | ||
|
d2f71d11d6 | ||
|
c01494ec33 | ||
|
a7e9bdd43e | ||
|
d7ffa33950 | ||
|
a20a43b8aa | ||
|
ed6087e233 | ||
|
42e77798e5 | ||
|
85dfe1f5d1 | ||
|
b719d11500 | ||
|
b7d2cedf2e | ||
|
f3ee9c2cad | ||
|
582fb6c5ad | ||
|
dda40610c7 | ||
|
bafca6bd37 | ||
|
bbc75b840b | ||
|
019702f8e5 | ||
|
365aa8d814 | ||
|
7e37dacb9a | ||
|
e8c650797c | ||
|
eca90a2b00 | ||
|
4829ad8c5d | ||
|
59e70cb763 | ||
|
6476e58907 | ||
|
e773e454e9 | ||
|
958f96f06d | ||
|
d0067a0a12 | ||
|
130d8d0177 | ||
|
f791d4a9bf | ||
|
9905ca574c | ||
|
fc429108ac | ||
|
10ffde2595 | ||
|
a5faa4b225 | ||
|
da168fc220 | ||
|
b9b48e1b2d | ||
|
a760898281 | ||
|
289b72d07d | ||
|
9257a7a19e | ||
|
8084c7e61c | ||
|
1cf88f4fea | ||
|
0f6cb15561 | ||
|
14199dc2cb | ||
|
e8e83808d3 | ||
|
324d879aad | ||
|
d2a44487b3 | ||
|
35668219ec | ||
|
895f6d2ff1 | ||
|
3088cc6141 | ||
|
98415bd419 | ||
|
087d20b775 | ||
|
ec7923f4fd | ||
|
86b3ff6bfd | ||
|
e6159d9ab4 | ||
|
e781325633 | ||
|
72478090e7 | ||
|
50ec9fec05 | ||
|
62c55f0e25 | ||
|
3ab35c38fc | ||
|
1d86fa2b5c | ||
|
cef072cae9 | ||
|
abcc98c836 | ||
|
aa38344c3d | ||
|
9ff0ae67df | ||
|
a14d05daab | ||
|
93c5ab0bd8 | ||
|
d27a9e7d7f | ||
|
c120c37030 | ||
|
acd1e7211a | ||
|
e40ce59e66 | ||
|
a309cf0e2c | ||
|
0071775525 | ||
|
46d90a6a99 | ||
|
3479992302 | ||
|
030bb1c0b8 | ||
|
7da401662f | ||
|
243726b03c | ||
|
936665aac3 | ||
|
cfc69e69e8 | ||
|
ad71fc9481 | ||
|
8160930470 | ||
|
727651b4ae | ||
|
f1430ec6af |
125 changed files with 6420 additions and 2399 deletions
8
.github/workflows/auto-test.yml
vendored
8
.github/workflows/auto-test.yml
vendored
|
@ -15,14 +15,14 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
auto-test:
|
auto-test:
|
||||||
needs: [ check-linters, e2e-test ]
|
needs: [ check-linters ]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [macos-latest, ubuntu-latest, windows-latest, ARM64]
|
os: [macos-latest, ubuntu-latest, windows-latest, ARM64]
|
||||||
node: [ 18, 20.5 ]
|
node: [ 18, 20 ]
|
||||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
@ -42,7 +42,7 @@ jobs:
|
||||||
|
|
||||||
# As a lot of dev dependencies are not supported on ARMv7, we have to test it separately and just test if `npm ci --production` works
|
# As a lot of dev dependencies are not supported on ARMv7, we have to test it separately and just test if `npm ci --production` works
|
||||||
armv7-simple-test:
|
armv7-simple-test:
|
||||||
needs: [ check-linters ]
|
needs: [ ]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
if: ${{ github.repository == 'louislam/uptime-kuma' }}
|
if: ${{ github.repository == 'louislam/uptime-kuma' }}
|
||||||
|
@ -77,7 +77,7 @@ jobs:
|
||||||
- run: npm run lint:prod
|
- run: npm run lint:prod
|
||||||
|
|
||||||
e2e-test:
|
e2e-test:
|
||||||
needs: [ check-linters ]
|
needs: [ ]
|
||||||
runs-on: ARM64
|
runs-on: ARM64
|
||||||
steps:
|
steps:
|
||||||
- run: git config --global core.autocrlf false # Mainly for Windows
|
- run: git config --global core.autocrlf false # Mainly for Windows
|
||||||
|
|
10
.github/workflows/json-yaml-validate.yml
vendored
10
.github/workflows/json-yaml-validate.yml
vendored
|
@ -25,3 +25,13 @@ jobs:
|
||||||
with:
|
with:
|
||||||
comment: "true" # enable comment mode
|
comment: "true" # enable comment mode
|
||||||
exclude_file: ".github/config/exclude.txt" # gitignore style file for exclusions
|
exclude_file: ".github/config/exclude.txt" # gitignore style file for exclusions
|
||||||
|
|
||||||
|
check-lang-json:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Use Node.js 20
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
- run: node ./extra/check-lang-json.js
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Project Info
|
# Project Info
|
||||||
|
|
||||||
First of all, I want to thank everyone who have wrote issues or shared pull requests for Uptime Kuma.
|
First of all, I want to thank everyone who has submitted issues or shared pull requests for Uptime Kuma.
|
||||||
I never thought the GitHub community would be so nice!
|
I never thought the GitHub community would be so nice!
|
||||||
Because of this, I also never thought that other people would actually read and edit my code.
|
Because of this, I also never thought that other people would actually read and edit my code.
|
||||||
Parts of the code are not very well-structured or commented, sorry about that.
|
Parts of the code are not very well-structured or commented, sorry about that.
|
||||||
|
@ -9,7 +9,7 @@ The project was created with `vite.js` and is written in `vue3`.
|
||||||
Our backend lives in the `server`-directory and mostly communicates via websockets.
|
Our backend lives in the `server`-directory and mostly communicates via websockets.
|
||||||
Both frontend and backend share the same `package.json`.
|
Both frontend and backend share the same `package.json`.
|
||||||
|
|
||||||
For production, the frontend is build into `dist`-directory and the server (`express.js`) exposes the `dist` directory as the root of the endpoint.
|
For production, the frontend is built into the `dist`-directory and the server (`express.js`) exposes the `dist` directory as the root of the endpoint.
|
||||||
For development, we run vite in development mode on another port.
|
For development, we run vite in development mode on another port.
|
||||||
|
|
||||||
## Directories
|
## Directories
|
||||||
|
@ -28,7 +28,7 @@ For development, we run vite in development mode on another port.
|
||||||
## Can I create a pull request for Uptime Kuma?
|
## Can I create a pull request for Uptime Kuma?
|
||||||
|
|
||||||
Yes or no, it depends on what you will try to do.
|
Yes or no, it depends on what you will try to do.
|
||||||
Both your and our maintainers time is precious, and we don't want to waste both time.
|
Both yours and our maintainers' time is precious, and we don't want to waste either.
|
||||||
|
|
||||||
If you have any questions about any process/.. is not clear, you are likely not alone => please ask them ^^
|
If you have any questions about any process/.. is not clear, you are likely not alone => please ask them ^^
|
||||||
|
|
||||||
|
@ -49,11 +49,11 @@ Different guidelines exist for different types of pull requests (PRs):
|
||||||
<p>
|
<p>
|
||||||
|
|
||||||
If you come across a bug and think you can solve, we appreciate your work.
|
If you come across a bug and think you can solve, we appreciate your work.
|
||||||
Please make sure that you follow by these rules:
|
Please make sure that you follow these rules:
|
||||||
- keep the PR as small as possible, fix only one thing at a time => keeping it reviewable
|
- keep the PR as small as possible, fix only one thing at a time => keeping it reviewable
|
||||||
- test that your code does what you came it does.
|
- test that your code does what you claim it does.
|
||||||
|
|
||||||
<sub>Because maintainer time is precious junior maintainers may merge uncontroversial PRs in this area.</sub>
|
<sub>Because maintainer time is precious, junior maintainers may merge uncontroversial PRs in this area.</sub>
|
||||||
</p>
|
</p>
|
||||||
</details>
|
</details>
|
||||||
- <details><summary><b>translations / internationalisation (i18n)</b></summary>
|
- <details><summary><b>translations / internationalisation (i18n)</b></summary>
|
||||||
|
@ -68,7 +68,7 @@ Different guidelines exist for different types of pull requests (PRs):
|
||||||
- language keys need to be **added to `en.json`** to be visible in weblate. If this has not happened, a PR is appreciated.
|
- language keys need to be **added to `en.json`** to be visible in weblate. If this has not happened, a PR is appreciated.
|
||||||
- **Adding a new language** requires a new file see [these instructions](https://github.com/louislam/uptime-kuma/blob/master/src/lang/README.md)
|
- **Adding a new language** requires a new file see [these instructions](https://github.com/louislam/uptime-kuma/blob/master/src/lang/README.md)
|
||||||
|
|
||||||
<sub>Because maintainer time is precious junior maintainers may merge uncontroversial PRs in this area.</sub>
|
<sub>Because maintainer time is precious, junior maintainers may merge uncontroversial PRs in this area.</sub>
|
||||||
</p>
|
</p>
|
||||||
</details>
|
</details>
|
||||||
- <details><summary><b>new notification providers</b></summary>
|
- <details><summary><b>new notification providers</b></summary>
|
||||||
|
@ -102,7 +102,7 @@ Different guidelines exist for different types of pull requests (PRs):
|
||||||
Therefore, making sure that they work is also really important.
|
Therefore, making sure that they work is also really important.
|
||||||
Because testing notification providers is quite time intensive, we mostly offload this onto the person contributing a notification provider.
|
Because testing notification providers is quite time intensive, we mostly offload this onto the person contributing a notification provider.
|
||||||
|
|
||||||
To make shure you have tested the notification provider, please include screenshots of the following events in the pull-request description:
|
To make sure you have tested the notification provider, please include screenshots of the following events in the pull-request description:
|
||||||
- `UP`/`DOWN`
|
- `UP`/`DOWN`
|
||||||
- Certificate Expiry via https://expired.badssl.com/
|
- Certificate Expiry via https://expired.badssl.com/
|
||||||
- Testing (the test button on the notification provider setup page)
|
- Testing (the test button on the notification provider setup page)
|
||||||
|
@ -117,7 +117,7 @@ Different guidelines exist for different types of pull requests (PRs):
|
||||||
| Testing | paste-image-here | paste-image-here |
|
| Testing | paste-image-here | paste-image-here |
|
||||||
```
|
```
|
||||||
|
|
||||||
<sub>Because maintainer time is precious junior maintainers may merge uncontroversial PRs in this area.</sub>
|
<sub>Because maintainer time is precious, junior maintainers may merge uncontroversial PRs in this area.</sub>
|
||||||
</p>
|
</p>
|
||||||
</details>
|
</details>
|
||||||
- <details><summary><b>new monitoring types</b></summary>
|
- <details><summary><b>new monitoring types</b></summary>
|
||||||
|
@ -138,14 +138,14 @@ Different guidelines exist for different types of pull requests (PRs):
|
||||||
-
|
-
|
||||||
|
|
||||||
|
|
||||||
<sub>Because maintainer time is precious junior maintainers may merge uncontroversial PRs in this area.</sub>
|
<sub>Because maintainer time is precious, junior maintainers may merge uncontroversial PRs in this area.</sub>
|
||||||
</p>
|
</p>
|
||||||
</details>
|
</details>
|
||||||
- <details><summary><b>new features/ major changes / breaking bugfixes</b></summary>
|
- <details><summary><b>new features/ major changes / breaking bugfixes</b></summary>
|
||||||
<p>
|
<p>
|
||||||
|
|
||||||
be sure to **create an empty draft pull request or open an issue, so we can have a discussion first**.
|
be sure to **create an empty draft pull request or open an issue, so we can have a discussion first**.
|
||||||
This is especially important for a large pull request or you don't know if it will be merged or not.
|
This is especially important for a large pull request or when you don't know if it will be merged or not.
|
||||||
|
|
||||||
<sub>Because of the large impact of this work, only senior maintainers may merge PRs in this area.</sub>
|
<sub>Because of the large impact of this work, only senior maintainers may merge PRs in this area.</sub>
|
||||||
</p>
|
</p>
|
||||||
|
@ -201,7 +201,7 @@ The rationale behind this is that we can align the direction and scope of the fe
|
||||||
|
|
||||||
## Project Styles
|
## Project Styles
|
||||||
|
|
||||||
I personally do not like something that requires so many configurations before you can finally start the app.
|
I personally do not like something that requires a lot of configuration before you can finally start the app.
|
||||||
The goal is to make the Uptime Kuma installation as easy as installing a mobile app.
|
The goal is to make the Uptime Kuma installation as easy as installing a mobile app.
|
||||||
|
|
||||||
- Easy to install for non-Docker users
|
- Easy to install for non-Docker users
|
||||||
|
@ -260,7 +260,7 @@ Port `3000` and port `3001` will be used.
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
But sometimes, you would like to restart the server, but not the frontend, you can run these commands in two terminals:
|
But sometimes you may want to restart the server without restarting the frontend. In that case, you can run these commands in two terminals:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run start-frontend-dev
|
npm run start-frontend-dev
|
||||||
|
@ -409,7 +409,7 @@ https://github.com/louislam/uptime-kuma/issues?q=sort%3Aupdated-desc
|
||||||
|
|
||||||
### What is a maintainer and what are their roles?
|
### What is a maintainer and what are their roles?
|
||||||
|
|
||||||
This project has multiple maintainers which specialise in different areas.
|
This project has multiple maintainers who specialise in different areas.
|
||||||
Currently, there are 3 maintainers:
|
Currently, there are 3 maintainers:
|
||||||
|
|
||||||
| Person | Role | Main Area |
|
| Person | Role | Main Area |
|
||||||
|
|
|
@ -16,9 +16,7 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
define: {
|
define: {
|
||||||
"FRONTEND_VERSION": JSON.stringify(process.env.npm_package_version),
|
"FRONTEND_VERSION": JSON.stringify(process.env.npm_package_version),
|
||||||
"DEVCONTAINER": JSON.stringify(process.env.DEVCONTAINER),
|
"process.env": {},
|
||||||
"GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN": JSON.stringify(process.env.GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN),
|
|
||||||
"CODESPACE_NAME": JSON.stringify(process.env.CODESPACE_NAME),
|
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
vue(),
|
||||||
|
|
17
db/knex_migrations/2024-10-1315-rabbitmq-monitor.js
Normal file
17
db/knex_migrations/2024-10-1315-rabbitmq-monitor.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
exports.up = function (knex) {
|
||||||
|
return knex.schema.alterTable("monitor", function (table) {
|
||||||
|
table.text("rabbitmq_nodes");
|
||||||
|
table.string("rabbitmq_username");
|
||||||
|
table.string("rabbitmq_password");
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function (knex) {
|
||||||
|
return knex.schema.alterTable("monitor", function (table) {
|
||||||
|
table.dropColumn("rabbitmq_nodes");
|
||||||
|
table.dropColumn("rabbitmq_username");
|
||||||
|
table.dropColumn("rabbitmq_password");
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
|
@ -1,3 +1,13 @@
|
||||||
|
# Download Apprise deb package
|
||||||
|
FROM node:20-bookworm-slim AS download-apprise
|
||||||
|
WORKDIR /app
|
||||||
|
COPY ./extra/download-apprise.mjs ./download-apprise.mjs
|
||||||
|
RUN apt update && \
|
||||||
|
apt --yes --no-install-recommends install curl && \
|
||||||
|
npm install cheerio semver && \
|
||||||
|
node ./download-apprise.mjs
|
||||||
|
|
||||||
|
# Base Image (Slim)
|
||||||
# If the image changed, the second stage image should be changed too
|
# If the image changed, the second stage image should be changed too
|
||||||
FROM node:20-bookworm-slim AS base2-slim
|
FROM node:20-bookworm-slim AS base2-slim
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
|
@ -27,8 +37,9 @@ RUN apt update && \
|
||||||
# apprise = for notifications (Install from the deb package, as the stable one is too old) (workaround for #4867)
|
# apprise = for notifications (Install from the deb package, as the stable one is too old) (workaround for #4867)
|
||||||
# Switching to testing repo is no longer working, as the testing repo is not bookworm anymore.
|
# Switching to testing repo is no longer working, as the testing repo is not bookworm anymore.
|
||||||
# python3-paho-mqtt (#4859)
|
# python3-paho-mqtt (#4859)
|
||||||
RUN curl http://ftp.debian.org/debian/pool/main/a/apprise/apprise_1.8.0-2_all.deb --output apprise.deb && \
|
# TODO: no idea how to delete the deb file after installation as it becomes a layer already
|
||||||
apt update && \
|
COPY --from=download-apprise /app/apprise.deb ./apprise.deb
|
||||||
|
RUN apt update && \
|
||||||
apt --yes --no-install-recommends install ./apprise.deb python3-paho-mqtt && \
|
apt --yes --no-install-recommends install ./apprise.deb python3-paho-mqtt && \
|
||||||
rm -rf /var/lib/apt/lists/* && \
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
rm -f apprise.deb && \
|
rm -f apprise.deb && \
|
||||||
|
|
27
extra/check-lang-json.js
Normal file
27
extra/check-lang-json.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
// For #5231
|
||||||
|
|
||||||
|
const fs = require("fs");
|
||||||
|
|
||||||
|
let path = "./src/lang";
|
||||||
|
|
||||||
|
// list directories in the lang directory
|
||||||
|
let jsonFileList = fs.readdirSync(path);
|
||||||
|
|
||||||
|
for (let jsonFile of jsonFileList) {
|
||||||
|
if (!jsonFile.endsWith(".json")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let jsonPath = path + "/" + jsonFile;
|
||||||
|
let originalContent = fs.readFileSync(jsonPath, "utf8");
|
||||||
|
let langData = JSON.parse(originalContent);
|
||||||
|
|
||||||
|
let formattedContent = JSON.stringify(langData, null, 4) + "\n";
|
||||||
|
|
||||||
|
if (originalContent !== formattedContent) {
|
||||||
|
console.error(`File ${jsonFile} is not formatted correctly.`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("All lang json files are formatted correctly.");
|
57
extra/download-apprise.mjs
Normal file
57
extra/download-apprise.mjs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
// Go to http://ftp.debian.org/debian/pool/main/a/apprise/ using fetch api, where it is a apache directory listing page
|
||||||
|
// Use cheerio to parse the html and get the latest version of Apprise
|
||||||
|
// call curl to download the latest version of Apprise
|
||||||
|
// Target file: the latest version of Apprise, which the format is apprise_{VERSION}_all.deb
|
||||||
|
|
||||||
|
import * as cheerio from "cheerio";
|
||||||
|
import semver from "semver";
|
||||||
|
import * as childProcess from "child_process";
|
||||||
|
|
||||||
|
const baseURL = "http://ftp.debian.org/debian/pool/main/a/apprise/";
|
||||||
|
const response = await fetch(baseURL);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to fetch page of Apprise Debian repository.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const html = await response.text();
|
||||||
|
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
|
||||||
|
// Get all the links in the page
|
||||||
|
const linkElements = $("a");
|
||||||
|
|
||||||
|
// Filter the links which match apprise_{VERSION}_all.deb
|
||||||
|
const links = [];
|
||||||
|
const pattern = /apprise_(.*?)_all.deb/;
|
||||||
|
|
||||||
|
for (let i = 0; i < linkElements.length; i++) {
|
||||||
|
const link = linkElements[i];
|
||||||
|
if (link.attribs.href.match(pattern) && !link.attribs.href.includes("~")) {
|
||||||
|
links.push({
|
||||||
|
filename: link.attribs.href,
|
||||||
|
version: link.attribs.href.match(pattern)[1],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(links);
|
||||||
|
|
||||||
|
// semver compare and download
|
||||||
|
let latestLink = {
|
||||||
|
filename: "",
|
||||||
|
version: "0.0.0",
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const link of links) {
|
||||||
|
if (semver.gt(link.version, latestLink.version)) {
|
||||||
|
latestLink = link;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadURL = baseURL + latestLink.filename;
|
||||||
|
console.log(`Downloading ${downloadURL}...`);
|
||||||
|
let result = childProcess.spawnSync("curl", [ downloadURL, "--output", "apprise.deb" ]);
|
||||||
|
console.log(result.stdout?.toString());
|
||||||
|
console.error(result.stderr?.toString());
|
||||||
|
process.exit(result.status !== null ? result.status : 1);
|
|
@ -4,7 +4,6 @@ const tar = require("tar");
|
||||||
|
|
||||||
const packageJSON = require("../package.json");
|
const packageJSON = require("../package.json");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const rmSync = require("./fs-rmSync.js");
|
|
||||||
const version = packageJSON.version;
|
const version = packageJSON.version;
|
||||||
|
|
||||||
const filename = "dist.tar.gz";
|
const filename = "dist.tar.gz";
|
||||||
|
@ -29,8 +28,9 @@ function download(url) {
|
||||||
if (fs.existsSync("./dist")) {
|
if (fs.existsSync("./dist")) {
|
||||||
|
|
||||||
if (fs.existsSync("./dist-backup")) {
|
if (fs.existsSync("./dist-backup")) {
|
||||||
rmSync("./dist-backup", {
|
fs.rmSync("./dist-backup", {
|
||||||
recursive: true
|
recursive: true,
|
||||||
|
force: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,8 +43,9 @@ function download(url) {
|
||||||
|
|
||||||
tarStream.on("close", () => {
|
tarStream.on("close", () => {
|
||||||
if (fs.existsSync("./dist-backup")) {
|
if (fs.existsSync("./dist-backup")) {
|
||||||
rmSync("./dist-backup", {
|
fs.rmSync("./dist-backup", {
|
||||||
recursive: true
|
recursive: true,
|
||||||
|
force: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.log("Done");
|
console.log("Done");
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
const fs = require("fs");
|
|
||||||
/**
|
|
||||||
* Detect if `fs.rmSync` is available
|
|
||||||
* to avoid the runtime deprecation warning triggered for using `fs.rmdirSync` with `{ recursive: true }` in Node.js v16,
|
|
||||||
* or the `recursive` property removing completely in the future Node.js version.
|
|
||||||
* See the link below.
|
|
||||||
* @todo Once we drop the support for Node.js v14 (or at least versions before v14.14.0), we can safely replace this function with `fs.rmSync`, since `fs.rmSync` was add in Node.js v14.14.0 and currently we supports all the Node.js v14 versions that include the versions before the v14.14.0, and this function have almost the same signature with `fs.rmSync`.
|
|
||||||
* @link https://nodejs.org/docs/latest-v16.x/api/deprecations.html#dep0147-fsrmdirpath--recursive-true- the deprecation information of `fs.rmdirSync`
|
|
||||||
* @link https://nodejs.org/docs/latest-v16.x/api/fs.html#fsrmsyncpath-options the document of `fs.rmSync`
|
|
||||||
* @param {fs.PathLike} path Valid types for path values in "fs".
|
|
||||||
* @param {fs.RmDirOptions} options options for `fs.rmdirSync`, if `fs.rmSync` is available and property `recursive` is true, it will automatically have property `force` with value `true`.
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
const rmSync = (path, options) => {
|
|
||||||
if (typeof fs.rmSync === "function") {
|
|
||||||
if (options.recursive) {
|
|
||||||
options.force = true;
|
|
||||||
}
|
|
||||||
return fs.rmSync(path, options);
|
|
||||||
}
|
|
||||||
return fs.rmdirSync(path, options);
|
|
||||||
};
|
|
||||||
module.exports = rmSync;
|
|
25
extra/remove-empty-lang-keys.js
Normal file
25
extra/remove-empty-lang-keys.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// For #5231
|
||||||
|
|
||||||
|
const fs = require("fs");
|
||||||
|
|
||||||
|
let path = "../src/lang";
|
||||||
|
|
||||||
|
// list directories in the lang directory
|
||||||
|
let jsonFileList = fs.readdirSync(path);
|
||||||
|
|
||||||
|
for (let jsonFile of jsonFileList) {
|
||||||
|
if (!jsonFile.endsWith(".json")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let jsonPath = path + "/" + jsonFile;
|
||||||
|
let langData = JSON.parse(fs.readFileSync(jsonPath, "utf8"));
|
||||||
|
|
||||||
|
for (let key in langData) {
|
||||||
|
if (langData[key] === "") {
|
||||||
|
delete langData[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(jsonPath, JSON.stringify(langData, null, 4) + "\n");
|
||||||
|
}
|
24
extra/reset-migrate-aggregate-table-state.js
Normal file
24
extra/reset-migrate-aggregate-table-state.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
const { R } = require("redbean-node");
|
||||||
|
const Database = require("../server/database");
|
||||||
|
const args = require("args-parser")(process.argv);
|
||||||
|
const { Settings } = require("../server/settings");
|
||||||
|
|
||||||
|
const main = async () => {
|
||||||
|
console.log("Connecting the database");
|
||||||
|
Database.initDataDir(args);
|
||||||
|
await Database.connect(false, false, true);
|
||||||
|
|
||||||
|
console.log("Deleting all data from aggregate tables");
|
||||||
|
await R.exec("DELETE FROM stat_minutely");
|
||||||
|
await R.exec("DELETE FROM stat_hourly");
|
||||||
|
await R.exec("DELETE FROM stat_daily");
|
||||||
|
|
||||||
|
console.log("Resetting the aggregate table state");
|
||||||
|
await Settings.set("migrateAggregateTableState", "");
|
||||||
|
|
||||||
|
await Database.close();
|
||||||
|
console.log("Done");
|
||||||
|
};
|
||||||
|
|
||||||
|
main();
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import util from "util";
|
import util from "util";
|
||||||
import rmSync from "../fs-rmSync.js";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy across the required language files
|
* Copy across the required language files
|
||||||
|
@ -16,7 +15,10 @@ import rmSync from "../fs-rmSync.js";
|
||||||
*/
|
*/
|
||||||
function copyFiles(langCode, baseLang) {
|
function copyFiles(langCode, baseLang) {
|
||||||
if (fs.existsSync("./languages")) {
|
if (fs.existsSync("./languages")) {
|
||||||
rmSync("./languages", { recursive: true });
|
fs.rmSync("./languages", {
|
||||||
|
recursive: true,
|
||||||
|
force: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
fs.mkdirSync("./languages");
|
fs.mkdirSync("./languages");
|
||||||
|
|
||||||
|
@ -93,6 +95,9 @@ console.log("Updating: " + langCode);
|
||||||
|
|
||||||
copyFiles(langCode, baseLangCode);
|
copyFiles(langCode, baseLangCode);
|
||||||
await updateLanguage(langCode, baseLangCode);
|
await updateLanguage(langCode, baseLangCode);
|
||||||
rmSync("./languages", { recursive: true });
|
fs.rmSync("./languages", {
|
||||||
|
recursive: true,
|
||||||
|
force: true,
|
||||||
|
});
|
||||||
|
|
||||||
console.log("Done. Fixing formatting by ESLint...");
|
console.log("Done. Fixing formatting by ESLint...");
|
||||||
|
|
4574
package-lock.json
generated
4574
package-lock.json
generated
File diff suppressed because it is too large
Load diff
22
package.json
22
package.json
|
@ -38,8 +38,8 @@
|
||||||
"build-docker-base": "docker buildx build -f docker/debian-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base2 --target base2 . --push",
|
"build-docker-base": "docker buildx build -f docker/debian-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base2 --target base2 . --push",
|
||||||
"build-docker-base-slim": "docker buildx build -f docker/debian-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base2-slim --target base2-slim . --push",
|
"build-docker-base-slim": "docker buildx build -f docker/debian-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base2-slim --target base2-slim . --push",
|
||||||
"build-docker-builder-go": "docker buildx build -f docker/builder-go.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:builder-go . --push",
|
"build-docker-builder-go": "docker buildx build -f docker/builder-go.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:builder-go . --push",
|
||||||
"build-docker-slim": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:2-slim -t louislam/uptime-kuma:$VERSION-slim --target release --build-arg BASE_IMAGE=louislam/uptime-kuma:base2-slim . --push",
|
"build-docker-slim": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:next-slim -t louislam/uptime-kuma:2-slim -t louislam/uptime-kuma:$VERSION-slim --target release --build-arg BASE_IMAGE=louislam/uptime-kuma:base2-slim . --push",
|
||||||
"build-docker-full": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:2 -t louislam/uptime-kuma:$VERSION --target release . --push",
|
"build-docker-full": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:next -t louislam/uptime-kuma:2 -t louislam/uptime-kuma:$VERSION --target release . --push",
|
||||||
"build-docker-nightly": "node ./extra/test-docker.js && npm run build && docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly2 --target nightly . --push",
|
"build-docker-nightly": "node ./extra/test-docker.js && npm run build && docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly2 --target nightly . --push",
|
||||||
"build-docker-slim-rootless": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:2-slim-rootless -t louislam/uptime-kuma:$VERSION-slim-rootless --target rootless --build-arg BASE_IMAGE=louislam/uptime-kuma:base2-slim . --push",
|
"build-docker-slim-rootless": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:2-slim-rootless -t louislam/uptime-kuma:$VERSION-slim-rootless --target rootless --build-arg BASE_IMAGE=louislam/uptime-kuma:base2-slim . --push",
|
||||||
"build-docker-full-rootless": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:2-rootless -t louislam/uptime-kuma:$VERSION-rootless --target rootless . --push",
|
"build-docker-full-rootless": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:2-rootless -t louislam/uptime-kuma:$VERSION-rootless --target rootless . --push",
|
||||||
|
@ -47,7 +47,7 @@
|
||||||
"build-docker-nightly-local": "npm run build && docker build -f docker/dockerfile -t louislam/uptime-kuma:nightly2 --target nightly .",
|
"build-docker-nightly-local": "npm run build && docker build -f docker/dockerfile -t louislam/uptime-kuma:nightly2 --target nightly .",
|
||||||
"build-docker-pr-test": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64 -t louislam/uptime-kuma:pr-test2 --target pr-test2 . --push",
|
"build-docker-pr-test": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64 -t louislam/uptime-kuma:pr-test2 --target pr-test2 . --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",
|
"upload-artifacts": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg VERSION --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain",
|
||||||
"setup": "git checkout 1.23.13 && npm ci --production && npm run download-dist",
|
"setup": "git checkout 1.23.14 && npm ci --production && npm run download-dist",
|
||||||
"download-dist": "node extra/download-dist.js",
|
"download-dist": "node extra/download-dist.js",
|
||||||
"mark-as-nightly": "node extra/mark-as-nightly.js",
|
"mark-as-nightly": "node extra/mark-as-nightly.js",
|
||||||
"reset-password": "node extra/reset-password.js",
|
"reset-password": "node extra/reset-password.js",
|
||||||
|
@ -69,10 +69,10 @@
|
||||||
"quick-run-nightly": "docker run --rm --env NODE_ENV=development -p 3001:3001 louislam/uptime-kuma:nightly2",
|
"quick-run-nightly": "docker run --rm --env NODE_ENV=development -p 3001:3001 louislam/uptime-kuma:nightly2",
|
||||||
"start-dev-container": "cd docker && docker-compose -f docker-compose-dev.yml up --force-recreate",
|
"start-dev-container": "cd docker && docker-compose -f docker-compose-dev.yml up --force-recreate",
|
||||||
"rebase-pr-to-1.23.X": "node extra/rebase-pr.js 1.23.X",
|
"rebase-pr-to-1.23.X": "node extra/rebase-pr.js 1.23.X",
|
||||||
"start-server-node14-win": "private\\node14\\node.exe server/server.js"
|
"reset-migrate-aggregate-table-state": "node extra/reset-migrate-aggregate-table-state.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@grpc/grpc-js": "~1.7.3",
|
"@grpc/grpc-js": "~1.8.22",
|
||||||
"@louislam/ping": "~0.4.4-mod.1",
|
"@louislam/ping": "~0.4.4-mod.1",
|
||||||
"@louislam/sqlite3": "15.1.6",
|
"@louislam/sqlite3": "15.1.6",
|
||||||
"@vvo/tzdb": "^6.125.0",
|
"@vvo/tzdb": "^6.125.0",
|
||||||
|
@ -91,8 +91,9 @@
|
||||||
"dayjs": "~1.11.5",
|
"dayjs": "~1.11.5",
|
||||||
"dev-null": "^0.1.1",
|
"dev-null": "^0.1.1",
|
||||||
"dotenv": "~16.0.3",
|
"dotenv": "~16.0.3",
|
||||||
"express": "~4.19.2",
|
"express": "~4.21.0",
|
||||||
"express-basic-auth": "~1.2.1",
|
"express-basic-auth": "~1.2.1",
|
||||||
|
"express-ipfilter": "^1.3.1",
|
||||||
"express-static-gzip": "~2.1.7",
|
"express-static-gzip": "~2.1.7",
|
||||||
"feed": "^4.2.2",
|
"feed": "^4.2.2",
|
||||||
"form-data": "~4.0.0",
|
"form-data": "~4.0.0",
|
||||||
|
@ -138,8 +139,8 @@
|
||||||
"redbean-node": "~0.3.0",
|
"redbean-node": "~0.3.0",
|
||||||
"redis": "~4.5.1",
|
"redis": "~4.5.1",
|
||||||
"semver": "~7.5.4",
|
"semver": "~7.5.4",
|
||||||
"socket.io": "~4.7.5",
|
"socket.io": "~4.8.0",
|
||||||
"socket.io-client": "~4.7.5",
|
"socket.io-client": "~4.8.0",
|
||||||
"socks-proxy-agent": "6.1.1",
|
"socks-proxy-agent": "6.1.1",
|
||||||
"tar": "~6.2.1",
|
"tar": "~6.2.1",
|
||||||
"tcp-ping": "~0.1.1",
|
"tcp-ping": "~0.1.1",
|
||||||
|
@ -155,6 +156,8 @@
|
||||||
"@fortawesome/vue-fontawesome": "~3.0.0-5",
|
"@fortawesome/vue-fontawesome": "~3.0.0-5",
|
||||||
"@playwright/test": "~1.39.0",
|
"@playwright/test": "~1.39.0",
|
||||||
"@popperjs/core": "~2.10.2",
|
"@popperjs/core": "~2.10.2",
|
||||||
|
"@testcontainers/hivemq": "^10.13.1",
|
||||||
|
"@testcontainers/rabbitmq": "^10.13.2",
|
||||||
"@types/bootstrap": "~5.1.9",
|
"@types/bootstrap": "~5.1.9",
|
||||||
"@types/node": "^20.8.6",
|
"@types/node": "^20.8.6",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.7.5",
|
"@typescript-eslint/eslint-plugin": "^6.7.5",
|
||||||
|
@ -172,7 +175,7 @@
|
||||||
"cross-env": "~7.0.3",
|
"cross-env": "~7.0.3",
|
||||||
"delay": "^5.0.0",
|
"delay": "^5.0.0",
|
||||||
"dns2": "~2.0.1",
|
"dns2": "~2.0.1",
|
||||||
"dompurify": "~3.0.11",
|
"dompurify": "~3.1.7",
|
||||||
"eslint": "~8.14.0",
|
"eslint": "~8.14.0",
|
||||||
"eslint-plugin-jsdoc": "~46.4.6",
|
"eslint-plugin-jsdoc": "~46.4.6",
|
||||||
"eslint-plugin-vue": "~8.7.1",
|
"eslint-plugin-vue": "~8.7.1",
|
||||||
|
@ -190,6 +193,7 @@
|
||||||
"stylelint-config-standard": "~25.0.0",
|
"stylelint-config-standard": "~25.0.0",
|
||||||
"terser": "~5.15.0",
|
"terser": "~5.15.0",
|
||||||
"test": "~3.3.0",
|
"test": "~3.3.0",
|
||||||
|
"testcontainers": "^10.13.1",
|
||||||
"typescript": "~4.4.4",
|
"typescript": "~4.4.4",
|
||||||
"v-pagination-3": "~0.1.7",
|
"v-pagination-3": "~0.1.7",
|
||||||
"vite": "~5.2.8",
|
"vite": "~5.2.8",
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const basicAuth = require("express-basic-auth");
|
const basicAuth = require("express-basic-auth");
|
||||||
const passwordHash = require("./password-hash");
|
const passwordHash = require("./password-hash");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
|
const { setting } = require("./util-server");
|
||||||
const { log } = require("../src/util");
|
const { log } = require("../src/util");
|
||||||
const { loginRateLimiter, apiRateLimiter } = require("./rate-limiter");
|
const { loginRateLimiter, apiRateLimiter } = require("./rate-limiter");
|
||||||
const { Settings } = require("./settings");
|
const { Settings } = require("./settings");
|
||||||
|
@ -138,7 +139,7 @@ exports.basicAuth = async function (req, res, next) {
|
||||||
challenge: true,
|
challenge: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const disabledAuth = await Settings.get("disableAuth");
|
const disabledAuth = await setting("disableAuth");
|
||||||
|
|
||||||
if (!disabledAuth) {
|
if (!disabledAuth) {
|
||||||
middleware(req, res, next);
|
middleware(req, res, next);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
const { setSetting, setting } = require("./util-server");
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
const compareVersions = require("compare-versions");
|
const compareVersions = require("compare-versions");
|
||||||
const { log } = require("../src/util");
|
const { log } = require("../src/util");
|
||||||
const { Settings } = require("./settings");
|
|
||||||
|
|
||||||
exports.version = require("../package.json").version;
|
exports.version = require("../package.json").version;
|
||||||
exports.latestVersion = null;
|
exports.latestVersion = null;
|
||||||
|
@ -14,7 +14,7 @@ let interval;
|
||||||
|
|
||||||
exports.startInterval = () => {
|
exports.startInterval = () => {
|
||||||
let check = async () => {
|
let check = async () => {
|
||||||
if (await Settings.get("checkUpdate") === false) {
|
if (await setting("checkUpdate") === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ exports.startInterval = () => {
|
||||||
res.data.slow = "1000.0.0";
|
res.data.slow = "1000.0.0";
|
||||||
}
|
}
|
||||||
|
|
||||||
let checkBeta = await Settings.get("checkBeta");
|
let checkBeta = await setting("checkBeta");
|
||||||
|
|
||||||
if (checkBeta && res.data.beta) {
|
if (checkBeta && res.data.beta) {
|
||||||
if (compareVersions.compare(res.data.beta, res.data.slow, ">")) {
|
if (compareVersions.compare(res.data.beta, res.data.slow, ">")) {
|
||||||
|
@ -57,7 +57,7 @@ exports.startInterval = () => {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
exports.enableCheckUpdate = async (value) => {
|
exports.enableCheckUpdate = async (value) => {
|
||||||
await Settings.set("checkUpdate", value);
|
await setSetting("checkUpdate", value);
|
||||||
|
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,8 @@ const { R } = require("redbean-node");
|
||||||
const { UptimeKumaServer } = require("./uptime-kuma-server");
|
const { UptimeKumaServer } = require("./uptime-kuma-server");
|
||||||
const server = UptimeKumaServer.getInstance();
|
const server = UptimeKumaServer.getInstance();
|
||||||
const io = server.io;
|
const io = server.io;
|
||||||
|
const { setting } = require("./util-server");
|
||||||
const checkVersion = require("./check-version");
|
const checkVersion = require("./check-version");
|
||||||
const { Settings } = require("./settings");
|
|
||||||
const Database = require("./database");
|
const Database = require("./database");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -158,8 +158,8 @@ async function sendInfo(socket, hideVersion = false) {
|
||||||
version,
|
version,
|
||||||
latestVersion,
|
latestVersion,
|
||||||
isContainer,
|
isContainer,
|
||||||
primaryBaseURL: await Settings.get("primaryBaseURL"),
|
|
||||||
dbType,
|
dbType,
|
||||||
|
primaryBaseURL: await setting("primaryBaseURL"),
|
||||||
serverTimezone: await server.getTimezone(),
|
serverTimezone: await server.getTimezone(),
|
||||||
serverTimezoneOffset: server.getTimezoneOffset(),
|
serverTimezoneOffset: server.getTimezoneOffset(),
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
|
const { setSetting, setting } = require("./util-server");
|
||||||
const { log, sleep } = require("../src/util");
|
const { log, sleep } = require("../src/util");
|
||||||
const knex = require("knex");
|
const knex = require("knex");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const { EmbeddedMariaDB } = require("./embedded-mariadb");
|
const { EmbeddedMariaDB } = require("./embedded-mariadb");
|
||||||
const mysql = require("mysql2/promise");
|
const mysql = require("mysql2/promise");
|
||||||
const { Settings } = require("./settings");
|
const { Settings } = require("./settings");
|
||||||
|
const { UptimeCalculator } = require("./uptime-calculator");
|
||||||
|
const dayjs = require("dayjs");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Database & App Data Folder
|
* Database & App Data Folder
|
||||||
|
@ -391,9 +394,23 @@ class Database {
|
||||||
// https://knexjs.org/guide/migrations.html
|
// https://knexjs.org/guide/migrations.html
|
||||||
// https://gist.github.com/NigelEarle/70db130cc040cc2868555b29a0278261
|
// https://gist.github.com/NigelEarle/70db130cc040cc2868555b29a0278261
|
||||||
try {
|
try {
|
||||||
|
// Disable foreign key check for SQLite
|
||||||
|
// Known issue of knex: https://github.com/drizzle-team/drizzle-orm/issues/1813
|
||||||
|
if (Database.dbConfig.type === "sqlite") {
|
||||||
|
await R.exec("PRAGMA foreign_keys = OFF");
|
||||||
|
}
|
||||||
|
|
||||||
await R.knex.migrate.latest({
|
await R.knex.migrate.latest({
|
||||||
directory: Database.knexMigrationsPath,
|
directory: Database.knexMigrationsPath,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Enable foreign key check for SQLite
|
||||||
|
if (Database.dbConfig.type === "sqlite") {
|
||||||
|
await R.exec("PRAGMA foreign_keys = ON");
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.migrateAggregateTable();
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Allow missing patch files for downgrade or testing pr.
|
// Allow missing patch files for downgrade or testing pr.
|
||||||
if (e.message.includes("the following files are missing:")) {
|
if (e.message.includes("the following files are missing:")) {
|
||||||
|
@ -420,7 +437,7 @@ class Database {
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
static async patchSqlite() {
|
static async patchSqlite() {
|
||||||
let version = parseInt(await Settings.get("database_version"));
|
let version = parseInt(await setting("database_version"));
|
||||||
|
|
||||||
if (! version) {
|
if (! version) {
|
||||||
version = 0;
|
version = 0;
|
||||||
|
@ -445,7 +462,7 @@ class Database {
|
||||||
log.info("db", `Patching ${sqlFile}`);
|
log.info("db", `Patching ${sqlFile}`);
|
||||||
await Database.importSQLFile(sqlFile);
|
await Database.importSQLFile(sqlFile);
|
||||||
log.info("db", `Patched ${sqlFile}`);
|
log.info("db", `Patched ${sqlFile}`);
|
||||||
await Settings.set("database_version", i);
|
await setSetting("database_version", i);
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
await Database.close();
|
await Database.close();
|
||||||
|
@ -471,7 +488,7 @@ class Database {
|
||||||
*/
|
*/
|
||||||
static async patchSqlite2() {
|
static async patchSqlite2() {
|
||||||
log.debug("db", "Database Patch 2.0 Process");
|
log.debug("db", "Database Patch 2.0 Process");
|
||||||
let databasePatchedFiles = await Settings.get("databasePatchedFiles");
|
let databasePatchedFiles = await setting("databasePatchedFiles");
|
||||||
|
|
||||||
if (! databasePatchedFiles) {
|
if (! databasePatchedFiles) {
|
||||||
databasePatchedFiles = {};
|
databasePatchedFiles = {};
|
||||||
|
@ -499,7 +516,7 @@ class Database {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
await Settings.set("databasePatchedFiles", databasePatchedFiles);
|
await setSetting("databasePatchedFiles", databasePatchedFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -512,27 +529,27 @@ class Database {
|
||||||
// Fix 1.13.0 empty slug bug
|
// Fix 1.13.0 empty slug bug
|
||||||
await R.exec("UPDATE status_page SET slug = 'empty-slug-recover' WHERE TRIM(slug) = ''");
|
await R.exec("UPDATE status_page SET slug = 'empty-slug-recover' WHERE TRIM(slug) = ''");
|
||||||
|
|
||||||
let title = await Settings.get("title");
|
let title = await setting("title");
|
||||||
|
|
||||||
if (title) {
|
if (title) {
|
||||||
log.info("database", "Migrating Status Page");
|
console.log("Migrating Status Page");
|
||||||
|
|
||||||
let statusPageCheck = await R.findOne("status_page", " slug = 'default' ");
|
let statusPageCheck = await R.findOne("status_page", " slug = 'default' ");
|
||||||
|
|
||||||
if (statusPageCheck !== null) {
|
if (statusPageCheck !== null) {
|
||||||
log.info("database", "Migrating Status Page - Skip, default slug record is already existing");
|
console.log("Migrating Status Page - Skip, default slug record is already existing");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let statusPage = R.dispense("status_page");
|
let statusPage = R.dispense("status_page");
|
||||||
statusPage.slug = "default";
|
statusPage.slug = "default";
|
||||||
statusPage.title = title;
|
statusPage.title = title;
|
||||||
statusPage.description = await Settings.get("description");
|
statusPage.description = await setting("description");
|
||||||
statusPage.icon = await Settings.get("icon");
|
statusPage.icon = await setting("icon");
|
||||||
statusPage.theme = await Settings.get("statusPageTheme");
|
statusPage.theme = await setting("statusPageTheme");
|
||||||
statusPage.published = !!await Settings.get("statusPagePublished");
|
statusPage.published = !!await setting("statusPagePublished");
|
||||||
statusPage.search_engine_index = !!await Settings.get("searchEngineIndex");
|
statusPage.search_engine_index = !!await setting("searchEngineIndex");
|
||||||
statusPage.show_tags = !!await Settings.get("statusPageTags");
|
statusPage.show_tags = !!await setting("statusPageTags");
|
||||||
statusPage.password = null;
|
statusPage.password = null;
|
||||||
|
|
||||||
if (!statusPage.title) {
|
if (!statusPage.title) {
|
||||||
|
@ -560,13 +577,13 @@ class Database {
|
||||||
await R.exec("DELETE FROM setting WHERE type = 'statusPage'");
|
await R.exec("DELETE FROM setting WHERE type = 'statusPage'");
|
||||||
|
|
||||||
// Migrate Entry Page if it is status page
|
// Migrate Entry Page if it is status page
|
||||||
let entryPage = await Settings.get("entryPage");
|
let entryPage = await setting("entryPage");
|
||||||
|
|
||||||
if (entryPage === "statusPage") {
|
if (entryPage === "statusPage") {
|
||||||
await Settings.set("entryPage", "statusPage-default", "general");
|
await setSetting("entryPage", "statusPage-default", "general");
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("database", "Migrating Status Page - Done");
|
console.log("Migrating Status Page - Done");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -711,6 +728,152 @@ class Database {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrate the old data in the heartbeat table to the new format (stat_daily, stat_hourly, stat_minutely)
|
||||||
|
* It should be run once while upgrading V1 to V2
|
||||||
|
*
|
||||||
|
* Normally, it should be in transaction, but UptimeCalculator wasn't designed to be in transaction before that.
|
||||||
|
* I don't want to heavily modify the UptimeCalculator, so it is not in transaction.
|
||||||
|
* Run `npm run reset-migrate-aggregate-table-state` to reset, in case the migration is interrupted.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
static async migrateAggregateTable() {
|
||||||
|
log.debug("db", "Enter Migrate Aggregate Table function");
|
||||||
|
|
||||||
|
// Add a setting for 2.0.0-dev users to skip this migration
|
||||||
|
if (process.env.SET_MIGRATE_AGGREGATE_TABLE_TO_TRUE === "1") {
|
||||||
|
log.warn("db", "SET_MIGRATE_AGGREGATE_TABLE_TO_TRUE is set to 1, skipping aggregate table migration forever (for 2.0.0-dev users)");
|
||||||
|
await Settings.set("migrateAggregateTableState", "migrated");
|
||||||
|
}
|
||||||
|
|
||||||
|
let migrateState = await Settings.get("migrateAggregateTableState");
|
||||||
|
|
||||||
|
// Skip if already migrated
|
||||||
|
// If it is migrating, it possibly means the migration was interrupted, or the migration is in progress
|
||||||
|
if (migrateState === "migrated") {
|
||||||
|
log.debug("db", "Migrated aggregate table already, skip");
|
||||||
|
return;
|
||||||
|
} else if (migrateState === "migrating") {
|
||||||
|
log.warn("db", "Aggregate table migration is already in progress, or it was interrupted");
|
||||||
|
throw new Error("Aggregate table migration is already in progress");
|
||||||
|
}
|
||||||
|
|
||||||
|
await Settings.set("migrateAggregateTableState", "migrating");
|
||||||
|
|
||||||
|
log.info("db", "Migrating Aggregate Table");
|
||||||
|
|
||||||
|
log.info("db", "Getting list of unique monitors");
|
||||||
|
|
||||||
|
// Get a list of unique monitors from the heartbeat table, using raw sql
|
||||||
|
let monitors = await R.getAll(`
|
||||||
|
SELECT DISTINCT monitor_id
|
||||||
|
FROM heartbeat
|
||||||
|
ORDER BY monitor_id ASC
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Stop if stat_* tables are not empty
|
||||||
|
for (let table of [ "stat_minutely", "stat_hourly", "stat_daily" ]) {
|
||||||
|
let countResult = await R.getRow(`SELECT COUNT(*) AS count FROM ${table}`);
|
||||||
|
let count = countResult.count;
|
||||||
|
if (count > 0) {
|
||||||
|
log.warn("db", `Aggregate table ${table} is not empty, migration will not be started (Maybe you were using 2.0.0-dev?)`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let progressPercent = 0;
|
||||||
|
let part = 100 / monitors.length;
|
||||||
|
let i = 1;
|
||||||
|
for (let monitor of monitors) {
|
||||||
|
// Get a list of unique dates from the heartbeat table, using raw sql
|
||||||
|
let dates = await R.getAll(`
|
||||||
|
SELECT DISTINCT DATE(time) AS date
|
||||||
|
FROM heartbeat
|
||||||
|
WHERE monitor_id = ?
|
||||||
|
ORDER BY date ASC
|
||||||
|
`, [
|
||||||
|
monitor.monitor_id
|
||||||
|
]);
|
||||||
|
|
||||||
|
for (let date of dates) {
|
||||||
|
// New Uptime Calculator
|
||||||
|
let calculator = new UptimeCalculator();
|
||||||
|
calculator.monitorID = monitor.monitor_id;
|
||||||
|
calculator.setMigrationMode(true);
|
||||||
|
|
||||||
|
// Get all the heartbeats for this monitor and date
|
||||||
|
let heartbeats = await R.getAll(`
|
||||||
|
SELECT status, ping, time
|
||||||
|
FROM heartbeat
|
||||||
|
WHERE monitor_id = ?
|
||||||
|
AND DATE(time) = ?
|
||||||
|
ORDER BY time ASC
|
||||||
|
`, [ monitor.monitor_id, date.date ]);
|
||||||
|
|
||||||
|
if (heartbeats.length > 0) {
|
||||||
|
log.info("db", `[DON'T STOP] Migrating monitor data ${monitor.monitor_id} - ${date.date} [${progressPercent.toFixed(2)}%][${i}/${monitors.length}]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let heartbeat of heartbeats) {
|
||||||
|
await calculator.update(heartbeat.status, parseFloat(heartbeat.ping), dayjs(heartbeat.time));
|
||||||
|
}
|
||||||
|
|
||||||
|
progressPercent += (Math.round(part / dates.length * 100) / 100);
|
||||||
|
|
||||||
|
// Lazy to fix the floating point issue, it is acceptable since it is just a progress bar
|
||||||
|
if (progressPercent > 100) {
|
||||||
|
progressPercent = 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Database.clearHeartbeatData(true);
|
||||||
|
|
||||||
|
await Settings.set("migrateAggregateTableState", "migrated");
|
||||||
|
|
||||||
|
if (monitors.length > 0) {
|
||||||
|
log.info("db", "Aggregate Table Migration Completed");
|
||||||
|
} else {
|
||||||
|
log.info("db", "No data to migrate");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all non-important heartbeats from heartbeat table, keep last 24-hour or {KEEP_LAST_ROWS} rows for each monitor
|
||||||
|
* @param {boolean} detailedLog Log detailed information
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
static async clearHeartbeatData(detailedLog = false) {
|
||||||
|
let monitors = await R.getAll("SELECT id FROM monitor");
|
||||||
|
const sqlHourOffset = Database.sqlHourOffset();
|
||||||
|
|
||||||
|
for (let monitor of monitors) {
|
||||||
|
if (detailedLog) {
|
||||||
|
log.info("db", "Deleting non-important heartbeats for monitor " + monitor.id);
|
||||||
|
}
|
||||||
|
await R.exec(`
|
||||||
|
DELETE FROM heartbeat
|
||||||
|
WHERE monitor_id = ?
|
||||||
|
AND important = 0
|
||||||
|
AND time < ${sqlHourOffset}
|
||||||
|
AND id NOT IN (
|
||||||
|
SELECT id
|
||||||
|
FROM heartbeat
|
||||||
|
WHERE monitor_id = ?
|
||||||
|
ORDER BY time DESC
|
||||||
|
LIMIT ?
|
||||||
|
)
|
||||||
|
`, [
|
||||||
|
monitor.id,
|
||||||
|
-24,
|
||||||
|
monitor.id,
|
||||||
|
100,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Database;
|
module.exports = Database;
|
||||||
|
|
|
@ -2,15 +2,16 @@ const { R } = require("redbean-node");
|
||||||
const { log } = require("../../src/util");
|
const { log } = require("../../src/util");
|
||||||
const Database = require("../database");
|
const Database = require("../database");
|
||||||
const { Settings } = require("../settings");
|
const { Settings } = require("../settings");
|
||||||
|
const dayjs = require("dayjs");
|
||||||
|
|
||||||
const DEFAULT_KEEP_PERIOD = 180;
|
const DEFAULT_KEEP_PERIOD = 365;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears old data from the heartbeat table of the database.
|
* Clears old data from the heartbeat table and the stat_daily of the database.
|
||||||
* @returns {Promise<void>} A promise that resolves when the data has been cleared.
|
* @returns {Promise<void>} A promise that resolves when the data has been cleared.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const clearOldData = async () => {
|
const clearOldData = async () => {
|
||||||
|
await Database.clearHeartbeatData();
|
||||||
let period = await Settings.get("keepDataPeriodDays");
|
let period = await Settings.get("keepDataPeriodDays");
|
||||||
|
|
||||||
// Set Default Period
|
// Set Default Period
|
||||||
|
@ -32,16 +33,21 @@ const clearOldData = async () => {
|
||||||
if (parsedPeriod < 1) {
|
if (parsedPeriod < 1) {
|
||||||
log.info("clearOldData", `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 {
|
} else {
|
||||||
|
|
||||||
log.debug("clearOldData", `Clearing Data older than ${parsedPeriod} days...`);
|
log.debug("clearOldData", `Clearing Data older than ${parsedPeriod} days...`);
|
||||||
|
|
||||||
const sqlHourOffset = Database.sqlHourOffset();
|
const sqlHourOffset = Database.sqlHourOffset();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await R.exec(
|
// Heartbeat
|
||||||
"DELETE FROM heartbeat WHERE time < " + sqlHourOffset,
|
await R.exec("DELETE FROM heartbeat WHERE time < " + sqlHourOffset, [
|
||||||
[ parsedPeriod * -24 ]
|
parsedPeriod * -24,
|
||||||
);
|
]);
|
||||||
|
|
||||||
|
let timestamp = dayjs().subtract(parsedPeriod, "day").utc().startOf("day").unix();
|
||||||
|
|
||||||
|
// stat_daily
|
||||||
|
await R.exec("DELETE FROM stat_daily WHERE timestamp < ? ", [
|
||||||
|
timestamp,
|
||||||
|
]);
|
||||||
|
|
||||||
if (Database.dbConfig.type === "sqlite") {
|
if (Database.dbConfig.type === "sqlite") {
|
||||||
await R.exec("PRAGMA optimize;");
|
await R.exec("PRAGMA optimize;");
|
||||||
|
@ -50,6 +56,8 @@ const clearOldData = async () => {
|
||||||
log.error("clearOldData", `Failed to clear old data: ${e.message}`);
|
log.error("clearOldData", `Failed to clear old data: ${e.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.debug("clearOldData", "Data cleared.");
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
|
@ -4,7 +4,7 @@ const { Prometheus } = require("../prometheus");
|
||||||
const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND,
|
const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND,
|
||||||
SQL_DATETIME_FORMAT, evaluateJsonQuery
|
SQL_DATETIME_FORMAT, evaluateJsonQuery
|
||||||
} = require("../../src/util");
|
} = require("../../src/util");
|
||||||
const { tcping, ping, checkCertificate, checkStatusCode, getTotalClientInRoom, mssqlQuery, postgresQuery, mysqlQuery, httpNtlm, radius, grpcQuery,
|
const { tcping, ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, setSetting, httpNtlm, radius, grpcQuery,
|
||||||
redisPingAsync, kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal
|
redisPingAsync, kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal
|
||||||
} = require("../util-server");
|
} = require("../util-server");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
|
@ -24,7 +24,6 @@ const { CookieJar } = require("tough-cookie");
|
||||||
const { HttpsCookieAgent } = require("http-cookie-agent/http");
|
const { HttpsCookieAgent } = require("http-cookie-agent/http");
|
||||||
const https = require("https");
|
const https = require("https");
|
||||||
const http = require("http");
|
const http = require("http");
|
||||||
const { Settings } = require("../settings");
|
|
||||||
|
|
||||||
const rootCertificates = rootCertificatesFingerprints();
|
const rootCertificates = rootCertificatesFingerprints();
|
||||||
|
|
||||||
|
@ -72,23 +71,12 @@ class Monitor extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an object that ready to parse to JSON
|
* Return an object that ready to parse to JSON
|
||||||
|
* @param {object} preloadData to prevent n+1 problems, we query the data in a batch outside of this function
|
||||||
* @param {boolean} includeSensitiveData Include sensitive data in
|
* @param {boolean} includeSensitiveData Include sensitive data in
|
||||||
* JSON
|
* JSON
|
||||||
* @returns {Promise<object>} Object ready to parse
|
* @returns {object} Object ready to parse
|
||||||
*/
|
*/
|
||||||
async toJSON(includeSensitiveData = true) {
|
toJSON(preloadData = {}, includeSensitiveData = true) {
|
||||||
|
|
||||||
let notificationIDList = {};
|
|
||||||
|
|
||||||
let list = await R.find("monitor_notification", " monitor_id = ? ", [
|
|
||||||
this.id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
for (let bean of list) {
|
|
||||||
notificationIDList[bean.notification_id] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tags = await this.getTags();
|
|
||||||
|
|
||||||
let screenshot = null;
|
let screenshot = null;
|
||||||
|
|
||||||
|
@ -96,7 +84,7 @@ class Monitor extends BeanModel {
|
||||||
screenshot = "/screenshots/" + jwt.sign(this.id, UptimeKumaServer.getInstance().jwtSecret) + ".png";
|
screenshot = "/screenshots/" + jwt.sign(this.id, UptimeKumaServer.getInstance().jwtSecret) + ".png";
|
||||||
}
|
}
|
||||||
|
|
||||||
const path = await this.getPath();
|
const path = preloadData.paths.get(this.id) || [];
|
||||||
const pathName = path.join(" / ");
|
const pathName = path.join(" / ");
|
||||||
|
|
||||||
let data = {
|
let data = {
|
||||||
|
@ -106,15 +94,15 @@ class Monitor extends BeanModel {
|
||||||
path,
|
path,
|
||||||
pathName,
|
pathName,
|
||||||
parent: this.parent,
|
parent: this.parent,
|
||||||
childrenIDs: await Monitor.getAllChildrenIDs(this.id),
|
childrenIDs: preloadData.childrenIDs.get(this.id) || [],
|
||||||
url: this.url,
|
url: this.url,
|
||||||
method: this.method,
|
method: this.method,
|
||||||
hostname: this.hostname,
|
hostname: this.hostname,
|
||||||
port: this.port,
|
port: this.port,
|
||||||
maxretries: this.maxretries,
|
maxretries: this.maxretries,
|
||||||
weight: this.weight,
|
weight: this.weight,
|
||||||
active: await this.isActive(),
|
active: preloadData.activeStatus.get(this.id),
|
||||||
forceInactive: !await Monitor.isParentActive(this.id),
|
forceInactive: preloadData.forceInactive.get(this.id),
|
||||||
type: this.type,
|
type: this.type,
|
||||||
timeout: this.timeout,
|
timeout: this.timeout,
|
||||||
interval: this.interval,
|
interval: this.interval,
|
||||||
|
@ -134,9 +122,9 @@ class Monitor extends BeanModel {
|
||||||
docker_container: this.docker_container,
|
docker_container: this.docker_container,
|
||||||
docker_host: this.docker_host,
|
docker_host: this.docker_host,
|
||||||
proxyId: this.proxy_id,
|
proxyId: this.proxy_id,
|
||||||
notificationIDList,
|
notificationIDList: preloadData.notifications.get(this.id) || {},
|
||||||
tags: tags,
|
tags: preloadData.tags.get(this.id) || [],
|
||||||
maintenance: await Monitor.isUnderMaintenance(this.id),
|
maintenance: preloadData.maintenanceStatus.get(this.id),
|
||||||
mqttTopic: this.mqttTopic,
|
mqttTopic: this.mqttTopic,
|
||||||
mqttSuccessMessage: this.mqttSuccessMessage,
|
mqttSuccessMessage: this.mqttSuccessMessage,
|
||||||
mqttCheckType: this.mqttCheckType,
|
mqttCheckType: this.mqttCheckType,
|
||||||
|
@ -165,6 +153,7 @@ class Monitor extends BeanModel {
|
||||||
snmpOid: this.snmpOid,
|
snmpOid: this.snmpOid,
|
||||||
jsonPathOperator: this.jsonPathOperator,
|
jsonPathOperator: this.jsonPathOperator,
|
||||||
snmpVersion: this.snmpVersion,
|
snmpVersion: this.snmpVersion,
|
||||||
|
rabbitmqNodes: JSON.parse(this.rabbitmqNodes),
|
||||||
conditions: JSON.parse(this.conditions),
|
conditions: JSON.parse(this.conditions),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -195,6 +184,8 @@ class Monitor extends BeanModel {
|
||||||
tlsCert: this.tlsCert,
|
tlsCert: this.tlsCert,
|
||||||
tlsKey: this.tlsKey,
|
tlsKey: this.tlsKey,
|
||||||
kafkaProducerSaslOptions: JSON.parse(this.kafkaProducerSaslOptions),
|
kafkaProducerSaslOptions: JSON.parse(this.kafkaProducerSaslOptions),
|
||||||
|
rabbitmqUsername: this.rabbitmqUsername,
|
||||||
|
rabbitmqPassword: this.rabbitmqPassword,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,16 +193,6 @@ class Monitor extends BeanModel {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the monitor is active based on itself and its parents
|
|
||||||
* @returns {Promise<boolean>} Is the monitor active?
|
|
||||||
*/
|
|
||||||
async isActive() {
|
|
||||||
const parentActive = await Monitor.isParentActive(this.id);
|
|
||||||
|
|
||||||
return (this.active === 1) && parentActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all tags applied to this monitor
|
* Get all tags applied to this monitor
|
||||||
* @returns {Promise<LooseObject<any>[]>} List of tags on the
|
* @returns {Promise<LooseObject<any>[]>} List of tags on the
|
||||||
|
@ -347,7 +328,7 @@ class Monitor extends BeanModel {
|
||||||
let previousBeat = null;
|
let previousBeat = null;
|
||||||
let retries = 0;
|
let retries = 0;
|
||||||
|
|
||||||
this.prometheus = await Prometheus.createAndInitMetrics(this);
|
this.prometheus = new Prometheus(this);
|
||||||
|
|
||||||
const beat = async () => {
|
const beat = async () => {
|
||||||
|
|
||||||
|
@ -673,7 +654,7 @@ class Monitor extends BeanModel {
|
||||||
|
|
||||||
} else if (this.type === "steam") {
|
} else if (this.type === "steam") {
|
||||||
const steamApiUrl = "https://api.steampowered.com/IGameServersService/GetServerList/v1/";
|
const steamApiUrl = "https://api.steampowered.com/IGameServersService/GetServerList/v1/";
|
||||||
const steamAPIKey = await Settings.get("steamAPIKey");
|
const steamAPIKey = await setting("steamAPIKey");
|
||||||
const filter = `addr\\${this.hostname}:${this.port}`;
|
const filter = `addr\\${this.hostname}:${this.port}`;
|
||||||
|
|
||||||
if (!steamAPIKey) {
|
if (!steamAPIKey) {
|
||||||
|
@ -999,7 +980,7 @@ class Monitor extends BeanModel {
|
||||||
await R.store(bean);
|
await R.store(bean);
|
||||||
|
|
||||||
log.debug("monitor", `[${this.name}] prometheus.update`);
|
log.debug("monitor", `[${this.name}] prometheus.update`);
|
||||||
await this.prometheus?.update(bean, tlsInfo);
|
this.prometheus?.update(bean, tlsInfo);
|
||||||
|
|
||||||
previousBeat = bean;
|
previousBeat = bean;
|
||||||
|
|
||||||
|
@ -1197,6 +1178,18 @@ class Monitor extends BeanModel {
|
||||||
return checkCertificateResult;
|
return checkCertificateResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the monitor is active based on itself and its parents
|
||||||
|
* @param {number} monitorID ID of monitor to send
|
||||||
|
* @param {boolean} active is active
|
||||||
|
* @returns {Promise<boolean>} Is the monitor active?
|
||||||
|
*/
|
||||||
|
static async isActive(monitorID, active) {
|
||||||
|
const parentActive = await Monitor.isParentActive(monitorID);
|
||||||
|
|
||||||
|
return (active === 1) && parentActive;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send statistics to clients
|
* Send statistics to clients
|
||||||
* @param {Server} io Socket server instance
|
* @param {Server} io Socket server instance
|
||||||
|
@ -1333,7 +1326,10 @@ class Monitor extends BeanModel {
|
||||||
for (let notification of notificationList) {
|
for (let notification of notificationList) {
|
||||||
try {
|
try {
|
||||||
const heartbeatJSON = bean.toJSON();
|
const heartbeatJSON = bean.toJSON();
|
||||||
|
const monitorData = [{ id: monitor.id,
|
||||||
|
active: monitor.active
|
||||||
|
}];
|
||||||
|
const preloadData = await Monitor.preparePreloadData(monitorData);
|
||||||
// Prevent if the msg is undefined, notifications such as Discord cannot send out.
|
// Prevent if the msg is undefined, notifications such as Discord cannot send out.
|
||||||
if (!heartbeatJSON["msg"]) {
|
if (!heartbeatJSON["msg"]) {
|
||||||
heartbeatJSON["msg"] = "N/A";
|
heartbeatJSON["msg"] = "N/A";
|
||||||
|
@ -1344,7 +1340,7 @@ class Monitor extends BeanModel {
|
||||||
heartbeatJSON["timezoneOffset"] = UptimeKumaServer.getInstance().getTimezoneOffset();
|
heartbeatJSON["timezoneOffset"] = UptimeKumaServer.getInstance().getTimezoneOffset();
|
||||||
heartbeatJSON["localDateTime"] = dayjs.utc(heartbeatJSON["time"]).tz(heartbeatJSON["timezone"]).format(SQL_DATETIME_FORMAT);
|
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);
|
await Notification.send(JSON.parse(notification.config), msg, monitor.toJSON(preloadData, false), heartbeatJSON);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("monitor", "Cannot send notification to " + notification.name);
|
log.error("monitor", "Cannot send notification to " + notification.name);
|
||||||
log.error("monitor", e);
|
log.error("monitor", e);
|
||||||
|
@ -1380,12 +1376,11 @@ class Monitor extends BeanModel {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let notifyDays = await Settings.get("tlsExpiryNotifyDays");
|
let notifyDays = await setting("tlsExpiryNotifyDays");
|
||||||
if (notifyDays == null || !Array.isArray(notifyDays)) {
|
if (notifyDays == null || !Array.isArray(notifyDays)) {
|
||||||
// Reset Default
|
// Reset Default
|
||||||
await Settings.set("tlsExpiryNotifyDays", [ 7, 14, 21 ], "general");
|
await setSetting("tlsExpiryNotifyDays", [ 7, 14, 21 ], "general");
|
||||||
notifyDays = [ 7, 14, 21 ];
|
notifyDays = [ 7, 14, 21 ];
|
||||||
await Settings.set("tlsExpiryNotifyDays", notifyDays, "general");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(notifyDays)) {
|
if (Array.isArray(notifyDays)) {
|
||||||
|
@ -1507,6 +1502,108 @@ class Monitor extends BeanModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets monitor notification of multiple monitor
|
||||||
|
* @param {Array} monitorIDs IDs of monitor to get
|
||||||
|
* @returns {Promise<LooseObject<any>>} object
|
||||||
|
*/
|
||||||
|
static async getMonitorNotification(monitorIDs) {
|
||||||
|
return await R.getAll(`
|
||||||
|
SELECT monitor_notification.monitor_id, monitor_notification.notification_id
|
||||||
|
FROM monitor_notification
|
||||||
|
WHERE monitor_notification.monitor_id IN (${monitorIDs.map((_) => "?").join(",")})
|
||||||
|
`, monitorIDs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets monitor tags of multiple monitor
|
||||||
|
* @param {Array} monitorIDs IDs of monitor to get
|
||||||
|
* @returns {Promise<LooseObject<any>>} object
|
||||||
|
*/
|
||||||
|
static async getMonitorTag(monitorIDs) {
|
||||||
|
return await R.getAll(`
|
||||||
|
SELECT monitor_tag.monitor_id, monitor_tag.tag_id, tag.name, tag.color
|
||||||
|
FROM monitor_tag
|
||||||
|
JOIN tag ON monitor_tag.tag_id = tag.id
|
||||||
|
WHERE monitor_tag.monitor_id IN (${monitorIDs.map((_) => "?").join(",")})
|
||||||
|
`, monitorIDs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* prepare preloaded data for efficient access
|
||||||
|
* @param {Array} monitorData IDs & active field of monitor to get
|
||||||
|
* @returns {Promise<LooseObject<any>>} object
|
||||||
|
*/
|
||||||
|
static async preparePreloadData(monitorData) {
|
||||||
|
|
||||||
|
const notificationsMap = new Map();
|
||||||
|
const tagsMap = new Map();
|
||||||
|
const maintenanceStatusMap = new Map();
|
||||||
|
const childrenIDsMap = new Map();
|
||||||
|
const activeStatusMap = new Map();
|
||||||
|
const forceInactiveMap = new Map();
|
||||||
|
const pathsMap = new Map();
|
||||||
|
|
||||||
|
if (monitorData.length > 0) {
|
||||||
|
const monitorIDs = monitorData.map(monitor => monitor.id);
|
||||||
|
const notifications = await Monitor.getMonitorNotification(monitorIDs);
|
||||||
|
const tags = await Monitor.getMonitorTag(monitorIDs);
|
||||||
|
const maintenanceStatuses = await Promise.all(monitorData.map(monitor => Monitor.isUnderMaintenance(monitor.id)));
|
||||||
|
const childrenIDs = await Promise.all(monitorData.map(monitor => Monitor.getAllChildrenIDs(monitor.id)));
|
||||||
|
const activeStatuses = await Promise.all(monitorData.map(monitor => Monitor.isActive(monitor.id, monitor.active)));
|
||||||
|
const forceInactiveStatuses = await Promise.all(monitorData.map(monitor => Monitor.isParentActive(monitor.id)));
|
||||||
|
const paths = await Promise.all(monitorData.map(monitor => Monitor.getAllPath(monitor.id, monitor.name)));
|
||||||
|
|
||||||
|
notifications.forEach(row => {
|
||||||
|
if (!notificationsMap.has(row.monitor_id)) {
|
||||||
|
notificationsMap.set(row.monitor_id, {});
|
||||||
|
}
|
||||||
|
notificationsMap.get(row.monitor_id)[row.notification_id] = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
tags.forEach(row => {
|
||||||
|
if (!tagsMap.has(row.monitor_id)) {
|
||||||
|
tagsMap.set(row.monitor_id, []);
|
||||||
|
}
|
||||||
|
tagsMap.get(row.monitor_id).push({
|
||||||
|
tag_id: row.tag_id,
|
||||||
|
name: row.name,
|
||||||
|
color: row.color
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
monitorData.forEach((monitor, index) => {
|
||||||
|
maintenanceStatusMap.set(monitor.id, maintenanceStatuses[index]);
|
||||||
|
});
|
||||||
|
|
||||||
|
monitorData.forEach((monitor, index) => {
|
||||||
|
childrenIDsMap.set(monitor.id, childrenIDs[index]);
|
||||||
|
});
|
||||||
|
|
||||||
|
monitorData.forEach((monitor, index) => {
|
||||||
|
activeStatusMap.set(monitor.id, activeStatuses[index]);
|
||||||
|
});
|
||||||
|
|
||||||
|
monitorData.forEach((monitor, index) => {
|
||||||
|
forceInactiveMap.set(monitor.id, !forceInactiveStatuses[index]);
|
||||||
|
});
|
||||||
|
|
||||||
|
monitorData.forEach((monitor, index) => {
|
||||||
|
pathsMap.set(monitor.id, paths[index]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
notifications: notificationsMap,
|
||||||
|
tags: tagsMap,
|
||||||
|
maintenanceStatus: maintenanceStatusMap,
|
||||||
|
childrenIDs: childrenIDsMap,
|
||||||
|
activeStatus: activeStatusMap,
|
||||||
|
forceInactive: forceInactiveMap,
|
||||||
|
paths: pathsMap,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets Parent of the monitor
|
* Gets Parent of the monitor
|
||||||
* @param {number} monitorID ID of monitor to get
|
* @param {number} monitorID ID of monitor to get
|
||||||
|
@ -1539,16 +1636,18 @@ class Monitor extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the full path
|
* Gets the full path
|
||||||
|
* @param {number} monitorID ID of the monitor to get
|
||||||
|
* @param {string} name of the monitor to get
|
||||||
* @returns {Promise<string[]>} Full path (includes groups and the name) of the monitor
|
* @returns {Promise<string[]>} Full path (includes groups and the name) of the monitor
|
||||||
*/
|
*/
|
||||||
async getPath() {
|
static async getAllPath(monitorID, name) {
|
||||||
const path = [ this.name ];
|
const path = [ name ];
|
||||||
|
|
||||||
if (this.parent === null) {
|
if (this.parent === null) {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
let parent = await Monitor.getParent(this.id);
|
let parent = await Monitor.getParent(monitorID);
|
||||||
while (parent !== null) {
|
while (parent !== null) {
|
||||||
path.unshift(parent.name);
|
path.unshift(parent.name);
|
||||||
parent = await Monitor.getParent(parent.id);
|
parent = await Monitor.getParent(parent.id);
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
let url = require("url");
|
let url = require("url");
|
||||||
let MemoryCache = require("./memory-cache");
|
let MemoryCache = require("./memory-cache");
|
||||||
const { log } = require("../../../src/util");
|
|
||||||
|
|
||||||
let t = {
|
let t = {
|
||||||
ms: 1,
|
ms: 1,
|
||||||
|
@ -91,6 +90,24 @@ function ApiCache() {
|
||||||
instances.push(this);
|
instances.push(this);
|
||||||
this.id = instances.length;
|
this.id = instances.length;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs a message to the console if the `DEBUG` environment variable is set.
|
||||||
|
* @param {string} a The first argument to log.
|
||||||
|
* @param {string} b The second argument to log.
|
||||||
|
* @param {string} c The third argument to log.
|
||||||
|
* @param {string} d The fourth argument to log, and so on... (optional)
|
||||||
|
*
|
||||||
|
* Generated by Trelent
|
||||||
|
*/
|
||||||
|
function debug(a, b, c, d) {
|
||||||
|
let arr = ["\x1b[36m[apicache]\x1b[0m", a, b, c, d].filter(function (arg) {
|
||||||
|
return arg !== undefined;
|
||||||
|
});
|
||||||
|
let debugEnv = process.env.DEBUG && process.env.DEBUG.split(",").indexOf("apicache") !== -1;
|
||||||
|
|
||||||
|
return (globalOptions.debug || debugEnv) && console.log.apply(null, arr);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the given request and response should be logged.
|
* Returns true if the given request and response should be logged.
|
||||||
* @param {Object} request The HTTP request object.
|
* @param {Object} request The HTTP request object.
|
||||||
|
@ -129,7 +146,7 @@ function ApiCache() {
|
||||||
let groupName = req.apicacheGroup;
|
let groupName = req.apicacheGroup;
|
||||||
|
|
||||||
if (groupName) {
|
if (groupName) {
|
||||||
log.debug("apicache", `group detected "${groupName}"`);
|
debug("group detected \"" + groupName + "\"");
|
||||||
let group = (index.groups[groupName] = index.groups[groupName] || []);
|
let group = (index.groups[groupName] = index.groups[groupName] || []);
|
||||||
group.unshift(key);
|
group.unshift(key);
|
||||||
}
|
}
|
||||||
|
@ -195,7 +212,7 @@ function ApiCache() {
|
||||||
redis.hset(key, "duration", duration);
|
redis.hset(key, "duration", duration);
|
||||||
redis.expire(key, duration / 1000, expireCallback || function () {});
|
redis.expire(key, duration / 1000, expireCallback || function () {});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.debug("apicache", `error in redis.hset(): ${err}`);
|
debug("[apicache] error in redis.hset()");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
memCache.add(key, value, duration, expireCallback);
|
memCache.add(key, value, duration, expireCallback);
|
||||||
|
@ -303,10 +320,10 @@ function ApiCache() {
|
||||||
|
|
||||||
// display log entry
|
// display log entry
|
||||||
let elapsed = new Date() - req.apicacheTimer;
|
let elapsed = new Date() - req.apicacheTimer;
|
||||||
log.debug("apicache", `adding cache entry for "${key}" @ ${strDuration} ${logDuration(elapsed)}`);
|
debug("adding cache entry for \"" + key + "\" @ " + strDuration, logDuration(elapsed));
|
||||||
log.debug("apicache", `_apicache.headers: ${JSON.stringify(res._apicache.headers)}`);
|
debug("_apicache.headers: ", res._apicache.headers);
|
||||||
log.debug("apicache", `res.getHeaders(): ${JSON.stringify(getSafeHeaders(res))}`);
|
debug("res.getHeaders(): ", getSafeHeaders(res));
|
||||||
log.debug("apicache", `cacheObject: ${JSON.stringify(cacheObject)}`);
|
debug("cacheObject: ", cacheObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -385,10 +402,10 @@ function ApiCache() {
|
||||||
let redis = globalOptions.redisClient;
|
let redis = globalOptions.redisClient;
|
||||||
|
|
||||||
if (group) {
|
if (group) {
|
||||||
log.debug("apicache", `clearing group "${target}"`);
|
debug("clearing group \"" + target + "\"");
|
||||||
|
|
||||||
group.forEach(function (key) {
|
group.forEach(function (key) {
|
||||||
log.debug("apicache", `clearing cached entry for "${key}"`);
|
debug("clearing cached entry for \"" + key + "\"");
|
||||||
clearTimeout(timers[key]);
|
clearTimeout(timers[key]);
|
||||||
delete timers[key];
|
delete timers[key];
|
||||||
if (!globalOptions.redisClient) {
|
if (!globalOptions.redisClient) {
|
||||||
|
@ -397,7 +414,7 @@ function ApiCache() {
|
||||||
try {
|
try {
|
||||||
redis.del(key);
|
redis.del(key);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.info("apicache", "error in redis.del(\"" + key + "\")");
|
console.log("[apicache] error in redis.del(\"" + key + "\")");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
index.all = index.all.filter(doesntMatch(key));
|
index.all = index.all.filter(doesntMatch(key));
|
||||||
|
@ -405,7 +422,7 @@ function ApiCache() {
|
||||||
|
|
||||||
delete index.groups[target];
|
delete index.groups[target];
|
||||||
} else if (target) {
|
} else if (target) {
|
||||||
log.debug("apicache", `clearing ${isAutomatic ? "expired" : "cached"} entry for "${target}"`);
|
debug("clearing " + (isAutomatic ? "expired" : "cached") + " entry for \"" + target + "\"");
|
||||||
clearTimeout(timers[target]);
|
clearTimeout(timers[target]);
|
||||||
delete timers[target];
|
delete timers[target];
|
||||||
// clear actual cached entry
|
// clear actual cached entry
|
||||||
|
@ -415,7 +432,7 @@ function ApiCache() {
|
||||||
try {
|
try {
|
||||||
redis.del(target);
|
redis.del(target);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.error("apicache", "error in redis.del(\"" + target + "\")");
|
console.log("[apicache] error in redis.del(\"" + target + "\")");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -432,7 +449,7 @@ function ApiCache() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
log.debug("apicache", "clearing entire index");
|
debug("clearing entire index");
|
||||||
|
|
||||||
if (!redis) {
|
if (!redis) {
|
||||||
memCache.clear();
|
memCache.clear();
|
||||||
|
@ -444,7 +461,7 @@ function ApiCache() {
|
||||||
try {
|
try {
|
||||||
redis.del(key);
|
redis.del(key);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.error("apicache", `error in redis.del("${key}"): ${err}`);
|
console.log("[apicache] error in redis.del(\"" + key + "\")");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -735,7 +752,7 @@ function ApiCache() {
|
||||||
*/
|
*/
|
||||||
let cache = function (req, res, next) {
|
let cache = function (req, res, next) {
|
||||||
function bypass() {
|
function bypass() {
|
||||||
log.debug("apicache", "bypass detected, skipping cache.");
|
debug("bypass detected, skipping cache.");
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -788,7 +805,7 @@ function ApiCache() {
|
||||||
// send if cache hit from memory-cache
|
// send if cache hit from memory-cache
|
||||||
if (cached) {
|
if (cached) {
|
||||||
let elapsed = new Date() - req.apicacheTimer;
|
let elapsed = new Date() - req.apicacheTimer;
|
||||||
log.debug("apicache", `sending cached (memory-cache) version of ${key} ${logDuration(elapsed)}`);
|
debug("sending cached (memory-cache) version of", key, logDuration(elapsed));
|
||||||
|
|
||||||
perf.hit(key);
|
perf.hit(key);
|
||||||
return sendCachedResponse(req, res, cached, middlewareToggle, next, duration);
|
return sendCachedResponse(req, res, cached, middlewareToggle, next, duration);
|
||||||
|
@ -800,7 +817,7 @@ function ApiCache() {
|
||||||
redis.hgetall(key, function (err, obj) {
|
redis.hgetall(key, function (err, obj) {
|
||||||
if (!err && obj && obj.response) {
|
if (!err && obj && obj.response) {
|
||||||
let elapsed = new Date() - req.apicacheTimer;
|
let elapsed = new Date() - req.apicacheTimer;
|
||||||
log.debug("apicache", "sending cached (redis) version of "+ key+" "+ logDuration(elapsed));
|
debug("sending cached (redis) version of", key, logDuration(elapsed));
|
||||||
|
|
||||||
perf.hit(key);
|
perf.hit(key);
|
||||||
return sendCachedResponse(
|
return sendCachedResponse(
|
||||||
|
@ -856,7 +873,7 @@ function ApiCache() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (globalOptions.trackPerformance) {
|
if (globalOptions.trackPerformance) {
|
||||||
log.debug("apicache", "WARNING: using trackPerformance flag can cause high memory usage!");
|
debug("WARNING: using trackPerformance flag can cause high memory usage!");
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
|
67
server/monitor-types/rabbitmq.js
Normal file
67
server/monitor-types/rabbitmq.js
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
const { MonitorType } = require("./monitor-type");
|
||||||
|
const { log, UP, DOWN } = require("../../src/util");
|
||||||
|
const { axiosAbortSignal } = require("../util-server");
|
||||||
|
const axios = require("axios");
|
||||||
|
|
||||||
|
class RabbitMqMonitorType extends MonitorType {
|
||||||
|
name = "rabbitmq";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async check(monitor, heartbeat, server) {
|
||||||
|
let baseUrls = [];
|
||||||
|
try {
|
||||||
|
baseUrls = JSON.parse(monitor.rabbitmqNodes);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error("Invalid RabbitMQ Nodes");
|
||||||
|
}
|
||||||
|
|
||||||
|
heartbeat.status = DOWN;
|
||||||
|
for (let baseUrl of baseUrls) {
|
||||||
|
try {
|
||||||
|
// Without a trailing slash, path in baseUrl will be removed. https://example.com/api -> https://example.com
|
||||||
|
if ( !baseUrl.endsWith("/") ) {
|
||||||
|
baseUrl += "/";
|
||||||
|
}
|
||||||
|
const options = {
|
||||||
|
// Do not start with slash, it will strip the trailing slash from baseUrl
|
||||||
|
url: new URL("api/health/checks/alarms/", baseUrl).href,
|
||||||
|
method: "get",
|
||||||
|
timeout: monitor.timeout * 1000,
|
||||||
|
headers: {
|
||||||
|
"Accept": "application/json",
|
||||||
|
"Authorization": "Basic " + Buffer.from(`${monitor.rabbitmqUsername || ""}:${monitor.rabbitmqPassword || ""}`).toString("base64"),
|
||||||
|
},
|
||||||
|
signal: axiosAbortSignal((monitor.timeout + 10) * 1000),
|
||||||
|
// Capture reason for 503 status
|
||||||
|
validateStatus: (status) => status === 200 || status === 503,
|
||||||
|
};
|
||||||
|
log.debug("monitor", `[${monitor.name}] Axios Request: ${JSON.stringify(options)}`);
|
||||||
|
const res = await axios.request(options);
|
||||||
|
log.debug("monitor", `[${monitor.name}] Axios Response: status=${res.status} body=${JSON.stringify(res.data)}`);
|
||||||
|
if (res.status === 200) {
|
||||||
|
heartbeat.status = UP;
|
||||||
|
heartbeat.msg = "OK";
|
||||||
|
break;
|
||||||
|
} else if (res.status === 503) {
|
||||||
|
heartbeat.msg = res.data.reason;
|
||||||
|
} else {
|
||||||
|
heartbeat.msg = `${res.status} - ${res.statusText}`;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (axios.isCancel(error)) {
|
||||||
|
heartbeat.msg = "Request timed out";
|
||||||
|
log.debug("monitor", `[${monitor.name}] Request timed out`);
|
||||||
|
} else {
|
||||||
|
log.debug("monitor", `[${monitor.name}] Axios Error: ${JSON.stringify(error.message)}`);
|
||||||
|
heartbeat.msg = error.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
RabbitMqMonitorType,
|
||||||
|
};
|
|
@ -63,7 +63,7 @@ if (process.platform === "win32") {
|
||||||
* @returns {Promise<boolean>} The executable is allowed?
|
* @returns {Promise<boolean>} The executable is allowed?
|
||||||
*/
|
*/
|
||||||
async function isAllowedChromeExecutable(executablePath) {
|
async function isAllowedChromeExecutable(executablePath) {
|
||||||
log.info("Chromium", config.args);
|
console.log(config.args);
|
||||||
if (config.args["allow-all-chrome-exec"] || process.env.UPTIME_KUMA_ALLOW_ALL_CHROME_EXEC === "1") {
|
if (config.args["allow-all-chrome-exec"] || process.env.UPTIME_KUMA_ALLOW_ALL_CHROME_EXEC === "1") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -102,8 +102,7 @@ async function getBrowser() {
|
||||||
*/
|
*/
|
||||||
async function getRemoteBrowser(remoteBrowserID, userId) {
|
async function getRemoteBrowser(remoteBrowserID, userId) {
|
||||||
let remoteBrowser = await RemoteBrowser.get(remoteBrowserID, userId);
|
let remoteBrowser = await RemoteBrowser.get(remoteBrowserID, userId);
|
||||||
log.debug("Chromium", `Using remote browser: ${remoteBrowser.name} (${remoteBrowser.id})`);
|
log.debug("MONITOR", `Using remote browser: ${remoteBrowser.name} (${remoteBrowser.id})`);
|
||||||
browser = chromium.connect(remoteBrowser.url);
|
|
||||||
browser = await chromium.connect(remoteBrowser.url);
|
browser = await chromium.connect(remoteBrowser.url);
|
||||||
return browser;
|
return browser;
|
||||||
}
|
}
|
||||||
|
|
35
server/notification-providers/46elks.js
Normal file
35
server/notification-providers/46elks.js
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
const NotificationProvider = require("./notification-provider");
|
||||||
|
const axios = require("axios");
|
||||||
|
|
||||||
|
class Elks extends NotificationProvider {
|
||||||
|
name = "Elks";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||||
|
const okMsg = "Sent Successfully.";
|
||||||
|
const url = "https://api.46elks.com/a1/sms";
|
||||||
|
|
||||||
|
try {
|
||||||
|
let data = new URLSearchParams();
|
||||||
|
data.append("from", notification.elksFromNumber);
|
||||||
|
data.append("to", notification.elksToNumber );
|
||||||
|
data.append("message", msg);
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
headers: {
|
||||||
|
"Authorization": "Basic " + Buffer.from(`${notification.elksUsername}:${notification.elksAuthToken}`).toString("base64")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await axios.post(url, data, config);
|
||||||
|
|
||||||
|
return okMsg;
|
||||||
|
} catch (error) {
|
||||||
|
this.throwGeneralAxiosError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Elks;
|
|
@ -1,7 +1,7 @@
|
||||||
const NotificationProvider = require("./notification-provider");
|
const NotificationProvider = require("./notification-provider");
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
|
const { setting } = require("../util-server");
|
||||||
const { getMonitorRelativeURL, UP, DOWN } = require("../../src/util");
|
const { getMonitorRelativeURL, UP, DOWN } = require("../../src/util");
|
||||||
const { Settings } = require("../settings");
|
|
||||||
|
|
||||||
class AlertNow extends NotificationProvider {
|
class AlertNow extends NotificationProvider {
|
||||||
name = "AlertNow";
|
name = "AlertNow";
|
||||||
|
@ -29,7 +29,7 @@ class AlertNow extends NotificationProvider {
|
||||||
|
|
||||||
textMsg += ` - ${msg}`;
|
textMsg += ` - ${msg}`;
|
||||||
|
|
||||||
const baseURL = await Settings.get("primaryBaseURL");
|
const baseURL = await setting("primaryBaseURL");
|
||||||
if (baseURL && monitorJSON) {
|
if (baseURL && monitorJSON) {
|
||||||
textMsg += ` >> ${baseURL + getMonitorRelativeURL(monitorJSON.id)}`;
|
textMsg += ` >> ${baseURL + getMonitorRelativeURL(monitorJSON.id)}`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,7 +87,6 @@ class DingDing extends NotificationProvider {
|
||||||
* @returns {string} Status
|
* @returns {string} Status
|
||||||
*/
|
*/
|
||||||
statusToString(status) {
|
statusToString(status) {
|
||||||
// TODO: Move to notification-provider.js to avoid repetition in classes
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case DOWN:
|
case DOWN:
|
||||||
return "DOWN";
|
return "DOWN";
|
||||||
|
|
|
@ -48,7 +48,7 @@ class Discord extends NotificationProvider {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
|
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
|
||||||
value: this.extractAdress(monitorJSON),
|
value: this.extractAddress(monitorJSON),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: `Time (${heartbeatJSON["timezone"]})`,
|
name: `Time (${heartbeatJSON["timezone"]})`,
|
||||||
|
@ -85,7 +85,7 @@ class Discord extends NotificationProvider {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
|
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
|
||||||
value: this.extractAdress(monitorJSON),
|
value: this.extractAddress(monitorJSON),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: `Time (${heartbeatJSON["timezone"]})`,
|
name: `Time (${heartbeatJSON["timezone"]})`,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const NotificationProvider = require("./notification-provider");
|
const NotificationProvider = require("./notification-provider");
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
|
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
|
||||||
const { Settings } = require("../settings");
|
const { setting } = require("../util-server");
|
||||||
const successMessage = "Sent Successfully.";
|
const successMessage = "Sent Successfully.";
|
||||||
|
|
||||||
class FlashDuty extends NotificationProvider {
|
class FlashDuty extends NotificationProvider {
|
||||||
|
@ -84,7 +84,7 @@ class FlashDuty extends NotificationProvider {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseURL = await Settings.get("primaryBaseURL");
|
const baseURL = await setting("primaryBaseURL");
|
||||||
if (baseURL && monitorInfo) {
|
if (baseURL && monitorInfo) {
|
||||||
options.client = "Uptime Kuma";
|
options.client = "Uptime Kuma";
|
||||||
options.client_url = baseURL + getMonitorRelativeURL(monitorInfo.id);
|
options.client_url = baseURL + getMonitorRelativeURL(monitorInfo.id);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const NotificationProvider = require("./notification-provider");
|
const NotificationProvider = require("./notification-provider");
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
|
const { setting } = require("../util-server");
|
||||||
const { getMonitorRelativeURL, UP } = require("../../src/util");
|
const { getMonitorRelativeURL, UP } = require("../../src/util");
|
||||||
const { Settings } = require("../settings");
|
|
||||||
|
|
||||||
class GoogleChat extends NotificationProvider {
|
class GoogleChat extends NotificationProvider {
|
||||||
name = "GoogleChat";
|
name = "GoogleChat";
|
||||||
|
@ -45,7 +45,7 @@ class GoogleChat extends NotificationProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
// add button for monitor link if available
|
// add button for monitor link if available
|
||||||
const baseURL = await Settings.get("primaryBaseURL");
|
const baseURL = await setting("primaryBaseURL");
|
||||||
if (baseURL) {
|
if (baseURL) {
|
||||||
const urlPath = monitorJSON ? getMonitorRelativeURL(monitorJSON.id) : "/";
|
const urlPath = monitorJSON ? getMonitorRelativeURL(monitorJSON.id) : "/";
|
||||||
sectionWidgets.push({
|
sectionWidgets.push({
|
||||||
|
|
|
@ -24,7 +24,7 @@ class NotificationProvider {
|
||||||
* @param {?object} monitorJSON Monitor details (For Up/Down only)
|
* @param {?object} monitorJSON Monitor details (For Up/Down only)
|
||||||
* @returns {string} The extracted address based on the monitor type.
|
* @returns {string} The extracted address based on the monitor type.
|
||||||
*/
|
*/
|
||||||
extractAdress(monitorJSON) {
|
extractAddress(monitorJSON) {
|
||||||
if (!monitorJSON) {
|
if (!monitorJSON) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const NotificationProvider = require("./notification-provider");
|
const NotificationProvider = require("./notification-provider");
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
|
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
|
||||||
const { Settings } = require("../settings");
|
const { setting } = require("../util-server");
|
||||||
let successMessage = "Sent Successfully.";
|
let successMessage = "Sent Successfully.";
|
||||||
|
|
||||||
class PagerDuty extends NotificationProvider {
|
class PagerDuty extends NotificationProvider {
|
||||||
|
@ -95,7 +95,7 @@ class PagerDuty extends NotificationProvider {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseURL = await Settings.get("primaryBaseURL");
|
const baseURL = await setting("primaryBaseURL");
|
||||||
if (baseURL && monitorInfo) {
|
if (baseURL && monitorInfo) {
|
||||||
options.client = "Uptime Kuma";
|
options.client = "Uptime Kuma";
|
||||||
options.client_url = baseURL + getMonitorRelativeURL(monitorInfo.id);
|
options.client_url = baseURL + getMonitorRelativeURL(monitorInfo.id);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const NotificationProvider = require("./notification-provider");
|
const NotificationProvider = require("./notification-provider");
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
|
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
|
||||||
const { Settings } = require("../settings");
|
const { setting } = require("../util-server");
|
||||||
let successMessage = "Sent Successfully.";
|
let successMessage = "Sent Successfully.";
|
||||||
|
|
||||||
class PagerTree extends NotificationProvider {
|
class PagerTree extends NotificationProvider {
|
||||||
|
@ -74,7 +74,7 @@ class PagerTree extends NotificationProvider {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseURL = await Settings.get("primaryBaseURL");
|
const baseURL = await setting("primaryBaseURL");
|
||||||
if (baseURL && monitorJSON) {
|
if (baseURL && monitorJSON) {
|
||||||
options.client = "Uptime Kuma";
|
options.client = "Uptime Kuma";
|
||||||
options.client_url = baseURL + getMonitorRelativeURL(monitorJSON.id);
|
options.client_url = baseURL + getMonitorRelativeURL(monitorJSON.id);
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
const NotificationProvider = require("./notification-provider");
|
const NotificationProvider = require("./notification-provider");
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
const Slack = require("./slack");
|
const Slack = require("./slack");
|
||||||
|
const { setting } = require("../util-server");
|
||||||
const { getMonitorRelativeURL, DOWN } = require("../../src/util");
|
const { getMonitorRelativeURL, DOWN } = require("../../src/util");
|
||||||
const { Settings } = require("../settings");
|
|
||||||
|
|
||||||
class RocketChat extends NotificationProvider {
|
class RocketChat extends NotificationProvider {
|
||||||
name = "rocket.chat";
|
name = "rocket.chat";
|
||||||
|
@ -49,7 +49,7 @@ class RocketChat extends NotificationProvider {
|
||||||
await Slack.deprecateURL(notification.rocketbutton);
|
await Slack.deprecateURL(notification.rocketbutton);
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseURL = await Settings.get("primaryBaseURL");
|
const baseURL = await setting("primaryBaseURL");
|
||||||
|
|
||||||
if (baseURL) {
|
if (baseURL) {
|
||||||
data.attachments[0].title_link = baseURL + getMonitorRelativeURL(monitorJSON.id);
|
data.attachments[0].title_link = baseURL + getMonitorRelativeURL(monitorJSON.id);
|
||||||
|
|
65
server/notification-providers/send-grid.js
Normal file
65
server/notification-providers/send-grid.js
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
const NotificationProvider = require("./notification-provider");
|
||||||
|
const axios = require("axios");
|
||||||
|
|
||||||
|
class SendGrid extends NotificationProvider {
|
||||||
|
name = "SendGrid";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||||
|
const okMsg = "Sent Successfully.";
|
||||||
|
|
||||||
|
try {
|
||||||
|
let config = {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${notification.sendgridApiKey}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let personalizations = {
|
||||||
|
to: [{ email: notification.sendgridToEmail }],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add CC recipients if provided
|
||||||
|
if (notification.sendgridCcEmail) {
|
||||||
|
personalizations.cc = notification.sendgridCcEmail
|
||||||
|
.split(",")
|
||||||
|
.map((email) => ({ email: email.trim() }));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add BCC recipients if provided
|
||||||
|
if (notification.sendgridBccEmail) {
|
||||||
|
personalizations.bcc = notification.sendgridBccEmail
|
||||||
|
.split(",")
|
||||||
|
.map((email) => ({ email: email.trim() }));
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = {
|
||||||
|
personalizations: [ personalizations ],
|
||||||
|
from: { email: notification.sendgridFromEmail.trim() },
|
||||||
|
subject:
|
||||||
|
notification.sendgridSubject ||
|
||||||
|
"Notification from Your Uptime Kuma",
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text/plain",
|
||||||
|
value: msg,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
await axios.post(
|
||||||
|
"https://api.sendgrid.com/v3/mail/send",
|
||||||
|
data,
|
||||||
|
config
|
||||||
|
);
|
||||||
|
return okMsg;
|
||||||
|
} catch (error) {
|
||||||
|
this.throwGeneralAxiosError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = SendGrid;
|
|
@ -11,8 +11,14 @@ class ServerChan extends NotificationProvider {
|
||||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||||
const okMsg = "Sent Successfully.";
|
const okMsg = "Sent Successfully.";
|
||||||
|
|
||||||
|
// serverchan3 requires sending via ft07.com
|
||||||
|
const matchResult = String(notification.serverChanSendKey).match(/^sctp(\d+)t/i);
|
||||||
|
const url = matchResult && matchResult[1]
|
||||||
|
? `https://${matchResult[1]}.push.ft07.com/send/${notification.serverChanSendKey}.send`
|
||||||
|
: `https://sctapi.ftqq.com/${notification.serverChanSendKey}.send`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await axios.post(`https://sctapi.ftqq.com/${notification.serverChanSendKey}.send`, {
|
await axios.post(url, {
|
||||||
"title": this.checkStatus(heartbeatJSON, monitorJSON),
|
"title": this.checkStatus(heartbeatJSON, monitorJSON),
|
||||||
"desp": msg,
|
"desp": msg,
|
||||||
});
|
});
|
||||||
|
|
|
@ -32,7 +32,7 @@ class SevenIO extends NotificationProvider {
|
||||||
return okMsg;
|
return okMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
let address = this.extractAdress(monitorJSON);
|
let address = this.extractAddress(monitorJSON);
|
||||||
if (address !== "") {
|
if (address !== "") {
|
||||||
address = `(${address}) `;
|
address = `(${address}) `;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ class SIGNL4 extends NotificationProvider {
|
||||||
msg,
|
msg,
|
||||||
// Source system
|
// Source system
|
||||||
"X-S4-SourceSystem": "UptimeKuma",
|
"X-S4-SourceSystem": "UptimeKuma",
|
||||||
monitorUrl: this.extractAdress(monitorJSON),
|
monitorUrl: this.extractAddress(monitorJSON),
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
const NotificationProvider = require("./notification-provider");
|
const NotificationProvider = require("./notification-provider");
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
|
const { setSettings, setting } = require("../util-server");
|
||||||
const { getMonitorRelativeURL, UP } = require("../../src/util");
|
const { getMonitorRelativeURL, UP } = require("../../src/util");
|
||||||
const { Settings } = require("../settings");
|
|
||||||
const { log } = require("../../src/util");
|
|
||||||
|
|
||||||
class Slack extends NotificationProvider {
|
class Slack extends NotificationProvider {
|
||||||
name = "slack";
|
name = "slack";
|
||||||
|
@ -15,13 +14,15 @@ class Slack extends NotificationProvider {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
static async deprecateURL(url) {
|
static async deprecateURL(url) {
|
||||||
let currentPrimaryBaseURL = await Settings.get("primaryBaseURL");
|
let currentPrimaryBaseURL = await setting("primaryBaseURL");
|
||||||
|
|
||||||
if (!currentPrimaryBaseURL) {
|
if (!currentPrimaryBaseURL) {
|
||||||
log.error("notification", "Move the url to be the primary base URL");
|
console.log("Move the url to be the primary base URL");
|
||||||
await Settings.set("primaryBaseURL", url, "general");
|
await setSettings("general", {
|
||||||
|
primaryBaseURL: url,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
log.debug("notification", "Already there, no need to move the primary base URL");
|
console.log("Already there, no need to move the primary base URL");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +32,7 @@ class Slack extends NotificationProvider {
|
||||||
* @param {object} monitorJSON The monitor config
|
* @param {object} monitorJSON The monitor config
|
||||||
* @returns {Array} The relevant action objects
|
* @returns {Array} The relevant action objects
|
||||||
*/
|
*/
|
||||||
static buildActions(baseURL, monitorJSON) {
|
buildActions(baseURL, monitorJSON) {
|
||||||
const actions = [];
|
const actions = [];
|
||||||
|
|
||||||
if (baseURL) {
|
if (baseURL) {
|
||||||
|
@ -47,7 +48,7 @@ class Slack extends NotificationProvider {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const address = this.extractAdress(monitorJSON);
|
const address = this.extractAddress(monitorJSON);
|
||||||
if (address) {
|
if (address) {
|
||||||
actions.push({
|
actions.push({
|
||||||
"type": "button",
|
"type": "button",
|
||||||
|
@ -72,7 +73,7 @@ class Slack extends NotificationProvider {
|
||||||
* @param {string} msg The message body
|
* @param {string} msg The message body
|
||||||
* @returns {Array<object>} The rich content blocks for the Slack message
|
* @returns {Array<object>} The rich content blocks for the Slack message
|
||||||
*/
|
*/
|
||||||
static buildBlocks(baseURL, monitorJSON, heartbeatJSON, title, msg) {
|
buildBlocks(baseURL, monitorJSON, heartbeatJSON, title, msg) {
|
||||||
|
|
||||||
//create an array to dynamically add blocks
|
//create an array to dynamically add blocks
|
||||||
const blocks = [];
|
const blocks = [];
|
||||||
|
@ -135,21 +136,26 @@ class Slack extends NotificationProvider {
|
||||||
return okMsg;
|
return okMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseURL = await Settings.get("primaryBaseURL");
|
const baseURL = await setting("primaryBaseURL");
|
||||||
|
|
||||||
const title = "Uptime Kuma Alert";
|
const title = "Uptime Kuma Alert";
|
||||||
let data = {
|
let data = {
|
||||||
"text": `${title}\n${msg}`,
|
|
||||||
"channel": notification.slackchannel,
|
"channel": notification.slackchannel,
|
||||||
"username": notification.slackusername,
|
"username": notification.slackusername,
|
||||||
"icon_emoji": notification.slackiconemo,
|
"icon_emoji": notification.slackiconemo,
|
||||||
"attachments": [
|
"attachments": [],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (notification.slackrichmessage) {
|
||||||
|
data.attachments.push(
|
||||||
{
|
{
|
||||||
"color": (heartbeatJSON["status"] === UP) ? "#2eb886" : "#e01e5a",
|
"color": (heartbeatJSON["status"] === UP) ? "#2eb886" : "#e01e5a",
|
||||||
"blocks": Slack.buildBlocks(baseURL, monitorJSON, heartbeatJSON, title, msg),
|
"blocks": this.buildBlocks(baseURL, monitorJSON, heartbeatJSON, title, msg),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
data.text = `${title}\n${msg}`;
|
||||||
}
|
}
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
if (notification.slackbutton) {
|
if (notification.slackbutton) {
|
||||||
await Slack.deprecateURL(notification.slackbutton);
|
await Slack.deprecateURL(notification.slackbutton);
|
||||||
|
|
|
@ -93,7 +93,7 @@ class SMTP extends NotificationProvider {
|
||||||
|
|
||||||
if (monitorJSON !== null) {
|
if (monitorJSON !== null) {
|
||||||
monitorName = monitorJSON["name"];
|
monitorName = monitorJSON["name"];
|
||||||
monitorHostnameOrURL = this.extractAdress(monitorJSON);
|
monitorHostnameOrURL = this.extractAddress(monitorJSON);
|
||||||
}
|
}
|
||||||
|
|
||||||
let serviceStatus = "⚠️ Test";
|
let serviceStatus = "⚠️ Test";
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const NotificationProvider = require("./notification-provider");
|
const NotificationProvider = require("./notification-provider");
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
|
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
|
||||||
const { Settings } = require("../settings");
|
const { setting } = require("../util-server");
|
||||||
let successMessage = "Sent Successfully.";
|
let successMessage = "Sent Successfully.";
|
||||||
|
|
||||||
class Splunk extends NotificationProvider {
|
class Splunk extends NotificationProvider {
|
||||||
|
@ -95,7 +95,7 @@ class Splunk extends NotificationProvider {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseURL = await Settings.get("primaryBaseURL");
|
const baseURL = await setting("primaryBaseURL");
|
||||||
if (baseURL && monitorInfo) {
|
if (baseURL && monitorInfo) {
|
||||||
options.client = "Uptime Kuma";
|
options.client = "Uptime Kuma";
|
||||||
options.client_url = baseURL + getMonitorRelativeURL(monitorInfo.id);
|
options.client_url = baseURL + getMonitorRelativeURL(monitorInfo.id);
|
||||||
|
|
|
@ -34,7 +34,7 @@ class Squadcast extends NotificationProvider {
|
||||||
data.status = "resolve";
|
data.status = "resolve";
|
||||||
}
|
}
|
||||||
|
|
||||||
data.tags["AlertAddress"] = this.extractAdress(monitorJSON);
|
data.tags["AlertAddress"] = this.extractAddress(monitorJSON);
|
||||||
|
|
||||||
monitorJSON["tags"].forEach(tag => {
|
monitorJSON["tags"].forEach(tag => {
|
||||||
data.tags[tag["name"]] = {
|
data.tags[tag["name"]] = {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const NotificationProvider = require("./notification-provider");
|
const NotificationProvider = require("./notification-provider");
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
|
const { setting } = require("../util-server");
|
||||||
const { getMonitorRelativeURL } = require("../../src/util");
|
const { getMonitorRelativeURL } = require("../../src/util");
|
||||||
const { Settings } = require("../settings");
|
|
||||||
|
|
||||||
class Stackfield extends NotificationProvider {
|
class Stackfield extends NotificationProvider {
|
||||||
name = "stackfield";
|
name = "stackfield";
|
||||||
|
@ -23,7 +23,7 @@ class Stackfield extends NotificationProvider {
|
||||||
|
|
||||||
textMsg += `\n${msg}`;
|
textMsg += `\n${msg}`;
|
||||||
|
|
||||||
const baseURL = await Settings.get("primaryBaseURL");
|
const baseURL = await setting("primaryBaseURL");
|
||||||
if (baseURL) {
|
if (baseURL) {
|
||||||
textMsg += `\n${baseURL + getMonitorRelativeURL(monitorJSON.id)}`;
|
textMsg += `\n${baseURL + getMonitorRelativeURL(monitorJSON.id)}`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -225,7 +225,7 @@ class Teams extends NotificationProvider {
|
||||||
const payload = this._notificationPayloadFactory({
|
const payload = this._notificationPayloadFactory({
|
||||||
heartbeatJSON: heartbeatJSON,
|
heartbeatJSON: heartbeatJSON,
|
||||||
monitorName: monitorJSON.name,
|
monitorName: monitorJSON.name,
|
||||||
monitorUrl: this.extractAdress(monitorJSON),
|
monitorUrl: this.extractAddress(monitorJSON),
|
||||||
dashboardUrl: dashboardUrl,
|
dashboardUrl: dashboardUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -10,11 +10,22 @@ class TechulusPush extends NotificationProvider {
|
||||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||||
const okMsg = "Sent Successfully.";
|
const okMsg = "Sent Successfully.";
|
||||||
|
|
||||||
try {
|
let data = {
|
||||||
await axios.post(`https://push.techulus.com/api/v1/notify/${notification.pushAPIKey}`, {
|
"title": notification?.pushTitle?.length ? notification.pushTitle : "Uptime-Kuma",
|
||||||
"title": "Uptime-Kuma",
|
|
||||||
"body": msg,
|
"body": msg,
|
||||||
});
|
"timeSensitive": notification.pushTimeSensitive ?? true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (notification.pushChannel) {
|
||||||
|
data.channel = notification.pushChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notification.pushSound) {
|
||||||
|
data.sound = notification.pushSound;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.post(`https://push.techulus.com/api/v1/notify/${notification.pushAPIKey}`, data);
|
||||||
return okMsg;
|
return okMsg;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.throwGeneralAxiosError(error);
|
this.throwGeneralAxiosError(error);
|
||||||
|
|
|
@ -32,20 +32,17 @@ class WeCom extends NotificationProvider {
|
||||||
* @returns {object} Message
|
* @returns {object} Message
|
||||||
*/
|
*/
|
||||||
composeMessage(heartbeatJSON, msg) {
|
composeMessage(heartbeatJSON, msg) {
|
||||||
let title;
|
let title = "UptimeKuma Message";
|
||||||
if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] === UP) {
|
if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] === UP) {
|
||||||
title = "UptimeKuma Monitor Up";
|
title = "UptimeKuma Monitor Up";
|
||||||
}
|
}
|
||||||
if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] === DOWN) {
|
if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] === DOWN) {
|
||||||
title = "UptimeKuma Monitor Down";
|
title = "UptimeKuma Monitor Down";
|
||||||
}
|
}
|
||||||
if (msg != null) {
|
|
||||||
title = "UptimeKuma Message";
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
msgtype: "text",
|
msgtype: "text",
|
||||||
text: {
|
text: {
|
||||||
content: title + msg
|
content: title + "\n" + msg
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,7 +85,7 @@ class ZohoCliq extends NotificationProvider {
|
||||||
const payload = this._notificationPayloadFactory({
|
const payload = this._notificationPayloadFactory({
|
||||||
monitorMessage: heartbeatJSON.msg,
|
monitorMessage: heartbeatJSON.msg,
|
||||||
monitorName: monitorJSON.name,
|
monitorName: monitorJSON.name,
|
||||||
monitorUrl: this.extractAdress(monitorJSON),
|
monitorUrl: this.extractAddress(monitorJSON),
|
||||||
status: heartbeatJSON.status
|
status: heartbeatJSON.status
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ const CallMeBot = require("./notification-providers/call-me-bot");
|
||||||
const SMSC = require("./notification-providers/smsc");
|
const SMSC = require("./notification-providers/smsc");
|
||||||
const DingDing = require("./notification-providers/dingding");
|
const DingDing = require("./notification-providers/dingding");
|
||||||
const Discord = require("./notification-providers/discord");
|
const Discord = require("./notification-providers/discord");
|
||||||
|
const Elks = require("./notification-providers/46elks");
|
||||||
const Feishu = require("./notification-providers/feishu");
|
const Feishu = require("./notification-providers/feishu");
|
||||||
const FreeMobile = require("./notification-providers/freemobile");
|
const FreeMobile = require("./notification-providers/freemobile");
|
||||||
const GoogleChat = require("./notification-providers/google-chat");
|
const GoogleChat = require("./notification-providers/google-chat");
|
||||||
|
@ -67,6 +68,7 @@ const GtxMessaging = require("./notification-providers/gtx-messaging");
|
||||||
const Cellsynt = require("./notification-providers/cellsynt");
|
const Cellsynt = require("./notification-providers/cellsynt");
|
||||||
const Onesender = require("./notification-providers/onesender");
|
const Onesender = require("./notification-providers/onesender");
|
||||||
const Wpush = require("./notification-providers/wpush");
|
const Wpush = require("./notification-providers/wpush");
|
||||||
|
const SendGrid = require("./notification-providers/send-grid");
|
||||||
|
|
||||||
class Notification {
|
class Notification {
|
||||||
|
|
||||||
|
@ -95,6 +97,7 @@ class Notification {
|
||||||
new SMSC(),
|
new SMSC(),
|
||||||
new DingDing(),
|
new DingDing(),
|
||||||
new Discord(),
|
new Discord(),
|
||||||
|
new Elks(),
|
||||||
new Feishu(),
|
new Feishu(),
|
||||||
new FreeMobile(),
|
new FreeMobile(),
|
||||||
new GoogleChat(),
|
new GoogleChat(),
|
||||||
|
@ -151,6 +154,7 @@ class Notification {
|
||||||
new GtxMessaging(),
|
new GtxMessaging(),
|
||||||
new Cellsynt(),
|
new Cellsynt(),
|
||||||
new Wpush(),
|
new Wpush(),
|
||||||
|
new SendGrid()
|
||||||
];
|
];
|
||||||
for (let item of list) {
|
for (let item of list) {
|
||||||
if (! item.name) {
|
if (! item.name) {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
const { R } = require("redbean-node");
|
|
||||||
const PrometheusClient = require("prom-client");
|
const PrometheusClient = require("prom-client");
|
||||||
const { log } = require("../src/util");
|
const { log } = require("../src/util");
|
||||||
|
|
||||||
|
@ -10,102 +9,36 @@ const commonLabels = [
|
||||||
"monitor_port",
|
"monitor_port",
|
||||||
];
|
];
|
||||||
|
|
||||||
class Prometheus {
|
const monitorCertDaysRemaining = new PrometheusClient.Gauge({
|
||||||
|
|
||||||
/**
|
|
||||||
* Metric: monitor_cert_days_remaining
|
|
||||||
* @type {PrometheusClient.Gauge<string> | null}
|
|
||||||
*/
|
|
||||||
static monitorCertDaysRemaining = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Metric: monitor_cert_is_valid
|
|
||||||
* @type {PrometheusClient.Gauge<string> | null}
|
|
||||||
*/
|
|
||||||
static monitorCertIsValid = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Metric: monitor_response_time
|
|
||||||
* @type {PrometheusClient.Gauge<string> | null}
|
|
||||||
*/
|
|
||||||
static monitorResponseTime = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Metric: monitor_status
|
|
||||||
* @type {PrometheusClient.Gauge<string> | null}
|
|
||||||
*/
|
|
||||||
static monitorStatus = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All registered metric labels.
|
|
||||||
* @type {string[] | null}
|
|
||||||
*/
|
|
||||||
static monitorLabelNames = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Monitor labels/values combination.
|
|
||||||
* @type {{}}
|
|
||||||
*/
|
|
||||||
monitorLabelValues;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize metrics and get all label names the first time called.
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
static async initMetrics() {
|
|
||||||
if (!this.monitorLabelNames) {
|
|
||||||
let labelNames = await R.getCol("SELECT name FROM tag");
|
|
||||||
this.monitorLabelNames = [ ...commonLabels, ...labelNames ];
|
|
||||||
}
|
|
||||||
if (!this.monitorCertDaysRemaining) {
|
|
||||||
this.monitorCertDaysRemaining = new PrometheusClient.Gauge({
|
|
||||||
name: "monitor_cert_days_remaining",
|
name: "monitor_cert_days_remaining",
|
||||||
help: "The number of days remaining until the certificate expires",
|
help: "The number of days remaining until the certificate expires",
|
||||||
labelNames: this.monitorLabelNames
|
labelNames: commonLabels
|
||||||
});
|
});
|
||||||
}
|
|
||||||
if (!this.monitorCertIsValid) {
|
const monitorCertIsValid = new PrometheusClient.Gauge({
|
||||||
this.monitorCertIsValid = new PrometheusClient.Gauge({
|
|
||||||
name: "monitor_cert_is_valid",
|
name: "monitor_cert_is_valid",
|
||||||
help: "Is the certificate still valid? (1 = Yes, 0 = No)",
|
help: "Is the certificate still valid? (1 = Yes, 0= No)",
|
||||||
labelNames: this.monitorLabelNames
|
labelNames: commonLabels
|
||||||
});
|
});
|
||||||
}
|
const monitorResponseTime = new PrometheusClient.Gauge({
|
||||||
if (!this.monitorResponseTime) {
|
|
||||||
this.monitorResponseTime = new PrometheusClient.Gauge({
|
|
||||||
name: "monitor_response_time",
|
name: "monitor_response_time",
|
||||||
help: "Monitor Response Time (ms)",
|
help: "Monitor Response Time (ms)",
|
||||||
labelNames: this.monitorLabelNames
|
labelNames: commonLabels
|
||||||
});
|
});
|
||||||
}
|
|
||||||
if (!this.monitorStatus) {
|
const monitorStatus = new PrometheusClient.Gauge({
|
||||||
this.monitorStatus = new PrometheusClient.Gauge({
|
|
||||||
name: "monitor_status",
|
name: "monitor_status",
|
||||||
help: "Monitor Status (1 = UP, 0 = DOWN, 2 = PENDING, 3 = MAINTENANCE)",
|
help: "Monitor Status (1 = UP, 0= DOWN, 2= PENDING, 3= MAINTENANCE)",
|
||||||
labelNames: this.monitorLabelNames
|
labelNames: commonLabels
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
class Prometheus {
|
||||||
|
monitorLabelValues = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper to create a `Prometheus` instance and ensure metrics are initialized.
|
* @param {object} monitor Monitor object to monitor
|
||||||
* @param {Monitor} monitor Monitor object to monitor
|
|
||||||
* @returns {Promise<Prometheus>} `Prometheus` instance
|
|
||||||
*/
|
*/
|
||||||
static async createAndInitMetrics(monitor) {
|
constructor(monitor) {
|
||||||
await Prometheus.initMetrics();
|
|
||||||
let tags = await monitor.getTags();
|
|
||||||
return new Prometheus(monitor, tags);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a prometheus metric instance.
|
|
||||||
*
|
|
||||||
* Note: Make sure to call `Prometheus.initMetrics()` once prior creating Prometheus instances.
|
|
||||||
* @param {Monitor} monitor Monitor object to monitor
|
|
||||||
* @param {Promise<LooseObject<any>[]>} tags Tags of the monitor
|
|
||||||
*/
|
|
||||||
constructor(monitor, tags) {
|
|
||||||
this.monitorLabelValues = {
|
this.monitorLabelValues = {
|
||||||
monitor_name: monitor.name,
|
monitor_name: monitor.name,
|
||||||
monitor_type: monitor.type,
|
monitor_type: monitor.type,
|
||||||
|
@ -113,12 +46,6 @@ class Prometheus {
|
||||||
monitor_hostname: monitor.hostname,
|
monitor_hostname: monitor.hostname,
|
||||||
monitor_port: monitor.port
|
monitor_port: monitor.port
|
||||||
};
|
};
|
||||||
Object.values(tags)
|
|
||||||
// only label names that were known at first metric creation.
|
|
||||||
.filter(tag => Prometheus.monitorLabelNames.includes(tag.name))
|
|
||||||
.forEach(tag => {
|
|
||||||
this.monitorLabelValues[tag.name] = tag.value;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -128,6 +55,7 @@ class Prometheus {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
update(heartbeat, tlsInfo) {
|
update(heartbeat, tlsInfo) {
|
||||||
|
|
||||||
if (typeof tlsInfo !== "undefined") {
|
if (typeof tlsInfo !== "undefined") {
|
||||||
try {
|
try {
|
||||||
let isValid;
|
let isValid;
|
||||||
|
@ -136,7 +64,7 @@ class Prometheus {
|
||||||
} else {
|
} else {
|
||||||
isValid = 0;
|
isValid = 0;
|
||||||
}
|
}
|
||||||
Prometheus.monitorCertIsValid.set(this.monitorLabelValues, isValid);
|
monitorCertIsValid.set(this.monitorLabelValues, isValid);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("prometheus", "Caught error");
|
log.error("prometheus", "Caught error");
|
||||||
log.error("prometheus", e);
|
log.error("prometheus", e);
|
||||||
|
@ -144,7 +72,7 @@ class Prometheus {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (tlsInfo.certInfo != null) {
|
if (tlsInfo.certInfo != null) {
|
||||||
Prometheus.monitorCertDaysRemaining.set(this.monitorLabelValues, tlsInfo.certInfo.daysRemaining);
|
monitorCertDaysRemaining.set(this.monitorLabelValues, tlsInfo.certInfo.daysRemaining);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("prometheus", "Caught error");
|
log.error("prometheus", "Caught error");
|
||||||
|
@ -154,7 +82,7 @@ class Prometheus {
|
||||||
|
|
||||||
if (heartbeat) {
|
if (heartbeat) {
|
||||||
try {
|
try {
|
||||||
Prometheus.monitorStatus.set(this.monitorLabelValues, heartbeat.status);
|
monitorStatus.set(this.monitorLabelValues, heartbeat.status);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("prometheus", "Caught error");
|
log.error("prometheus", "Caught error");
|
||||||
log.error("prometheus", e);
|
log.error("prometheus", e);
|
||||||
|
@ -162,10 +90,10 @@ class Prometheus {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (typeof heartbeat.ping === "number") {
|
if (typeof heartbeat.ping === "number") {
|
||||||
Prometheus.monitorResponseTime.set(this.monitorLabelValues, heartbeat.ping);
|
monitorResponseTime.set(this.monitorLabelValues, heartbeat.ping);
|
||||||
} else {
|
} else {
|
||||||
// Is it good?
|
// Is it good?
|
||||||
Prometheus.monitorResponseTime.set(this.monitorLabelValues, -1);
|
monitorResponseTime.set(this.monitorLabelValues, -1);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("prometheus", "Caught error");
|
log.error("prometheus", "Caught error");
|
||||||
|
@ -180,10 +108,10 @@ class Prometheus {
|
||||||
*/
|
*/
|
||||||
remove() {
|
remove() {
|
||||||
try {
|
try {
|
||||||
Prometheus.monitorCertDaysRemaining?.remove(this.monitorLabelValues);
|
monitorCertDaysRemaining.remove(this.monitorLabelValues);
|
||||||
Prometheus.monitorCertIsValid?.remove(this.monitorLabelValues);
|
monitorCertIsValid.remove(this.monitorLabelValues);
|
||||||
Prometheus.monitorResponseTime?.remove(this.monitorLabelValues);
|
monitorResponseTime.remove(this.monitorLabelValues);
|
||||||
Prometheus.monitorStatus?.remove(this.monitorLabelValues);
|
monitorStatus.remove(this.monitorLabelValues);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ const { R } = require("redbean-node");
|
||||||
const HttpProxyAgent = require("http-proxy-agent");
|
const HttpProxyAgent = require("http-proxy-agent");
|
||||||
const HttpsProxyAgent = require("https-proxy-agent");
|
const HttpsProxyAgent = require("https-proxy-agent");
|
||||||
const SocksProxyAgent = require("socks-proxy-agent");
|
const SocksProxyAgent = require("socks-proxy-agent");
|
||||||
const { log } = require("../src/util");
|
const { debug } = require("../src/util");
|
||||||
const { UptimeKumaServer } = require("./uptime-kuma-server");
|
const { UptimeKumaServer } = require("./uptime-kuma-server");
|
||||||
const { CookieJar } = require("tough-cookie");
|
const { CookieJar } = require("tough-cookie");
|
||||||
const { createCookieAgent } = require("http-cookie-agent/http");
|
const { createCookieAgent } = require("http-cookie-agent/http");
|
||||||
|
@ -110,9 +110,9 @@ class Proxy {
|
||||||
proxyOptions.auth = `${proxy.username}:${proxy.password}`;
|
proxyOptions.auth = `${proxy.username}:${proxy.password}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("update-proxy", `Proxy Options: ${JSON.stringify(proxyOptions)}`);
|
debug(`Proxy Options: ${JSON.stringify(proxyOptions)}`);
|
||||||
log.debug("update-proxy", `HTTP Agent Options: ${JSON.stringify(httpAgentOptions)}`);
|
debug(`HTTP Agent Options: ${JSON.stringify(httpAgentOptions)}`);
|
||||||
log.debug("update-proxy", `HTTPS Agent Options: ${JSON.stringify(httpsAgentOptions)}`);
|
debug(`HTTPS Agent Options: ${JSON.stringify(httpsAgentOptions)}`);
|
||||||
|
|
||||||
switch (proxy.protocol) {
|
switch (proxy.protocol) {
|
||||||
case "http":
|
case "http":
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
let express = require("express");
|
let express = require("express");
|
||||||
const {
|
const {
|
||||||
|
setting,
|
||||||
allowDevAllOrigin,
|
allowDevAllOrigin,
|
||||||
allowAllOrigin,
|
allowAllOrigin,
|
||||||
percentageToColor,
|
percentageToColor,
|
||||||
|
@ -17,7 +18,6 @@ const { makeBadge } = require("badge-maker");
|
||||||
const { Prometheus } = require("../prometheus");
|
const { Prometheus } = require("../prometheus");
|
||||||
const Database = require("../database");
|
const Database = require("../database");
|
||||||
const { UptimeCalculator } = require("../uptime-calculator");
|
const { UptimeCalculator } = require("../uptime-calculator");
|
||||||
const { Settings } = require("../settings");
|
|
||||||
|
|
||||||
let router = express.Router();
|
let router = express.Router();
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ router.get("/api/entry-page", async (request, response) => {
|
||||||
|
|
||||||
let result = { };
|
let result = { };
|
||||||
let hostname = request.hostname;
|
let hostname = request.hostname;
|
||||||
if ((await Settings.get("trustProxy")) && request.headers["x-forwarded-host"]) {
|
if ((await setting("trustProxy")) && request.headers["x-forwarded-host"]) {
|
||||||
hostname = request.headers["x-forwarded-host"];
|
hostname = request.headers["x-forwarded-host"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -90,7 +90,8 @@ const Monitor = require("./model/monitor");
|
||||||
const User = require("./model/user");
|
const User = require("./model/user");
|
||||||
|
|
||||||
log.debug("server", "Importing Settings");
|
log.debug("server", "Importing Settings");
|
||||||
const { initJWTSecret, checkLogin, doubleCheckPassword, shake256, SHAKE256_LENGTH, allowDevAllOrigin } = require("./util-server");
|
const { getSettings, setSettings, setting, initJWTSecret, checkLogin, doubleCheckPassword, shake256, SHAKE256_LENGTH, allowDevAllOrigin,
|
||||||
|
} = require("./util-server");
|
||||||
|
|
||||||
log.debug("server", "Importing Notification");
|
log.debug("server", "Importing Notification");
|
||||||
const { Notification } = require("./notification");
|
const { Notification } = require("./notification");
|
||||||
|
@ -118,6 +119,27 @@ const port = config.port;
|
||||||
const disableFrameSameOrigin = !!process.env.UPTIME_KUMA_DISABLE_FRAME_SAMEORIGIN || args["disable-frame-sameorigin"] || false;
|
const disableFrameSameOrigin = !!process.env.UPTIME_KUMA_DISABLE_FRAME_SAMEORIGIN || args["disable-frame-sameorigin"] || false;
|
||||||
const cloudflaredToken = args["cloudflared-token"] || process.env.UPTIME_KUMA_CLOUDFLARED_TOKEN || undefined;
|
const cloudflaredToken = args["cloudflared-token"] || process.env.UPTIME_KUMA_CLOUDFLARED_TOKEN || undefined;
|
||||||
|
|
||||||
|
const ipsToAllow = process.env.UPTIME_KUMA_IPS_TO_ALLOW || args["ips-to-allow"] || undefined;
|
||||||
|
if (ipsToAllow !== undefined) {
|
||||||
|
if (typeof ipsToAllow !== "string") {
|
||||||
|
log.error("server", "IPs to allow must be a string, " + typeof ipsToAllow + " provided");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const splitIps = ipsToAllow.split(",");
|
||||||
|
const net = require("net");
|
||||||
|
for (const ip of splitIps) {
|
||||||
|
if (net.isIP(ip) === 0) {
|
||||||
|
log.error("server", "Provided IPs to allow must be valid IP addresses, " + ip + " provided");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("server", "IPs to allow: " + splitIps);
|
||||||
|
const ipfilter = require("express-ipfilter").IpFilter;
|
||||||
|
app.use(ipfilter(splitIps, { mode: "allow" }));
|
||||||
|
}
|
||||||
|
|
||||||
// 2FA / notp verification defaults
|
// 2FA / notp verification defaults
|
||||||
const twoFAVerifyOptions = {
|
const twoFAVerifyOptions = {
|
||||||
"window": 1,
|
"window": 1,
|
||||||
|
@ -200,7 +222,7 @@ let needSetup = false;
|
||||||
// Entry Page
|
// Entry Page
|
||||||
app.get("/", async (request, response) => {
|
app.get("/", async (request, response) => {
|
||||||
let hostname = request.hostname;
|
let hostname = request.hostname;
|
||||||
if (await Settings.get("trustProxy")) {
|
if (await setting("trustProxy")) {
|
||||||
const proxy = request.headers["x-forwarded-host"];
|
const proxy = request.headers["x-forwarded-host"];
|
||||||
if (proxy) {
|
if (proxy) {
|
||||||
hostname = proxy;
|
hostname = proxy;
|
||||||
|
@ -280,7 +302,7 @@ let needSetup = false;
|
||||||
// Robots.txt
|
// Robots.txt
|
||||||
app.get("/robots.txt", async (_request, response) => {
|
app.get("/robots.txt", async (_request, response) => {
|
||||||
let txt = "User-agent: *\nDisallow:";
|
let txt = "User-agent: *\nDisallow:";
|
||||||
if (!await Settings.get("searchEngineIndex")) {
|
if (!await setting("searchEngineIndex")) {
|
||||||
txt += " /";
|
txt += " /";
|
||||||
}
|
}
|
||||||
response.setHeader("Content-Type", "text/plain");
|
response.setHeader("Content-Type", "text/plain");
|
||||||
|
@ -717,6 +739,8 @@ let needSetup = false;
|
||||||
|
|
||||||
monitor.conditions = JSON.stringify(monitor.conditions);
|
monitor.conditions = JSON.stringify(monitor.conditions);
|
||||||
|
|
||||||
|
monitor.rabbitmqNodes = JSON.stringify(monitor.rabbitmqNodes);
|
||||||
|
|
||||||
bean.import(monitor);
|
bean.import(monitor);
|
||||||
bean.user_id = socket.userID;
|
bean.user_id = socket.userID;
|
||||||
|
|
||||||
|
@ -726,7 +750,7 @@ let needSetup = false;
|
||||||
|
|
||||||
await updateMonitorNotification(bean.id, notificationIDList);
|
await updateMonitorNotification(bean.id, notificationIDList);
|
||||||
|
|
||||||
await server.sendMonitorList(socket);
|
await server.sendUpdateMonitorIntoList(socket, bean.id);
|
||||||
|
|
||||||
if (monitor.active !== false) {
|
if (monitor.active !== false) {
|
||||||
await startMonitor(socket.userID, bean.id);
|
await startMonitor(socket.userID, bean.id);
|
||||||
|
@ -867,6 +891,9 @@ let needSetup = false;
|
||||||
bean.snmpOid = monitor.snmpOid;
|
bean.snmpOid = monitor.snmpOid;
|
||||||
bean.jsonPathOperator = monitor.jsonPathOperator;
|
bean.jsonPathOperator = monitor.jsonPathOperator;
|
||||||
bean.timeout = monitor.timeout;
|
bean.timeout = monitor.timeout;
|
||||||
|
bean.rabbitmqNodes = JSON.stringify(monitor.rabbitmqNodes);
|
||||||
|
bean.rabbitmqUsername = monitor.rabbitmqUsername;
|
||||||
|
bean.rabbitmqPassword = monitor.rabbitmqPassword;
|
||||||
bean.conditions = JSON.stringify(monitor.conditions);
|
bean.conditions = JSON.stringify(monitor.conditions);
|
||||||
|
|
||||||
bean.validate();
|
bean.validate();
|
||||||
|
@ -879,11 +906,11 @@ let needSetup = false;
|
||||||
|
|
||||||
await updateMonitorNotification(bean.id, monitor.notificationIDList);
|
await updateMonitorNotification(bean.id, monitor.notificationIDList);
|
||||||
|
|
||||||
if (await bean.isActive()) {
|
if (await Monitor.isActive(bean.id, bean.active)) {
|
||||||
await restartMonitor(socket.userID, bean.id);
|
await restartMonitor(socket.userID, bean.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
await server.sendMonitorList(socket);
|
await server.sendUpdateMonitorIntoList(socket, bean.id);
|
||||||
|
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
|
@ -923,14 +950,17 @@ let needSetup = false;
|
||||||
|
|
||||||
log.info("monitor", `Get Monitor: ${monitorID} User ID: ${socket.userID}`);
|
log.info("monitor", `Get Monitor: ${monitorID} User ID: ${socket.userID}`);
|
||||||
|
|
||||||
let bean = await R.findOne("monitor", " id = ? AND user_id = ? ", [
|
let monitor = await R.findOne("monitor", " id = ? AND user_id = ? ", [
|
||||||
monitorID,
|
monitorID,
|
||||||
socket.userID,
|
socket.userID,
|
||||||
]);
|
]);
|
||||||
|
const monitorData = [{ id: monitor.id,
|
||||||
|
active: monitor.active
|
||||||
|
}];
|
||||||
|
const preloadData = await Monitor.preparePreloadData(monitorData);
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
monitor: await bean.toJSON(),
|
monitor: monitor.toJSON(preloadData),
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -981,7 +1011,7 @@ let needSetup = false;
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
await startMonitor(socket.userID, monitorID);
|
await startMonitor(socket.userID, monitorID);
|
||||||
await server.sendMonitorList(socket);
|
await server.sendUpdateMonitorIntoList(socket, monitorID);
|
||||||
|
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
|
@ -1001,7 +1031,7 @@ let needSetup = false;
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
await pauseMonitor(socket.userID, monitorID);
|
await pauseMonitor(socket.userID, monitorID);
|
||||||
await server.sendMonitorList(socket);
|
await server.sendUpdateMonitorIntoList(socket, monitorID);
|
||||||
|
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
|
@ -1047,8 +1077,7 @@ let needSetup = false;
|
||||||
msg: "successDeleted",
|
msg: "successDeleted",
|
||||||
msgi18n: true,
|
msgi18n: true,
|
||||||
});
|
});
|
||||||
|
await server.sendDeleteMonitorFromList(socket, monitorID);
|
||||||
await server.sendMonitorList(socket);
|
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callback({
|
callback({
|
||||||
|
@ -1324,7 +1353,7 @@ let needSetup = false;
|
||||||
socket.on("getSettings", async (callback) => {
|
socket.on("getSettings", async (callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
const data = await Settings.getSettings("general");
|
const data = await getSettings("general");
|
||||||
|
|
||||||
if (!data.serverTimezone) {
|
if (!data.serverTimezone) {
|
||||||
data.serverTimezone = await server.getTimezone();
|
data.serverTimezone = await server.getTimezone();
|
||||||
|
@ -1352,7 +1381,7 @@ let needSetup = false;
|
||||||
// Disabled Auth + Want to Enable Auth => No Check
|
// Disabled Auth + Want to Enable Auth => No Check
|
||||||
// Enabled Auth + Want to Disable Auth => Check!!
|
// Enabled Auth + Want to Disable Auth => Check!!
|
||||||
// Enabled Auth + Want to Enable Auth => No Check
|
// Enabled Auth + Want to Enable Auth => No Check
|
||||||
const currentDisabledAuth = await Settings.get("disableAuth");
|
const currentDisabledAuth = await setting("disableAuth");
|
||||||
if (!currentDisabledAuth && data.disableAuth) {
|
if (!currentDisabledAuth && data.disableAuth) {
|
||||||
await doubleCheckPassword(socket, currentPassword);
|
await doubleCheckPassword(socket, currentPassword);
|
||||||
}
|
}
|
||||||
|
@ -1366,7 +1395,7 @@ let needSetup = false;
|
||||||
const previousChromeExecutable = await Settings.get("chromeExecutable");
|
const previousChromeExecutable = await Settings.get("chromeExecutable");
|
||||||
const previousNSCDStatus = await Settings.get("nscd");
|
const previousNSCDStatus = await Settings.get("nscd");
|
||||||
|
|
||||||
await Settings.setSettings("general", data);
|
await setSettings("general", data);
|
||||||
server.entryPage = data.entryPage;
|
server.entryPage = data.entryPage;
|
||||||
|
|
||||||
// Also need to apply timezone globally
|
// Also need to apply timezone globally
|
||||||
|
@ -1462,7 +1491,7 @@ let needSetup = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("server", e);
|
console.error(e);
|
||||||
|
|
||||||
callback({
|
callback({
|
||||||
ok: false,
|
ok: false,
|
||||||
|
@ -1575,7 +1604,7 @@ let needSetup = false;
|
||||||
// ***************************
|
// ***************************
|
||||||
|
|
||||||
log.debug("auth", "check auto login");
|
log.debug("auth", "check auto login");
|
||||||
if (await Settings.get("disableAuth")) {
|
if (await setting("disableAuth")) {
|
||||||
log.info("auth", "Disabled Auth: auto login to admin");
|
log.info("auth", "Disabled Auth: auto login to admin");
|
||||||
await afterLogin(socket, await R.findOne("user"));
|
await afterLogin(socket, await R.findOne("user"));
|
||||||
socket.emit("autoLogin");
|
socket.emit("autoLogin");
|
||||||
|
@ -1596,18 +1625,20 @@ let needSetup = false;
|
||||||
|
|
||||||
await server.start();
|
await server.start();
|
||||||
|
|
||||||
server.httpServer.listen(port, hostname, () => {
|
server.httpServer.listen(port, hostname, async () => {
|
||||||
if (hostname) {
|
if (hostname) {
|
||||||
log.info("server", `Listening on ${hostname}:${port}`);
|
log.info("server", `Listening on ${hostname}:${port}`);
|
||||||
} else {
|
} else {
|
||||||
log.info("server", `Listening on ${port}`);
|
log.info("server", `Listening on ${port}`);
|
||||||
}
|
}
|
||||||
startMonitors();
|
await startMonitors();
|
||||||
|
|
||||||
|
// Put this here. Start background jobs after the db and server is ready to prevent clear up during db migration.
|
||||||
|
await initBackgroundJobs();
|
||||||
|
|
||||||
checkVersion.startInterval();
|
checkVersion.startInterval();
|
||||||
});
|
});
|
||||||
|
|
||||||
await initBackgroundJobs();
|
|
||||||
|
|
||||||
// Start cloudflared at the end if configured
|
// Start cloudflared at the end if configured
|
||||||
await cloudflaredAutoStart(cloudflaredToken);
|
await cloudflaredAutoStart(cloudflaredToken);
|
||||||
|
|
||||||
|
@ -1678,13 +1709,13 @@ async function afterLogin(socket, user) {
|
||||||
|
|
||||||
await StatusPage.sendStatusPageList(io, socket);
|
await StatusPage.sendStatusPageList(io, socket);
|
||||||
|
|
||||||
|
const monitorPromises = [];
|
||||||
for (let monitorID in monitorList) {
|
for (let monitorID in monitorList) {
|
||||||
await sendHeartbeatList(socket, monitorID);
|
monitorPromises.push(sendHeartbeatList(socket, monitorID));
|
||||||
|
monitorPromises.push(Monitor.sendStats(io, monitorID, user.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let monitorID in monitorList) {
|
await Promise.all(monitorPromises);
|
||||||
await Monitor.sendStats(io, monitorID, user.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set server timezone from client browser if not set
|
// Set server timezone from client browser if not set
|
||||||
// It should be run once only
|
// It should be run once only
|
||||||
|
@ -1801,7 +1832,11 @@ async function startMonitors() {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let monitor of list) {
|
for (let monitor of list) {
|
||||||
|
try {
|
||||||
await monitor.start(io);
|
await monitor.start(io);
|
||||||
|
} catch (e) {
|
||||||
|
log.error("monitor", e);
|
||||||
|
}
|
||||||
// Give some delays, so all monitors won't make request at the same moment when just start the server.
|
// Give some delays, so all monitors won't make request at the same moment when just start the server.
|
||||||
await sleep(getRandomInt(300, 1000));
|
await sleep(getRandomInt(300, 1000));
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,7 @@ module.exports.apiKeySocketHandler = (socket) => {
|
||||||
ok: true,
|
ok: true,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("apikeys", e);
|
console.error(e);
|
||||||
callback({
|
callback({
|
||||||
ok: false,
|
ok: false,
|
||||||
msg: e.message,
|
msg: e.message,
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
const { checkLogin, doubleCheckPassword } = require("../util-server");
|
const { checkLogin, setSetting, setting, doubleCheckPassword } = require("../util-server");
|
||||||
const { CloudflaredTunnel } = require("node-cloudflared-tunnel");
|
const { CloudflaredTunnel } = require("node-cloudflared-tunnel");
|
||||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||||
const { log } = require("../../src/util");
|
const { log } = require("../../src/util");
|
||||||
const { Settings } = require("../settings");
|
|
||||||
const io = UptimeKumaServer.getInstance().io;
|
const io = UptimeKumaServer.getInstance().io;
|
||||||
|
|
||||||
const prefix = "cloudflared_";
|
const prefix = "cloudflared_";
|
||||||
|
@ -41,7 +40,7 @@ module.exports.cloudflaredSocketHandler = (socket) => {
|
||||||
socket.join("cloudflared");
|
socket.join("cloudflared");
|
||||||
io.to(socket.userID).emit(prefix + "installed", cloudflared.checkInstalled());
|
io.to(socket.userID).emit(prefix + "installed", cloudflared.checkInstalled());
|
||||||
io.to(socket.userID).emit(prefix + "running", cloudflared.running);
|
io.to(socket.userID).emit(prefix + "running", cloudflared.running);
|
||||||
io.to(socket.userID).emit(prefix + "token", await Settings.get("cloudflaredTunnelToken"));
|
io.to(socket.userID).emit(prefix + "token", await setting("cloudflaredTunnelToken"));
|
||||||
} catch (error) { }
|
} catch (error) { }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -56,7 +55,7 @@ module.exports.cloudflaredSocketHandler = (socket) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
if (token && typeof token === "string") {
|
if (token && typeof token === "string") {
|
||||||
await Settings.set("cloudflaredTunnelToken", token);
|
await setSetting("cloudflaredTunnelToken", token);
|
||||||
cloudflared.token = token;
|
cloudflared.token = token;
|
||||||
} else {
|
} else {
|
||||||
cloudflared.token = null;
|
cloudflared.token = null;
|
||||||
|
@ -68,7 +67,7 @@ module.exports.cloudflaredSocketHandler = (socket) => {
|
||||||
socket.on(prefix + "stop", async (currentPassword, callback) => {
|
socket.on(prefix + "stop", async (currentPassword, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
const disabledAuth = await Settings.get("disableAuth");
|
const disabledAuth = await setting("disableAuth");
|
||||||
if (!disabledAuth) {
|
if (!disabledAuth) {
|
||||||
await doubleCheckPassword(socket, currentPassword);
|
await doubleCheckPassword(socket, currentPassword);
|
||||||
}
|
}
|
||||||
|
@ -84,7 +83,7 @@ module.exports.cloudflaredSocketHandler = (socket) => {
|
||||||
socket.on(prefix + "removeToken", async () => {
|
socket.on(prefix + "removeToken", async () => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
await Settings.set("cloudflaredTunnelToken", "");
|
await setSetting("cloudflaredTunnelToken", "");
|
||||||
} catch (error) { }
|
} catch (error) { }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -97,15 +96,15 @@ module.exports.cloudflaredSocketHandler = (socket) => {
|
||||||
*/
|
*/
|
||||||
module.exports.autoStart = async (token) => {
|
module.exports.autoStart = async (token) => {
|
||||||
if (!token) {
|
if (!token) {
|
||||||
token = await Settings.get("cloudflaredTunnelToken");
|
token = await setting("cloudflaredTunnelToken");
|
||||||
} else {
|
} else {
|
||||||
// Override the current token via args or env var
|
// Override the current token via args or env var
|
||||||
await Settings.set("cloudflaredTunnelToken", token);
|
await setSetting("cloudflaredTunnelToken", token);
|
||||||
log.info("cloudflare", "Use cloudflared token from args or env var");
|
console.log("Use cloudflared token from args or env var");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
log.info("cloudflare", "Start cloudflared");
|
console.log("Start cloudflared");
|
||||||
cloudflared.token = token;
|
cloudflared.token = token;
|
||||||
cloudflared.start();
|
cloudflared.start();
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("maintenance", e);
|
console.error(e);
|
||||||
callback({
|
callback({
|
||||||
ok: false,
|
ok: false,
|
||||||
msg: e.message,
|
msg: e.message,
|
||||||
|
@ -177,7 +177,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
|
||||||
ok: true,
|
ok: true,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("maintenance", e);
|
console.error(e);
|
||||||
callback({
|
callback({
|
||||||
ok: false,
|
ok: false,
|
||||||
msg: e.message,
|
msg: e.message,
|
||||||
|
@ -201,7 +201,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("maintenance", e);
|
console.error(e);
|
||||||
callback({
|
callback({
|
||||||
ok: false,
|
ok: false,
|
||||||
msg: e.message,
|
msg: e.message,
|
||||||
|
@ -225,7 +225,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("maintenance", e);
|
console.error(e);
|
||||||
callback({
|
callback({
|
||||||
ok: false,
|
ok: false,
|
||||||
msg: e.message,
|
msg: e.message,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const { checkLogin } = require("../util-server");
|
const { checkLogin, setSetting } = require("../util-server");
|
||||||
const dayjs = require("dayjs");
|
const dayjs = require("dayjs");
|
||||||
const { log } = require("../../src/util");
|
const { log } = require("../../src/util");
|
||||||
const ImageDataURI = require("../image-data-uri");
|
const ImageDataURI = require("../image-data-uri");
|
||||||
|
@ -7,7 +7,6 @@ const Database = require("../database");
|
||||||
const apicache = require("../modules/apicache");
|
const apicache = require("../modules/apicache");
|
||||||
const StatusPage = require("../model/status_page");
|
const StatusPage = require("../model/status_page");
|
||||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||||
const { Settings } = require("../settings");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Socket handlers for status page
|
* Socket handlers for status page
|
||||||
|
@ -234,7 +233,7 @@ module.exports.statusPageSocketHandler = (socket) => {
|
||||||
// Also change entry page to new slug if it is the default one, and slug is changed.
|
// Also change entry page to new slug if it is the default one, and slug is changed.
|
||||||
if (server.entryPage === "statusPage-" + slug && statusPage.slug !== slug) {
|
if (server.entryPage === "statusPage-" + slug && statusPage.slug !== slug) {
|
||||||
server.entryPage = "statusPage-" + statusPage.slug;
|
server.entryPage = "statusPage-" + statusPage.slug;
|
||||||
await Settings.set("entryPage", server.entryPage, "general");
|
await setSetting("entryPage", server.entryPage, "general");
|
||||||
}
|
}
|
||||||
|
|
||||||
apicache.clear();
|
apicache.clear();
|
||||||
|
@ -292,7 +291,7 @@ module.exports.statusPageSocketHandler = (socket) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error("socket", error);
|
console.error(error);
|
||||||
callback({
|
callback({
|
||||||
ok: false,
|
ok: false,
|
||||||
msg: error.message,
|
msg: error.message,
|
||||||
|
@ -314,7 +313,7 @@ module.exports.statusPageSocketHandler = (socket) => {
|
||||||
// Reset entry page if it is the default one.
|
// Reset entry page if it is the default one.
|
||||||
if (server.entryPage === "statusPage-" + slug) {
|
if (server.entryPage === "statusPage-" + slug) {
|
||||||
server.entryPage = "dashboard";
|
server.entryPage = "dashboard";
|
||||||
await Settings.set("entryPage", server.entryPage, "general");
|
await setSetting("entryPage", server.entryPage, "general");
|
||||||
}
|
}
|
||||||
|
|
||||||
// No need to delete records from `status_page_cname`, because it has cascade foreign key.
|
// No need to delete records from `status_page_cname`, because it has cascade foreign key.
|
||||||
|
|
|
@ -12,7 +12,6 @@ class UptimeCalculator {
|
||||||
* @private
|
* @private
|
||||||
* @type {{string:UptimeCalculator}}
|
* @type {{string:UptimeCalculator}}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static list = {};
|
static list = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -55,6 +54,15 @@ class UptimeCalculator {
|
||||||
lastHourlyStatBean = null;
|
lastHourlyStatBean = null;
|
||||||
lastMinutelyStatBean = null;
|
lastMinutelyStatBean = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For migration purposes.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
migrationMode = false;
|
||||||
|
|
||||||
|
statMinutelyKeepHour = 24;
|
||||||
|
statHourlyKeepDay = 30;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the uptime calculator for a monitor
|
* Get the uptime calculator for a monitor
|
||||||
* Initializes and returns the monitor if it does not exist
|
* Initializes and returns the monitor if it does not exist
|
||||||
|
@ -189,16 +197,19 @@ class UptimeCalculator {
|
||||||
/**
|
/**
|
||||||
* @param {number} status status
|
* @param {number} status status
|
||||||
* @param {number} ping Ping
|
* @param {number} ping Ping
|
||||||
|
* @param {dayjs.Dayjs} date Date (Only for migration)
|
||||||
* @returns {dayjs.Dayjs} date
|
* @returns {dayjs.Dayjs} date
|
||||||
* @throws {Error} Invalid status
|
* @throws {Error} Invalid status
|
||||||
*/
|
*/
|
||||||
async update(status, ping = 0) {
|
async update(status, ping = 0, date) {
|
||||||
let date = this.getCurrentDate();
|
if (!date) {
|
||||||
|
date = this.getCurrentDate();
|
||||||
|
}
|
||||||
|
|
||||||
let flatStatus = this.flatStatus(status);
|
let flatStatus = this.flatStatus(status);
|
||||||
|
|
||||||
if (flatStatus === DOWN && ping > 0) {
|
if (flatStatus === DOWN && ping > 0) {
|
||||||
log.warn("uptime-calc", "The ping is not effective when the status is DOWN");
|
log.debug("uptime-calc", "The ping is not effective when the status is DOWN");
|
||||||
}
|
}
|
||||||
|
|
||||||
let divisionKey = this.getMinutelyKey(date);
|
let divisionKey = this.getMinutelyKey(date);
|
||||||
|
@ -297,6 +308,11 @@ class UptimeCalculator {
|
||||||
}
|
}
|
||||||
await R.store(dailyStatBean);
|
await R.store(dailyStatBean);
|
||||||
|
|
||||||
|
let currentDate = this.getCurrentDate();
|
||||||
|
|
||||||
|
// For migration mode, we don't need to store old hourly and minutely data, but we need 30-day's hourly data
|
||||||
|
// Run anyway for non-migration mode
|
||||||
|
if (!this.migrationMode || date.isAfter(currentDate.subtract(this.statHourlyKeepDay, "day"))) {
|
||||||
let hourlyStatBean = await this.getHourlyStatBean(hourlyKey);
|
let hourlyStatBean = await this.getHourlyStatBean(hourlyKey);
|
||||||
hourlyStatBean.up = hourlyData.up;
|
hourlyStatBean.up = hourlyData.up;
|
||||||
hourlyStatBean.down = hourlyData.down;
|
hourlyStatBean.down = hourlyData.down;
|
||||||
|
@ -311,7 +327,11 @@ class UptimeCalculator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await R.store(hourlyStatBean);
|
await R.store(hourlyStatBean);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For migration mode, we don't need to store old hourly and minutely data, but we need 24-hour's minutely data
|
||||||
|
// Run anyway for non-migration mode
|
||||||
|
if (!this.migrationMode || date.isAfter(currentDate.subtract(this.statMinutelyKeepHour, "hour"))) {
|
||||||
let minutelyStatBean = await this.getMinutelyStatBean(divisionKey);
|
let minutelyStatBean = await this.getMinutelyStatBean(divisionKey);
|
||||||
minutelyStatBean.up = minutelyData.up;
|
minutelyStatBean.up = minutelyData.up;
|
||||||
minutelyStatBean.down = minutelyData.down;
|
minutelyStatBean.down = minutelyData.down;
|
||||||
|
@ -326,18 +346,23 @@ class UptimeCalculator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await R.store(minutelyStatBean);
|
await R.store(minutelyStatBean);
|
||||||
|
}
|
||||||
|
|
||||||
|
// No need to remove old data in migration mode
|
||||||
|
if (!this.migrationMode) {
|
||||||
// Remove the old data
|
// Remove the old data
|
||||||
|
// TODO: Improvement: Convert it to a job?
|
||||||
log.debug("uptime-calc", "Remove old data");
|
log.debug("uptime-calc", "Remove old data");
|
||||||
await R.exec("DELETE FROM stat_minutely WHERE monitor_id = ? AND timestamp < ?", [
|
await R.exec("DELETE FROM stat_minutely WHERE monitor_id = ? AND timestamp < ?", [
|
||||||
this.monitorID,
|
this.monitorID,
|
||||||
this.getMinutelyKey(date.subtract(24, "hour")),
|
this.getMinutelyKey(currentDate.subtract(this.statMinutelyKeepHour, "hour")),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await R.exec("DELETE FROM stat_hourly WHERE monitor_id = ? AND timestamp < ?", [
|
await R.exec("DELETE FROM stat_hourly WHERE monitor_id = ? AND timestamp < ?", [
|
||||||
this.monitorID,
|
this.monitorID,
|
||||||
this.getHourlyKey(date.subtract(30, "day")),
|
this.getHourlyKey(currentDate.subtract(this.statHourlyKeepDay, "day")),
|
||||||
]);
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
return date;
|
return date;
|
||||||
}
|
}
|
||||||
|
@ -812,6 +837,14 @@ class UptimeCalculator {
|
||||||
return dayjs.utc();
|
return dayjs.utc();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For migration purposes.
|
||||||
|
* @param {boolean} value Migration mode on/off
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
setMigrationMode(value) {
|
||||||
|
this.migrationMode = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class UptimeDataResult {
|
class UptimeDataResult {
|
||||||
|
|
|
@ -115,6 +115,7 @@ class UptimeKumaServer {
|
||||||
UptimeKumaServer.monitorTypeList["mqtt"] = new MqttMonitorType();
|
UptimeKumaServer.monitorTypeList["mqtt"] = new MqttMonitorType();
|
||||||
UptimeKumaServer.monitorTypeList["snmp"] = new SNMPMonitorType();
|
UptimeKumaServer.monitorTypeList["snmp"] = new SNMPMonitorType();
|
||||||
UptimeKumaServer.monitorTypeList["mongodb"] = new MongodbMonitorType();
|
UptimeKumaServer.monitorTypeList["mongodb"] = new MongodbMonitorType();
|
||||||
|
UptimeKumaServer.monitorTypeList["rabbitmq"] = new RabbitMqMonitorType();
|
||||||
|
|
||||||
// Allow all CORS origins (polling) in development
|
// Allow all CORS origins (polling) in development
|
||||||
let cors = undefined;
|
let cors = undefined;
|
||||||
|
@ -205,24 +206,56 @@ class UptimeKumaServer {
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update Monitor into list
|
||||||
|
* @param {Socket} socket Socket to send list on
|
||||||
|
* @param {number} monitorID update or deleted monitor id
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async sendUpdateMonitorIntoList(socket, monitorID) {
|
||||||
|
let list = await this.getMonitorJSONList(socket.userID, monitorID);
|
||||||
|
this.io.to(socket.userID).emit("updateMonitorIntoList", list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete Monitor from list
|
||||||
|
* @param {Socket} socket Socket to send list on
|
||||||
|
* @param {number} monitorID update or deleted monitor id
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async sendDeleteMonitorFromList(socket, monitorID) {
|
||||||
|
this.io.to(socket.userID).emit("deleteMonitorFromList", monitorID);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a list of monitors for the given user.
|
* Get a list of monitors for the given user.
|
||||||
* @param {string} userID - The ID of the user to get monitors for.
|
* @param {string} userID - The ID of the user to get monitors for.
|
||||||
|
* @param {number} monitorID - The ID of monitor for.
|
||||||
* @returns {Promise<object>} A promise that resolves to an object with monitor IDs as keys and monitor objects as values.
|
* @returns {Promise<object>} A promise that resolves to an object with monitor IDs as keys and monitor objects as values.
|
||||||
*
|
*
|
||||||
* Generated by Trelent
|
* Generated by Trelent
|
||||||
*/
|
*/
|
||||||
async getMonitorJSONList(userID) {
|
async getMonitorJSONList(userID, monitorID = null) {
|
||||||
let result = {};
|
|
||||||
|
|
||||||
let monitorList = await R.find("monitor", " user_id = ? ORDER BY weight DESC, name", [
|
let query = " user_id = ? ";
|
||||||
userID,
|
let queryParams = [ userID ];
|
||||||
]);
|
|
||||||
|
|
||||||
for (let monitor of monitorList) {
|
if (monitorID) {
|
||||||
result[monitor.id] = await monitor.toJSON();
|
query += "AND id = ? ";
|
||||||
|
queryParams.push(monitorID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let monitorList = await R.find("monitor", query + "ORDER BY weight DESC, name", queryParams);
|
||||||
|
|
||||||
|
const monitorData = monitorList.map(monitor => ({
|
||||||
|
id: monitor.id,
|
||||||
|
active: monitor.active,
|
||||||
|
name: monitor.name,
|
||||||
|
}));
|
||||||
|
const preloadData = await Monitor.preparePreloadData(monitorData);
|
||||||
|
|
||||||
|
const result = {};
|
||||||
|
monitorList.forEach(monitor => result[monitor.id] = monitor.toJSON(preloadData));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -520,3 +553,5 @@ const { DnsMonitorType } = require("./monitor-types/dns");
|
||||||
const { MqttMonitorType } = require("./monitor-types/mqtt");
|
const { MqttMonitorType } = require("./monitor-types/mqtt");
|
||||||
const { SNMPMonitorType } = require("./monitor-types/snmp");
|
const { SNMPMonitorType } = require("./monitor-types/snmp");
|
||||||
const { MongodbMonitorType } = require("./monitor-types/mongodb");
|
const { MongodbMonitorType } = require("./monitor-types/mongodb");
|
||||||
|
const { RabbitMqMonitorType } = require("./monitor-types/rabbitmq");
|
||||||
|
const Monitor = require("./model/monitor");
|
||||||
|
|
|
@ -12,6 +12,7 @@ const { Client } = require("pg");
|
||||||
const postgresConParse = require("pg-connection-string").parse;
|
const postgresConParse = require("pg-connection-string").parse;
|
||||||
const mysql = require("mysql2");
|
const mysql = require("mysql2");
|
||||||
const { NtlmClient } = require("./modules/axios-ntlm/lib/ntlmClient.js");
|
const { NtlmClient } = require("./modules/axios-ntlm/lib/ntlmClient.js");
|
||||||
|
const { Settings } = require("./settings");
|
||||||
const grpc = require("@grpc/grpc-js");
|
const grpc = require("@grpc/grpc-js");
|
||||||
const protojs = require("protobufjs");
|
const protojs = require("protobufjs");
|
||||||
const radiusClient = require("node-radius-client");
|
const radiusClient = require("node-radius-client");
|
||||||
|
@ -520,6 +521,46 @@ exports.redisPingAsync = function (dsn, rejectUnauthorized) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve value of setting based on key
|
||||||
|
* @param {string} key Key of setting to retrieve
|
||||||
|
* @returns {Promise<any>} Value
|
||||||
|
* @deprecated Use await Settings.get(key)
|
||||||
|
*/
|
||||||
|
exports.setting = async function (key) {
|
||||||
|
return await Settings.get(key);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the specified setting to specified value
|
||||||
|
* @param {string} key Key of setting to set
|
||||||
|
* @param {any} value Value to set to
|
||||||
|
* @param {?string} type Type of setting
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
exports.setSetting = async function (key, value, type = null) {
|
||||||
|
await Settings.set(key, value, type);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get settings based on type
|
||||||
|
* @param {string} type The type of setting
|
||||||
|
* @returns {Promise<Bean>} Settings of requested type
|
||||||
|
*/
|
||||||
|
exports.getSettings = async function (type) {
|
||||||
|
return await Settings.getSettings(type);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set settings based on type
|
||||||
|
* @param {string} type Type of settings to set
|
||||||
|
* @param {object} data Values of settings
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
exports.setSettings = async function (type, data) {
|
||||||
|
await Settings.setSettings(type, data);
|
||||||
|
};
|
||||||
|
|
||||||
// ssl-checker by @dyaa
|
// ssl-checker by @dyaa
|
||||||
//https://github.com/dyaa/ssl-checker/blob/master/src/index.ts
|
//https://github.com/dyaa/ssl-checker/blob/master/src/index.ts
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,18 @@
|
||||||
<div
|
<div
|
||||||
v-for="(beat, index) in shortBeatList"
|
v-for="(beat, index) in shortBeatList"
|
||||||
:key="index"
|
:key="index"
|
||||||
|
class="beat-hover-area"
|
||||||
|
:class="{ 'empty': (beat === 0) }"
|
||||||
|
:style="beatHoverAreaStyle"
|
||||||
|
:title="getBeatTitle(beat)"
|
||||||
|
>
|
||||||
|
<div
|
||||||
class="beat"
|
class="beat"
|
||||||
:class="{ 'empty': (beat === 0), 'down': (beat.status === 0), 'pending': (beat.status === 2), 'maintenance': (beat.status === 3) }"
|
:class="{ 'empty': (beat === 0), 'down': (beat.status === 0), 'pending': (beat.status === 2), 'maintenance': (beat.status === 3) }"
|
||||||
:style="beatStyle"
|
:style="beatStyle"
|
||||||
:title="getBeatTitle(beat)"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="!$root.isMobile && size !== 'small' && beatList.length > 4 && $root.styleElapsedTime !== 'none'"
|
v-if="!$root.isMobile && size !== 'small' && beatList.length > 4 && $root.styleElapsedTime !== 'none'"
|
||||||
class="d-flex justify-content-between align-items-center word" :style="timeStyle"
|
class="d-flex justify-content-between align-items-center word" :style="timeStyle"
|
||||||
|
@ -47,7 +53,7 @@ export default {
|
||||||
beatWidth: 10,
|
beatWidth: 10,
|
||||||
beatHeight: 30,
|
beatHeight: 30,
|
||||||
hoverScale: 1.5,
|
hoverScale: 1.5,
|
||||||
beatMargin: 4,
|
beatHoverAreaPadding: 4,
|
||||||
move: false,
|
move: false,
|
||||||
maxBeat: -1,
|
maxBeat: -1,
|
||||||
};
|
};
|
||||||
|
@ -123,7 +129,7 @@ export default {
|
||||||
|
|
||||||
barStyle() {
|
barStyle() {
|
||||||
if (this.move && this.shortBeatList.length > this.maxBeat) {
|
if (this.move && this.shortBeatList.length > this.maxBeat) {
|
||||||
let width = -(this.beatWidth + this.beatMargin * 2);
|
let width = -(this.beatWidth + this.beatHoverAreaPadding * 2);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
transition: "all ease-in-out 0.25s",
|
transition: "all ease-in-out 0.25s",
|
||||||
|
@ -137,12 +143,17 @@ export default {
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
beatHoverAreaStyle() {
|
||||||
|
return {
|
||||||
|
padding: this.beatHoverAreaPadding + "px",
|
||||||
|
"--hover-scale": this.hoverScale,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
beatStyle() {
|
beatStyle() {
|
||||||
return {
|
return {
|
||||||
width: this.beatWidth + "px",
|
width: this.beatWidth + "px",
|
||||||
height: this.beatHeight + "px",
|
height: this.beatHeight + "px",
|
||||||
margin: this.beatMargin + "px",
|
|
||||||
"--hover-scale": this.hoverScale,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -152,7 +163,7 @@ export default {
|
||||||
*/
|
*/
|
||||||
timeStyle() {
|
timeStyle() {
|
||||||
return {
|
return {
|
||||||
"margin-left": this.numPadding * (this.beatWidth + this.beatMargin * 2) + "px",
|
"margin-left": this.numPadding * (this.beatWidth + this.beatHoverAreaPadding * 2) + "px",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -219,20 +230,20 @@ export default {
|
||||||
if (this.size !== "big") {
|
if (this.size !== "big") {
|
||||||
this.beatWidth = 5;
|
this.beatWidth = 5;
|
||||||
this.beatHeight = 16;
|
this.beatHeight = 16;
|
||||||
this.beatMargin = 2;
|
this.beatHoverAreaPadding = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Suddenly, have an idea how to handle it universally.
|
// Suddenly, have an idea how to handle it universally.
|
||||||
// If the pixel * ratio != Integer, then it causes render issue, round it to solve it!!
|
// If the pixel * ratio != Integer, then it causes render issue, round it to solve it!!
|
||||||
const actualWidth = this.beatWidth * window.devicePixelRatio;
|
const actualWidth = this.beatWidth * window.devicePixelRatio;
|
||||||
const actualMargin = this.beatMargin * window.devicePixelRatio;
|
const actualHoverAreaPadding = this.beatHoverAreaPadding * window.devicePixelRatio;
|
||||||
|
|
||||||
if (!Number.isInteger(actualWidth)) {
|
if (!Number.isInteger(actualWidth)) {
|
||||||
this.beatWidth = Math.round(actualWidth) / window.devicePixelRatio;
|
this.beatWidth = Math.round(actualWidth) / window.devicePixelRatio;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Number.isInteger(actualMargin)) {
|
if (!Number.isInteger(actualHoverAreaPadding)) {
|
||||||
this.beatMargin = Math.round(actualMargin) / window.devicePixelRatio;
|
this.beatHoverAreaPadding = Math.round(actualHoverAreaPadding) / window.devicePixelRatio;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("resize", this.resize);
|
window.addEventListener("resize", this.resize);
|
||||||
|
@ -245,7 +256,7 @@ export default {
|
||||||
*/
|
*/
|
||||||
resize() {
|
resize() {
|
||||||
if (this.$refs.wrap) {
|
if (this.$refs.wrap) {
|
||||||
this.maxBeat = Math.floor(this.$refs.wrap.clientWidth / (this.beatWidth + this.beatMargin * 2));
|
this.maxBeat = Math.floor(this.$refs.wrap.clientWidth / (this.beatWidth + this.beatHoverAreaPadding * 2));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -273,11 +284,25 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.hp-bar-big {
|
.hp-bar-big {
|
||||||
.beat {
|
.beat-hover-area {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
||||||
|
&:not(.empty):hover {
|
||||||
|
transition: all ease-in-out 0.15s;
|
||||||
|
opacity: 0.8;
|
||||||
|
transform: scale(var(--hover-scale));
|
||||||
|
}
|
||||||
|
|
||||||
|
.beat {
|
||||||
background-color: $primary;
|
background-color: $primary;
|
||||||
border-radius: $border-radius;
|
border-radius: $border-radius;
|
||||||
|
|
||||||
|
/*
|
||||||
|
pointer-events needs to be changed because
|
||||||
|
tooltip momentarily disappears when crossing between .beat-hover-area and .beat
|
||||||
|
*/
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
&.empty {
|
&.empty {
|
||||||
background-color: aliceblue;
|
background-color: aliceblue;
|
||||||
}
|
}
|
||||||
|
@ -293,11 +318,6 @@ export default {
|
||||||
&.maintenance {
|
&.maintenance {
|
||||||
background-color: $maintenance;
|
background-color: $maintenance;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(.empty):hover {
|
|
||||||
transition: all ease-in-out 0.15s;
|
|
||||||
opacity: 0.8;
|
|
||||||
transform: scale(var(--hover-scale));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,6 +118,7 @@ export default {
|
||||||
"clicksendsms": "ClickSend SMS",
|
"clicksendsms": "ClickSend SMS",
|
||||||
"CallMeBot": "CallMeBot (WhatsApp, Telegram Call, Facebook Messanger)",
|
"CallMeBot": "CallMeBot (WhatsApp, Telegram Call, Facebook Messanger)",
|
||||||
"discord": "Discord",
|
"discord": "Discord",
|
||||||
|
"Elks": "46elks",
|
||||||
"GoogleChat": "Google Chat (Google Workspace)",
|
"GoogleChat": "Google Chat (Google Workspace)",
|
||||||
"gorush": "Gorush",
|
"gorush": "Gorush",
|
||||||
"gotify": "Gotify",
|
"gotify": "Gotify",
|
||||||
|
@ -164,6 +165,7 @@ export default {
|
||||||
"whapi": "WhatsApp (Whapi)",
|
"whapi": "WhatsApp (Whapi)",
|
||||||
"gtxmessaging": "GtxMessaging",
|
"gtxmessaging": "GtxMessaging",
|
||||||
"Cellsynt": "Cellsynt",
|
"Cellsynt": "Cellsynt",
|
||||||
|
"SendGrid": "SendGrid"
|
||||||
};
|
};
|
||||||
|
|
||||||
// Put notifications here if it's not supported in most regions or its documentation is not in English
|
// Put notifications here if it's not supported in most regions or its documentation is not in English
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
<template #item="monitor">
|
<template #item="monitor">
|
||||||
<div class="item" data-testid="monitor">
|
<div class="item" data-testid="monitor">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 col-md-4 small-padding">
|
<div class="col-9 col-md-8 small-padding">
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<font-awesome-icon v-if="editMode" icon="arrows-alt-v" class="action drag me-3" />
|
<font-awesome-icon v-if="editMode" icon="arrows-alt-v" class="action drag me-3" />
|
||||||
<font-awesome-icon v-if="editMode" icon="times" class="action remove me-3" @click="removeMonitor(group.index, monitor.index)" />
|
<font-awesome-icon v-if="editMode" icon="times" class="action remove me-3" @click="removeMonitor(group.index, monitor.index)" />
|
||||||
|
@ -71,7 +71,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div :key="$root.userHeartbeatBar" class="col-6 col-md-8">
|
<div :key="$root.userHeartbeatBar" class="col-3 col-md-4">
|
||||||
<HeartbeatBar size="mid" :monitor-id="monitor.element.id" />
|
<HeartbeatBar size="mid" :monitor-id="monitor.element.id" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
48
src/components/notifications/46elks.vue
Normal file
48
src/components/notifications/46elks.vue
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<template>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="ElksUsername" class="form-label">{{ $t("Username") }}</label>
|
||||||
|
<input id="ElksUsername" v-model="$parent.notification.elksUsername" type="text" class="form-control" required>
|
||||||
|
<label for="ElksPassword" class="form-label">{{ $t("Password") }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-text">
|
||||||
|
<HiddenInput id="ElksPassword" v-model="$parent.notification.elksAuthToken" :required="true" autocomplete="new-password"></HiddenInput>
|
||||||
|
<i18n-t tag="p" keypath="Can be found on:">
|
||||||
|
<a href="https://46elks.com/account" target="_blank">https://46elks.com/account</a>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="Elks-from-number" class="form-label">{{ $t("From") }}</label>
|
||||||
|
<input id="Elks-from-number" v-model="$parent.notification.elksFromNumber" type="text" class="form-control" required>
|
||||||
|
<div class="form-text">
|
||||||
|
{{ $t("Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.") }}
|
||||||
|
<i18n-t tag="p" keypath="More info on:">
|
||||||
|
<a href="https://46elks.se/kb/text-sender-id" target="_blank">https://46elks.se/kb/text-sender-id</a>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="Elks-to-number" class="form-label">{{ $t("To Number") }}</label>
|
||||||
|
<input id="Elks-to-number" v-model="$parent.notification.elksToNumber" type="text" class="form-control" required>
|
||||||
|
<div class="form-text">
|
||||||
|
{{ $t("The phone number of the recipient in E.164 format.") }}
|
||||||
|
<i18n-t tag="p" keypath="More info on:">
|
||||||
|
<a href="https://46elks.se/kb/e164" target="_blank">https://46elks.se/kb/e164</a>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;">
|
||||||
|
<a href="https://46elks.com/docs/send-sms" target="_blank">https://46elks.com/docs/send-sms</a>
|
||||||
|
</i18n-t>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import HiddenInput from "../HiddenInput.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
HiddenInput,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
47
src/components/notifications/SendGrid.vue
Normal file
47
src/components/notifications/SendGrid.vue
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<template>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="sendgrid-api-key" class="form-label">{{ $t("SendGrid API Key") }}</label>
|
||||||
|
<HiddenInput id="push-api-key" v-model="$parent.notification.sendgridApiKey" :required="true" autocomplete="new-password"></HiddenInput>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="sendgrid-from-email" class="form-label">{{ $t("From Email") }}</label>
|
||||||
|
<input id="sendgrid-from-email" v-model="$parent.notification.sendgridFromEmail" type="email" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="sendgrid-to-email" class="form-label">{{ $t("To Email") }}</label>
|
||||||
|
<input id="sendgrid-to-email" v-model="$parent.notification.sendgridToEmail" type="email" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="sendgrid-cc-email" class="form-label">{{ $t("smtpCC") }}</label>
|
||||||
|
<input id="sendgrid-cc-email" v-model="$parent.notification.sendgridCcEmail" type="email" class="form-control">
|
||||||
|
<div class="form-text">{{ $t("Separate multiple email addresses with commas") }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="sendgrid-bcc-email" class="form-label">{{ $t("smtpBCC") }}</label>
|
||||||
|
<input id="sendgrid-bcc-email" v-model="$parent.notification.sendgridBccEmail" type="email" class="form-control">
|
||||||
|
<small class="form-text text-muted">{{ $t("Separate multiple email addresses with commas") }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="sendgrid-subject" class="form-label">{{ $t("Subject:") }}</label>
|
||||||
|
<input id="sendgrid-subject" v-model="$parent.notification.sendgridSubject" type="text" class="form-control">
|
||||||
|
<small class="form-text text-muted">{{ $t("leave blank for default subject") }}</small>
|
||||||
|
</div>
|
||||||
|
<i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;">
|
||||||
|
<a href="https://docs.sendgrid.com/api-reference/mail-send/mail-send" target="_blank">https://docs.sendgrid.com/api-reference/mail-send/mail-send</a>
|
||||||
|
</i18n-t>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import HiddenInput from "../HiddenInput.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
HiddenInput,
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (typeof this.$parent.notification.sendgridSubject === "undefined") {
|
||||||
|
this.$parent.notification.sendgridSubject = "Notification from Your Uptime Kuma";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -9,6 +9,12 @@
|
||||||
<label for="slack-channel" class="form-label">{{ $t("Channel Name") }}</label>
|
<label for="slack-channel" class="form-label">{{ $t("Channel Name") }}</label>
|
||||||
<input id="slack-channel-name" v-model="$parent.notification.slackchannel" type="text" class="form-control">
|
<input id="slack-channel-name" v-model="$parent.notification.slackchannel" type="text" class="form-control">
|
||||||
|
|
||||||
|
<label class="form-label">{{ $t("Message format") }}</label>
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input id="slack-text-message" v-model="$parent.notification.slackrichmessage" type="checkbox" class="form-check-input">
|
||||||
|
<label for="slack-text-message" class="form-label">{{ $t("Send rich messages") }}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
<span style="color: red;"><sup>*</sup></span>{{ $t("Required") }}
|
<span style="color: red;"><sup>*</sup></span>{{ $t("Required") }}
|
||||||
<i18n-t tag="p" keypath="aboutWebhooks" style="margin-top: 8px;">
|
<i18n-t tag="p" keypath="aboutWebhooks" style="margin-top: 8px;">
|
||||||
|
|
|
@ -4,6 +4,53 @@
|
||||||
<HiddenInput id="push-api-key" v-model="$parent.notification.pushAPIKey" :required="true" autocomplete="new-password"></HiddenInput>
|
<HiddenInput id="push-api-key" v-model="$parent.notification.pushAPIKey" :required="true" autocomplete="new-password"></HiddenInput>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="push-api-title" class="form-label">{{ $t("Title") }}</label>
|
||||||
|
<input id="push-api-title" v-model="$parent.notification.pushTitle" type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="push-api-channel" class="form-label">{{ $t("Notification Channel") }}</label>
|
||||||
|
<input id="push-api-channel" v-model="$parent.notification.pushChannel" type="text" class="form-control" patttern="[A-Za-z0-9-]+">
|
||||||
|
<div class="form-text">
|
||||||
|
{{ $t("Alphanumerical string and hyphens only") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="push-api-sound" class="form-label">{{ $t("Sound") }}</label>
|
||||||
|
<select id="push-api-sound" v-model="$parent.notification.pushSound" class="form-select">
|
||||||
|
<option value="default">{{ $t("Default") }}</option>
|
||||||
|
<option value="arcade">{{ $t("Arcade") }}</option>
|
||||||
|
<option value="correct">{{ $t("Correct") }}</option>
|
||||||
|
<option value="fail">{{ $t("Fail") }}</option>
|
||||||
|
<option value="harp">{{ $t("Harp") }}</option>
|
||||||
|
<option value="reveal">{{ $t("Reveal") }}</option>
|
||||||
|
<option value="bubble">{{ $t("Bubble") }}</option>
|
||||||
|
<option value="doorbell">{{ $t("Doorbell") }}</option>
|
||||||
|
<option value="flute">{{ $t("Flute") }}</option>
|
||||||
|
<option value="money">{{ $t("Money") }}</option>
|
||||||
|
<option value="scifi">{{ $t("Scifi") }}</option>
|
||||||
|
<option value="clear">{{ $t("Clear") }}</option>
|
||||||
|
<option value="elevator">{{ $t("Elevator") }}</option>
|
||||||
|
<option value="guitar">{{ $t("Guitar") }}</option>
|
||||||
|
<option value="pop">{{ $t("Pop") }}</option>
|
||||||
|
</select>
|
||||||
|
<div class="form-text">
|
||||||
|
{{ $t("Custom sound to override default notification sound") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input v-model="$parent.notification.pushTimeSensitive" class="form-check-input" type="checkbox">
|
||||||
|
<label class="form-check-label">{{ $t("Time Sensitive (iOS Only)") }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-text">
|
||||||
|
{{ $t("Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;">
|
<i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;">
|
||||||
<a href="https://docs.push.techulus.com" target="_blank">https://docs.push.techulus.com</a>
|
<a href="https://docs.push.techulus.com" target="_blank">https://docs.push.techulus.com</a>
|
||||||
</i18n-t>
|
</i18n-t>
|
||||||
|
@ -16,5 +63,19 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
HiddenInput,
|
HiddenInput,
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
if (typeof this.$parent.notification.pushTitle === "undefined") {
|
||||||
|
this.$parent.notification.pushTitle = "Uptime-Kuma";
|
||||||
|
}
|
||||||
|
if (typeof this.$parent.notification.pushChannel === "undefined") {
|
||||||
|
this.$parent.notification.pushChannel = "uptime-kuma";
|
||||||
|
}
|
||||||
|
if (typeof this.$parent.notification.pushSound === "undefined") {
|
||||||
|
this.$parent.notification.pushSound = "default";
|
||||||
|
}
|
||||||
|
if (typeof this.$parent.notification.pushTimeSensitive === "undefined") {
|
||||||
|
this.$parent.notification.pushTimeSensitive = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -9,6 +9,7 @@ import CallMeBot from "./CallMeBot.vue";
|
||||||
import SMSC from "./SMSC.vue";
|
import SMSC from "./SMSC.vue";
|
||||||
import DingDing from "./DingDing.vue";
|
import DingDing from "./DingDing.vue";
|
||||||
import Discord from "./Discord.vue";
|
import Discord from "./Discord.vue";
|
||||||
|
import Elks from "./46elks.vue";
|
||||||
import Feishu from "./Feishu.vue";
|
import Feishu from "./Feishu.vue";
|
||||||
import FreeMobile from "./FreeMobile.vue";
|
import FreeMobile from "./FreeMobile.vue";
|
||||||
import GoogleChat from "./GoogleChat.vue";
|
import GoogleChat from "./GoogleChat.vue";
|
||||||
|
@ -65,6 +66,7 @@ import Whapi from "./Whapi.vue";
|
||||||
import Cellsynt from "./Cellsynt.vue";
|
import Cellsynt from "./Cellsynt.vue";
|
||||||
import WPush from "./WPush.vue";
|
import WPush from "./WPush.vue";
|
||||||
import SIGNL4 from "./SIGNL4.vue";
|
import SIGNL4 from "./SIGNL4.vue";
|
||||||
|
import SendGrid from "./SendGrid.vue";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manage all notification form.
|
* Manage all notification form.
|
||||||
|
@ -82,6 +84,7 @@ const NotificationFormList = {
|
||||||
"smsc": SMSC,
|
"smsc": SMSC,
|
||||||
"DingDing": DingDing,
|
"DingDing": DingDing,
|
||||||
"discord": Discord,
|
"discord": Discord,
|
||||||
|
"Elks": Elks,
|
||||||
"Feishu": Feishu,
|
"Feishu": Feishu,
|
||||||
"FreeMobile": FreeMobile,
|
"FreeMobile": FreeMobile,
|
||||||
"GoogleChat": GoogleChat,
|
"GoogleChat": GoogleChat,
|
||||||
|
@ -137,7 +140,8 @@ const NotificationFormList = {
|
||||||
"whapi": Whapi,
|
"whapi": Whapi,
|
||||||
"gtxmessaging": GtxMessaging,
|
"gtxmessaging": GtxMessaging,
|
||||||
"Cellsynt": Cellsynt,
|
"Cellsynt": Cellsynt,
|
||||||
"WPush": WPush
|
"WPush": WPush,
|
||||||
|
"SendGrid": SendGrid,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default NotificationFormList;
|
export default NotificationFormList;
|
||||||
|
|
|
@ -32,7 +32,14 @@
|
||||||
<button class="btn btn-outline-info me-2" @click="shrinkDatabase">
|
<button class="btn btn-outline-info me-2" @click="shrinkDatabase">
|
||||||
{{ $t("Shrink Database") }} ({{ databaseSizeDisplay }})
|
{{ $t("Shrink Database") }} ({{ databaseSizeDisplay }})
|
||||||
</button>
|
</button>
|
||||||
<div class="form-text mt-2 mb-4 ms-2">{{ $t("shrinkDatabaseDescription") }}</div>
|
<i18n-t tag="div" keypath="shrinkDatabaseDescriptionSqlite" class="form-text mt-2 mb-4 ms-2">
|
||||||
|
<template #vacuum>
|
||||||
|
<code>VACUUM</code>
|
||||||
|
</template>
|
||||||
|
<template #auto_vacuum>
|
||||||
|
<code>AUTO_VACUUM</code>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
id="clearAllStats-btn"
|
id="clearAllStats-btn"
|
||||||
|
|
1
src/lang/ab.json
Normal file
1
src/lang/ab.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{}
|
|
@ -388,7 +388,6 @@
|
||||||
"Discard": "تجاهل",
|
"Discard": "تجاهل",
|
||||||
"Cancel": "يلغي",
|
"Cancel": "يلغي",
|
||||||
"Powered by": "مشغل بواسطة",
|
"Powered by": "مشغل بواسطة",
|
||||||
"shrinkDatabaseDescription": "تشغيل فراغ قاعدة البيانات لـ SQLite. إذا تم إنشاء قاعدة البيانات الخاصة بك بعد تمكين 1.10.0 AUTO_VACUUM بالفعل ولا يلزم هذا الإجراء.",
|
|
||||||
"serwersms": "Serwersms.pl",
|
"serwersms": "Serwersms.pl",
|
||||||
"serwersmsAPIUser": "اسم مستخدم API (بما في ذلك بادئة WebAPI_)",
|
"serwersmsAPIUser": "اسم مستخدم API (بما في ذلك بادئة WebAPI_)",
|
||||||
"serwersmsAPIPassword": "كلمة مرور API",
|
"serwersmsAPIPassword": "كلمة مرور API",
|
||||||
|
|
|
@ -278,7 +278,6 @@
|
||||||
"Discard": "تجاهل",
|
"Discard": "تجاهل",
|
||||||
"Cancel": "يلغي",
|
"Cancel": "يلغي",
|
||||||
"Powered by": "مشغل بواسطة",
|
"Powered by": "مشغل بواسطة",
|
||||||
"shrinkDatabaseDescription": "تشغيل فراغ قاعدة البيانات لـ SQLite. إذا تم إنشاء قاعدة البيانات الخاصة بك بعد تمكين 1.10.0 AUTO_VACUUM بالفعل ولا يلزم هذا الإجراء.",
|
|
||||||
"Customize": "يعدل أو يكيف",
|
"Customize": "يعدل أو يكيف",
|
||||||
"Custom Footer": "تذييل مخصص",
|
"Custom Footer": "تذييل مخصص",
|
||||||
"Custom CSS": "لغة تنسيق ويب حسب الطلب",
|
"Custom CSS": "لغة تنسيق ويب حسب الطلب",
|
||||||
|
|
|
@ -931,5 +931,7 @@
|
||||||
"gtxMessagingFromHint": "На мабільных тэлефонах атрымальнікі бачаць АІПП як адпраўшчыка паведамлення. Дапускаецца выкарыстаньне да 11 літарна-лічбавых сімвалаў, шорткода, мясцовага доўгага кода або міжнародных нумароў ({e164}, {e212} або {e214})",
|
"gtxMessagingFromHint": "На мабільных тэлефонах атрымальнікі бачаць АІПП як адпраўшчыка паведамлення. Дапускаецца выкарыстаньне да 11 літарна-лічбавых сімвалаў, шорткода, мясцовага доўгага кода або міжнародных нумароў ({e164}, {e212} або {e214})",
|
||||||
"Send to channel": "Адправіць у канал",
|
"Send to channel": "Адправіць у канал",
|
||||||
"threadForumPostID": "Трэд / ID паста",
|
"threadForumPostID": "Трэд / ID паста",
|
||||||
"whatHappensAtForumPost": "Стварыць новы пост на форуме. Гэта НЕ размяшчае паведамленні ў існуючым пасце. Для публікацыі ў існуючай публікацыі выкарыстоўвайце \"{option}\""
|
"whatHappensAtForumPost": "Стварыць новы пост на форуме. Гэта НЕ размяшчае паведамленні ў існуючым пасце. Для публікацыі ў існуючай публікацыі выкарыстоўвайце \"{option}\"",
|
||||||
|
"now": "зараз",
|
||||||
|
"-year": "-год"
|
||||||
}
|
}
|
||||||
|
|
|
@ -312,7 +312,6 @@
|
||||||
"PasswordsDoNotMatch": "Паролите не съвпадат.",
|
"PasswordsDoNotMatch": "Паролите не съвпадат.",
|
||||||
"Current User": "Текущ потребител",
|
"Current User": "Текущ потребител",
|
||||||
"recent": "Скорошни",
|
"recent": "Скорошни",
|
||||||
"shrinkDatabaseDescription": "Инициира \"VACUUM\" за \"SQLite\" база данни. Ако Вашата база данни е създадена след версия 1.10.0, \"AUTO_VACUUM\" функцията е активна и това действие не е нужно.",
|
|
||||||
"Done": "Готово",
|
"Done": "Готово",
|
||||||
"Info": "Информация",
|
"Info": "Информация",
|
||||||
"Security": "Сигурност",
|
"Security": "Сигурност",
|
||||||
|
@ -990,5 +989,104 @@
|
||||||
"threemaRecipientTypeIdentityFormat": "8 знака",
|
"threemaRecipientTypeIdentityFormat": "8 знака",
|
||||||
"threemaRecipientTypePhoneFormat": "E.164, без водещ +",
|
"threemaRecipientTypePhoneFormat": "E.164, без водещ +",
|
||||||
"threemaBasicModeInfo": "Забележка: Тази интеграция използва Threema Gateway в основен режим (сървърно базирано криптиране). Допълнителни подробности можете да намерите {0}.",
|
"threemaBasicModeInfo": "Забележка: Тази интеграция използва Threema Gateway в основен режим (сървърно базирано криптиране). Допълнителни подробности можете да намерите {0}.",
|
||||||
"apiKeysDisabledMsg": "API ключовете са деактивирани, защото удостоверяването е деактивирано."
|
"apiKeysDisabledMsg": "API ключовете са деактивирани, защото удостоверяването е деактивирано.",
|
||||||
|
"jsonQueryDescription": "Анализира и извлича конкретни данни от JSON отговора на сървъра, използвайки JSON заявка или чрез \"$\" за необработения отговор, ако не очаква JSON. След това резултатът се сравнява с очакваната стойност като низове. Вижте {0} за документация и използвайте {1}, за да експериментирате със заявки.",
|
||||||
|
"starts with": "започва с",
|
||||||
|
"less than or equal to": "по-малко или равно на",
|
||||||
|
"now": "сега",
|
||||||
|
"time ago": "преди {0}",
|
||||||
|
"-year": "-година",
|
||||||
|
"Json Query Expression": "Json израз на заявка",
|
||||||
|
"and": "и",
|
||||||
|
"cacheBusterParam": "Добави параметъра {0}",
|
||||||
|
"cacheBusterParamDescription": "Произволно генериран параметър за пропускане на кешове.",
|
||||||
|
"Community String": "Общностен низ",
|
||||||
|
"snmpCommunityStringHelptext": "Този низ функционира като парола за удостоверяване и контрол на достъпа до устройства с активиран SNMP. Сравнете го с конфигурацията на вашето SNMP устройство.",
|
||||||
|
"OID (Object Identifier)": "OID (Идентификатор на обект)",
|
||||||
|
"snmpOIDHelptext": "Въведете OID за сензора или състоянието, които искате да мониторирате. Използвайте инструменти за управление на мрежата като MIB браузъри или SNMP софтуер, ако не сте сигурни за OID.",
|
||||||
|
"SNMP Version": "SNMP Версия",
|
||||||
|
"Please enter a valid OID.": "Моля, въведете валиден OID.",
|
||||||
|
"Host Onesender": "Onesender хост",
|
||||||
|
"Token Onesender": "Onesender токен",
|
||||||
|
"Recipient Type": "Тип получател",
|
||||||
|
"Private Number": "Частен номер",
|
||||||
|
"privateOnesenderDesc": "Уверете се, че телефонният номер е валиден. За да изпратите съобщение на личен телефонен номер, напр.: 628123456789",
|
||||||
|
"groupOnesenderDesc": "Уверете се, че GroupID е валиден. За да изпратите съобщение в група, напр.: 628123456789-342345",
|
||||||
|
"Group ID": "ID на групата",
|
||||||
|
"wayToGetOnesenderUrlandToken": "Можете да получите URL адреса и токена, като посетите уебсайта на Onesender. Повече информация {0}",
|
||||||
|
"Add Remote Browser": "Добави отдалечен браузър",
|
||||||
|
"New Group": "Нова група",
|
||||||
|
"Group Name": "Име на групата",
|
||||||
|
"OAuth2: Client Credentials": "OAuth2: Идентификационни данни на клиента",
|
||||||
|
"Condition": "Условие",
|
||||||
|
"Authentication Method": "Метод за удостоверяване",
|
||||||
|
"Authorization Header": "Хедър за оторизация",
|
||||||
|
"Form Data Body": "Тяло на формата за данни",
|
||||||
|
"OAuth Token URL": "URL адрес на OAuth токена",
|
||||||
|
"Client ID": "ID на клиента",
|
||||||
|
"Client Secret": "Тайна на клиента",
|
||||||
|
"OAuth Scope": "Обхват на OAuth",
|
||||||
|
"Optional: Space separated list of scopes": "По избор: разделен с интервал списък с обхвати",
|
||||||
|
"Go back to home page.": "Обратно към началната страница.",
|
||||||
|
"No tags found.": "Няма намерени етикети.",
|
||||||
|
"Lost connection to the socket server.": "Изгубена връзка със сокет сървъра.",
|
||||||
|
"Cannot connect to the socket server.": "Не може да се свърже със сокет сървъра.",
|
||||||
|
"SIGNL4": "SIGNL4",
|
||||||
|
"SIGNL4 Webhook URL": "SIGNL4 URL адрес на уеб кука",
|
||||||
|
"signl4Docs": "Повече информация относно конфигуриране на SIGNL4 и получаване на URL адрес за уеб кука SIGNL4 в {0}.",
|
||||||
|
"Conditions": "Условия",
|
||||||
|
"conditionAdd": "Добави условие",
|
||||||
|
"conditionDelete": "Изтрий условие",
|
||||||
|
"conditionAddGroup": "Добави група",
|
||||||
|
"conditionDeleteGroup": "Изтрий група",
|
||||||
|
"conditionValuePlaceholder": "Стойност",
|
||||||
|
"contains": "съдържа",
|
||||||
|
"equals": "равно на",
|
||||||
|
"not equals": "не е равно на",
|
||||||
|
"not contains": "не съдържа",
|
||||||
|
"not starts with": "не започва с",
|
||||||
|
"ends with": "завършва с",
|
||||||
|
"not ends with": "не завършва с",
|
||||||
|
"less than": "по-малко от",
|
||||||
|
"greater than": "по-голямо от",
|
||||||
|
"greater than or equal to": "по-голямо или равно на",
|
||||||
|
"record": "запис",
|
||||||
|
"CurlDebugInfo": "За да отстраните грешки в монитора, можете или да поставите това в терминала на Вашата машина, или в терминала на машината, на която работи \"Uptime Kuma\" и да видите заявката.{newiline}Моля, вземете под внимание мрежовите разлики като {firewalls}, {dns_resolvers} или {docker_networks}.",
|
||||||
|
"shrinkDatabaseDescriptionSqlite": "Инициира {vacuum} за база данни тип SQLite. Функцията {auto_vacuum} вече е активирана, но това не дефрагментира базата данни, нито препакетира отделните страници на базата данни по начина, по който го прави командата {vacuum}.",
|
||||||
|
"ignoredTLSError": "TLS/SSL грешките са игнорирани",
|
||||||
|
"Debug": "Отстраняване на грешки",
|
||||||
|
"Copy": "Копирай",
|
||||||
|
"CopyToClipboardError": "Неуспешно копиране в клипборда: {error}",
|
||||||
|
"CopyToClipboardSuccess": "Копирано!",
|
||||||
|
"firewalls": "защитни стени",
|
||||||
|
"dns resolvers": "DNS преобразуватели",
|
||||||
|
"docker networks": "docker мрежи",
|
||||||
|
"CurlDebugInfoOAuth2CCUnsupported": "Пълният oauth клиентски идентификационен поток не се поддържа в {curl}.{newline}Моля, вземете токен на носител и го предайте чрез опцията {oauth2_bearer}.",
|
||||||
|
"CurlDebugInfoProxiesUnsupported": "Поддръжката на прокси в горната команда {curl} в момента не е внедрена.",
|
||||||
|
"Message format": "Формат на съобщението",
|
||||||
|
"Send rich messages": "Изпращай съобщения в \"rich\" формат",
|
||||||
|
"Sound": "Звук",
|
||||||
|
"Notification Channel": "Канал за известяване",
|
||||||
|
"Alphanumerical string and hyphens only": "Само буквено-цифров низ и тирета",
|
||||||
|
"Arcade": "Arcade",
|
||||||
|
"Correct": "Правилно",
|
||||||
|
"Fail": "Грешка",
|
||||||
|
"Harp": "Арфа",
|
||||||
|
"Reveal": "Разкрий",
|
||||||
|
"Bubble": "Балон",
|
||||||
|
"Doorbell": "Звънец на врата",
|
||||||
|
"Flute": "Флейта",
|
||||||
|
"Money": "Пари",
|
||||||
|
"Scifi": "Nаучна фантастика",
|
||||||
|
"Clear": "Премахни",
|
||||||
|
"Elevator": "Асансьор",
|
||||||
|
"Guitar": "Китара",
|
||||||
|
"Pop": "Поп",
|
||||||
|
"Custom sound to override default notification sound": "Персонализиран звук, заменящ звука за известяване по подразбиране",
|
||||||
|
"Time Sensitive (iOS Only)": "Зависещи от часа (само за iOS)",
|
||||||
|
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Известията от типа \"Зависещи от часа\" ще бъдат доставени незабавно, дори ако устройството е в режим „Не безпокойте“.",
|
||||||
|
"From": "От",
|
||||||
|
"Can be found on:": "Можте да се откриете на: {0}",
|
||||||
|
"The phone number of the recipient in E.164 format.": "Телефонният номер на получателя във формат E.164.",
|
||||||
|
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.": "Идентификационен номер на подателя на текста или телефонен номер във формат E.164, в случай, че желаете да получавате отговори."
|
||||||
}
|
}
|
||||||
|
|
|
@ -387,7 +387,6 @@
|
||||||
"Discard": "Zahodit",
|
"Discard": "Zahodit",
|
||||||
"Cancel": "Zrušit",
|
"Cancel": "Zrušit",
|
||||||
"Powered by": "Poskytuje",
|
"Powered by": "Poskytuje",
|
||||||
"shrinkDatabaseDescription": "Pomocí této možnosti provedete příkaz VACUUM nad SQLite databází. Pokud byla databáze vytvořena po vydání verze 1.10.0, AUTO_VACUUM je již povolena a tato akce není vyžadována.",
|
|
||||||
"serwersms": "SerwerSMS.pl",
|
"serwersms": "SerwerSMS.pl",
|
||||||
"serwersmsAPIUser": "API uživatelské jméno (včetně předpony webapi_)",
|
"serwersmsAPIUser": "API uživatelské jméno (včetně předpony webapi_)",
|
||||||
"serwersmsAPIPassword": "API heslo",
|
"serwersmsAPIPassword": "API heslo",
|
||||||
|
@ -989,5 +988,68 @@
|
||||||
"threemaRecipientTypeIdentityFormat": "8 znaků",
|
"threemaRecipientTypeIdentityFormat": "8 znaků",
|
||||||
"threemaRecipientTypeIdentity": "Threema-ID",
|
"threemaRecipientTypeIdentity": "Threema-ID",
|
||||||
"threemaRecipientTypePhone": "Telefonní číslo",
|
"threemaRecipientTypePhone": "Telefonní číslo",
|
||||||
"wayToGetThreemaGateway": "Můžete se zaregistrovat do služby brány Threema {0}."
|
"wayToGetThreemaGateway": "Můžete se zaregistrovat do služby brány Threema {0}.",
|
||||||
|
"privateOnesenderDesc": "Zkontrolujte, zda je telefonní číslo platné. Odeslání zprávy na soukromé telefonní číslo, např.: 628123456789",
|
||||||
|
"wayToGetOnesenderUrlandToken": "Adresu URL a token získáte na webových stránkách společnosti Onesender. Více informací {0}",
|
||||||
|
"now": "nyní",
|
||||||
|
"time ago": "před {0}",
|
||||||
|
"-year": "-rok",
|
||||||
|
"Json Query Expression": "Výraz dotazu JSON",
|
||||||
|
"and": "a",
|
||||||
|
"cacheBusterParam": "Přidání parametru {0}",
|
||||||
|
"cacheBusterParamDescription": "Náhodně generovaný parametr pro vynechání mezipaměti.",
|
||||||
|
"OID (Object Identifier)": "OID (identifikátor objektu)",
|
||||||
|
"Condition": "Stav",
|
||||||
|
"SNMP Version": "Verze SNMP",
|
||||||
|
"Please enter a valid OID.": "Zadejte prosím platný identifikátor OID.",
|
||||||
|
"Recipient Type": "Typ příjemce",
|
||||||
|
"Private Number": "Soukromé číslo",
|
||||||
|
"Group ID": "ID skupiny",
|
||||||
|
"Add Remote Browser": "Přidat vzdálený prohlížeč",
|
||||||
|
"New Group": "Nová skupina",
|
||||||
|
"Group Name": "Název skupiny",
|
||||||
|
"OAuth2: Client Credentials": "OAuth2: přihlašovací údaje klienta",
|
||||||
|
"Authentication Method": "Metoda ověřování",
|
||||||
|
"Authorization Header": "Hlavička autorizace",
|
||||||
|
"Form Data Body": "Tělo formuláře s daty",
|
||||||
|
"threemaRecipientTypePhoneFormat": "E.164, bez počátečního +",
|
||||||
|
"jsonQueryDescription": "Pro zpracování a získání konkrétních dat z JSON odpovědi serveru použijte JSON dotaz - případně \"$\" pro zdrojovou (raw) odpověď, pokud neočekáváte JSON výstup. Výsledek bude následně porovnán jako řetězec vůči očekávaní hodnotě. Dokumentaci naleznete na {0} a pro testování dotazů můžete využít {1}.",
|
||||||
|
"shrinkDatabaseDescriptionSqlite": "Podmínka spuštění příkazu {vacuum} nad SQLite databází. Příkaz {auto_vacuum} je již zapnutý, ale nedochází k defragmentaci databáze ani k přebalení jednotlivých stránek databáze tak, jak to dělá příkaz {vacuum}.",
|
||||||
|
"Community String": "Řetězec komunity",
|
||||||
|
"Host Onesender": "Onesender hostitel",
|
||||||
|
"Token Onesender": "Onesender token",
|
||||||
|
"snmpOIDHelptext": "Zadejte OID senzoru nebo stavu, který chcete monitorovat. Pokud si nejste jisti identifikátorem OID, použijte nástroje pro správu sítě, jako jsou prohlížeče MIB nebo SNMP software.",
|
||||||
|
"snmpCommunityStringHelptext": "Tento řetězec slouží jako heslo pro ověřování a řízení přístupu k zařízením podporujícím protokol SNMP. Shodujte se s konfigurací zařízení SNMP.",
|
||||||
|
"record": "záznam",
|
||||||
|
"Go back to home page.": "Vrátit se domovskou stránku.",
|
||||||
|
"No tags found.": "Nenalezeny žádné štítky.",
|
||||||
|
"Lost connection to the socket server.": "Ztraceno socketové spojení se serverem.",
|
||||||
|
"Cannot connect to the socket server.": "Nelze navázat socketové spojení se serverem.",
|
||||||
|
"SIGNL4": "SIGNL4",
|
||||||
|
"SIGNL4 Webhook URL": "URL adresa webhooku SIGNL4",
|
||||||
|
"signl4Docs": "Další informace související s konfigurací SIGNL4 a postup jak získat URL webhooku SIGNL4 naleznete na {0}.",
|
||||||
|
"Conditions": "Podmínky",
|
||||||
|
"conditionAdd": "Přidat podmínku",
|
||||||
|
"conditionDelete": "Vymazat podmínku",
|
||||||
|
"conditionAddGroup": "Přidat skupinu",
|
||||||
|
"conditionDeleteGroup": "Smazat skupinu",
|
||||||
|
"conditionValuePlaceholder": "Hodnota",
|
||||||
|
"equals": "rovná se",
|
||||||
|
"not equals": "nerovná se",
|
||||||
|
"contains": "obsahuje",
|
||||||
|
"not contains": "neobsahuje",
|
||||||
|
"starts with": "začíná na",
|
||||||
|
"not starts with": "nezačíná na",
|
||||||
|
"ends with": "končí na",
|
||||||
|
"not ends with": "nekončí na",
|
||||||
|
"less than": "menší než",
|
||||||
|
"greater than": "větší než",
|
||||||
|
"less than or equal to": "menší nebo rovno",
|
||||||
|
"greater than or equal to": "větší nebo rovno",
|
||||||
|
"groupOnesenderDesc": "Ujistěte se, že jste zadali platné GroupID. Pro odeslání zprávy do skupiny zadejte například 628123456789-342345",
|
||||||
|
"OAuth Token URL": "URL OAuth tokenu",
|
||||||
|
"Client ID": "ID klienta",
|
||||||
|
"Client Secret": "Tajemství klienta",
|
||||||
|
"OAuth Scope": "OAuth rozsah",
|
||||||
|
"Optional: Space separated list of scopes": "Volitelné: seznam rozsahů oddělte mezerami"
|
||||||
}
|
}
|
||||||
|
|
|
@ -345,7 +345,6 @@
|
||||||
"Discard": "Kassér",
|
"Discard": "Kassér",
|
||||||
"Cancel": "Annullér",
|
"Cancel": "Annullér",
|
||||||
"Powered by": "Drevet af",
|
"Powered by": "Drevet af",
|
||||||
"shrinkDatabaseDescription": "Udfør database VACUUM for SQLite. Hvis din database er oprettet efter 1.10.0, er AUTO_VACUUM allerede aktiveret, og denne handling er ikke nødvendig.",
|
|
||||||
"serwersms": "SerwerSMS.pl",
|
"serwersms": "SerwerSMS.pl",
|
||||||
"serwersmsAPIUser": "API Brugernavn (inkl. webapi_ prefix)",
|
"serwersmsAPIUser": "API Brugernavn (inkl. webapi_ prefix)",
|
||||||
"serwersmsAPIPassword": "API Adgangskode",
|
"serwersmsAPIPassword": "API Adgangskode",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"languageName": "Deutsch (Schweiz)",
|
"languageName": "Deutsch (Schweiz)",
|
||||||
"Settings": "Einstellungen",
|
"Settings": "Einstellungen",
|
||||||
"Dashboard": "Dashboard",
|
"Dashboard": "Überblick",
|
||||||
"New Update": "Update verfügbar",
|
"New Update": "Update verfügbar",
|
||||||
"Language": "Sprache",
|
"Language": "Sprache",
|
||||||
"Appearance": "Erscheinungsbild",
|
"Appearance": "Erscheinungsbild",
|
||||||
|
@ -355,7 +355,6 @@
|
||||||
"Discard": "Verwerfen",
|
"Discard": "Verwerfen",
|
||||||
"Cancel": "Abbrechen",
|
"Cancel": "Abbrechen",
|
||||||
"Powered by": "Erstellt mit",
|
"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",
|
"serwersms": "SerwerSMS.pl",
|
||||||
"serwersmsAPIUser": "API Benutzername (inkl. webapi_ prefix)",
|
"serwersmsAPIUser": "API Benutzername (inkl. webapi_ prefix)",
|
||||||
"serwersmsAPIPassword": "API Passwort",
|
"serwersmsAPIPassword": "API Passwort",
|
||||||
|
@ -987,5 +986,104 @@
|
||||||
"threemaRecipientTypeIdentityFormat": "8 Zeichen",
|
"threemaRecipientTypeIdentityFormat": "8 Zeichen",
|
||||||
"threemaSenderIdentityFormat": "8 Zeichen, beginnt normalerweise mit *",
|
"threemaSenderIdentityFormat": "8 Zeichen, beginnt normalerweise mit *",
|
||||||
"threemaBasicModeInfo": "Hinweis: Diese Integration verwendet Threema Gateway im Basismodus (serverbasierte Verschlüsselung). Weitere Details siehe {0}.",
|
"threemaBasicModeInfo": "Hinweis: Diese Integration verwendet Threema Gateway im Basismodus (serverbasierte Verschlüsselung). Weitere Details siehe {0}.",
|
||||||
"apiKeysDisabledMsg": "API-Schlüssel sind deaktiviert, da die Authentifizierung deaktiviert ist."
|
"apiKeysDisabledMsg": "API-Schlüssel sind deaktiviert, da die Authentifizierung deaktiviert ist.",
|
||||||
|
"Json Query Expression": "Json Query Ausdrck",
|
||||||
|
"Cannot connect to the socket server.": "Es kann keine Verbindung zum Socket-Server hergestellt werden.",
|
||||||
|
"not ends with": "endet nicht mit",
|
||||||
|
"signl4Docs": "Weitere Informationen zur Konfiguration von SIGNL4 und zum Abrufen der SIGNL4-Webhook-URL siehe {0}.",
|
||||||
|
"now": "jetzt",
|
||||||
|
"time ago": "vor {0}",
|
||||||
|
"-year": "-Jahr",
|
||||||
|
"and": "und",
|
||||||
|
"jsonQueryDescription": "Parsen und Extrahieren spezifischer Daten aus der JSON-Antwort des Servers mittels JSON-Abfrage oder Verwendung von \"$\" für die rohe Antwort, wenn kein JSON erwartet wird. Das Ergebnis wird dann mit dem erwarteten Wert in Form von Strings verglichen. Siehe {0} für die Dokumentation und verwende {1}, um mit Abfragen zu experimentieren.",
|
||||||
|
"cacheBusterParamDescription": "Zufällig generierter Parameter um den Cache zu umgehen.",
|
||||||
|
"cacheBusterParam": "Den Parameter {0} hinzufügen",
|
||||||
|
"Community String": "Gemeinschaftliche Zeichenkette",
|
||||||
|
"snmpCommunityStringHelptext": "Diese Zeichenfolge dient als Passwort zur Authentifizierung und Kontrolle des Zugriffs auf SNMP-fähigen Geräten. Pass sie an die Konfiguration des SNMP-Geräts an.",
|
||||||
|
"OID (Object Identifier)": "OID (Objekt-Identifikator)",
|
||||||
|
"Condition": "Bedingung",
|
||||||
|
"SNMP Version": "SNMP Version",
|
||||||
|
"Please enter a valid OID.": "Gib eine gültige OID ein.",
|
||||||
|
"Host Onesender": "Host Onesender",
|
||||||
|
"Token Onesender": "Token Onesender",
|
||||||
|
"Recipient Type": "Empfänger Typ",
|
||||||
|
"Private Number": "Private Nummer",
|
||||||
|
"Group ID": "Gruppen ID",
|
||||||
|
"wayToGetOnesenderUrlandToken": "Du kannst die URL und den Token auf der Onesender-Website erhalten. Weitere Infos {0}",
|
||||||
|
"Add Remote Browser": "Remote-Browser hinzufügen",
|
||||||
|
"New Group": "Neue Gruppe",
|
||||||
|
"Group Name": "Gruppenname",
|
||||||
|
"OAuth2: Client Credentials": "OAuth2: Client-Anmeldeinformationen",
|
||||||
|
"snmpOIDHelptext": "Gib die OID für den zu überwachenden Sensor oder Status ein. Verwende Netzwerkverwaltungstools wie MIB-Browser oder SNMP-Software, wenn du bezüglich OID unsicher bist.",
|
||||||
|
"privateOnesenderDesc": "Stell sicher, dass die Telefonnummer gültig ist. Um Nachrichten an private Telefonnummer zu senden, z. B.: 628123456789",
|
||||||
|
"groupOnesenderDesc": "Stell sicher, dass die GroupID gültig ist. Um Nachricht an die Gruppe zu senden, z.B.: 628123456789-342345",
|
||||||
|
"Authentication Method": "Authentifizierungsmethode",
|
||||||
|
"Authorization Header": "Autorisierungs-Header",
|
||||||
|
"Form Data Body": "Formular Data Body",
|
||||||
|
"OAuth Token URL": "OAuth Token URL",
|
||||||
|
"Client ID": "Client ID",
|
||||||
|
"Client Secret": "Client Secret",
|
||||||
|
"OAuth Scope": "OAuth Scope",
|
||||||
|
"Optional: Space separated list of scopes": "Optional: Durch Leerzeichen getrennte Liste der Scopes",
|
||||||
|
"Go back to home page.": "Zurück zur Startseite.",
|
||||||
|
"No tags found.": "Keine Tags gefunden.",
|
||||||
|
"Lost connection to the socket server.": "Verbindung zum Socket-Server verloren.",
|
||||||
|
"SIGNL4": "SIGNL4",
|
||||||
|
"SIGNL4 Webhook URL": "SIGNL4 Webhook URL",
|
||||||
|
"Conditions": "Bedingungen",
|
||||||
|
"conditionAdd": "Bedingung hinzufügen",
|
||||||
|
"conditionDelete": "Bedingung löschen",
|
||||||
|
"conditionAddGroup": "Gruppe hinzufügen",
|
||||||
|
"conditionDeleteGroup": "Gruppe löschen",
|
||||||
|
"conditionValuePlaceholder": "Wert",
|
||||||
|
"equals": "ist gleich",
|
||||||
|
"not equals": "ist nicht gleich",
|
||||||
|
"contains": "enthält",
|
||||||
|
"not contains": "enthält nicht",
|
||||||
|
"starts with": "beginnt mit",
|
||||||
|
"not starts with": "beginnt nicht mit",
|
||||||
|
"ends with": "endet mit",
|
||||||
|
"less than": "weniger als",
|
||||||
|
"greater than": "mehr als",
|
||||||
|
"less than or equal to": "kleiner als oder gleich",
|
||||||
|
"greater than or equal to": "grösser als oder gleich",
|
||||||
|
"record": "Eintrag",
|
||||||
|
"shrinkDatabaseDescriptionSqlite": "Datenbank {vacuum} für SQLite auslösen. {auto_vacuum} ist bereits aktiviert, aber dies defragmentiert die Datenbank nicht und packt auch nicht einzelne Datenbankseiten neu, wie es der Befehl {vacuum} tut.",
|
||||||
|
"ignoredTLSError": "TLS/SSL-Fehler wurden ignoriert",
|
||||||
|
"Debug": "Debug",
|
||||||
|
"Copy": "Kopieren",
|
||||||
|
"CurlDebugInfoOAuth2CCUnsupported": "Der vollständige OAuth-Client-Credential-Flow wird in {curl} nicht unterstützt.{newline}Bitte besorge dir ein Bearer-Token und übergebe ihn über die {oauth2_bearer}-Option.",
|
||||||
|
"Money": "Geld",
|
||||||
|
"Custom sound to override default notification sound": "Benutzerdefinierter Ton, um den standardmässigen Benachrichtigungston zu ersetzen",
|
||||||
|
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.": "Entweder eine Text-Absender-ID oder eine Telefonnummer im E.164-Format, wenn du Antworten erhalten möchtest.",
|
||||||
|
"CopyToClipboardError": "Konnte nicht in die Zwischenablage kopiert werden: {error}",
|
||||||
|
"CopyToClipboardSuccess": "Kopiert!",
|
||||||
|
"CurlDebugInfo": "Um den Monitor zu debuggen, kannst du dies entweder in das Terminal deines eigenen Rechners oder in das Terminal der Maschine, auf der Uptime Kuma läuft, einfügen und überprüfen, was du anforderst.{newiline}Bitte beachte Netzwerkunterschiede wie {firewalls}, {dns_resolvers} oder {docker_networks}.",
|
||||||
|
"firewalls": "Firewalls",
|
||||||
|
"dns resolvers": "DNS-Resolver",
|
||||||
|
"docker networks": "Docker-Netzwerke",
|
||||||
|
"CurlDebugInfoProxiesUnsupported": "Die Unterstützung von Proxys im oben genannten {curl}-Befehl ist derzeit nicht implementiert.",
|
||||||
|
"Message format": "Nachrichtenformat",
|
||||||
|
"Send rich messages": "Sende Rich-Text-Nachrichten",
|
||||||
|
"Notification Channel": "Benachrichtigungskanal",
|
||||||
|
"Sound": "Benachrichtigungston",
|
||||||
|
"Alphanumerical string and hyphens only": "Nur alphanumerische Zeichen und Bindestriche",
|
||||||
|
"Correct": "Korrekt",
|
||||||
|
"Fail": "Fehlgeschlagen",
|
||||||
|
"Harp": "Harfe",
|
||||||
|
"Reveal": "Enthüllen",
|
||||||
|
"Bubble": "Blase",
|
||||||
|
"Doorbell": "Türklingel",
|
||||||
|
"Flute": "Flöte",
|
||||||
|
"Scifi": "Science Fiction",
|
||||||
|
"Clear": "Klar",
|
||||||
|
"Elevator": "Lift",
|
||||||
|
"Guitar": "Gitarre",
|
||||||
|
"Pop": "Pop",
|
||||||
|
"Time Sensitive (iOS Only)": "Zeitkritisch (nur iOS)",
|
||||||
|
"The phone number of the recipient in E.164 format.": "Die Telefonnummer des Empfängers im E.164-Format.",
|
||||||
|
"Can be found on:": "Ist zu finden auf: {0}",
|
||||||
|
"From": "Von",
|
||||||
|
"Arcade": "Spielhalle",
|
||||||
|
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Zeitkritische Benachrichtigungen werden sofort zugestellt, auch wenn sich das Gerät im Nicht stören-Modus befindet."
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"languageName": "Deutsch",
|
"languageName": "Deutsch",
|
||||||
"Settings": "Einstellungen",
|
"Settings": "Einstellungen",
|
||||||
"Dashboard": "Dashboard",
|
"Dashboard": "Überblick",
|
||||||
"New Update": "Aktualisierung verfügbar",
|
"New Update": "Aktualisierung verfügbar",
|
||||||
"Language": "Sprache",
|
"Language": "Sprache",
|
||||||
"Appearance": "Erscheinungsbild",
|
"Appearance": "Erscheinungsbild",
|
||||||
|
@ -355,7 +355,6 @@
|
||||||
"Discard": "Verwerfen",
|
"Discard": "Verwerfen",
|
||||||
"Cancel": "Abbrechen",
|
"Cancel": "Abbrechen",
|
||||||
"Powered by": "Erstellt mit",
|
"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",
|
"serwersms": "SerwerSMS.pl",
|
||||||
"serwersmsAPIUser": "API Benutzername (inkl. webapi_ prefix)",
|
"serwersmsAPIUser": "API Benutzername (inkl. webapi_ prefix)",
|
||||||
"serwersmsAPIPassword": "API Passwort",
|
"serwersmsAPIPassword": "API Passwort",
|
||||||
|
@ -990,5 +989,104 @@
|
||||||
"threemaRecipientTypeIdentityFormat": "8 Zeichen",
|
"threemaRecipientTypeIdentityFormat": "8 Zeichen",
|
||||||
"threemaSenderIdentityFormat": "8 Zeichen, beginnt normalerweise mit *",
|
"threemaSenderIdentityFormat": "8 Zeichen, beginnt normalerweise mit *",
|
||||||
"threemaBasicModeInfo": "Hinweis: Diese Integration verwendet Threema Gateway im Basismodus (serverbasierte Verschlüsselung). Weitere Details siehe {0}.",
|
"threemaBasicModeInfo": "Hinweis: Diese Integration verwendet Threema Gateway im Basismodus (serverbasierte Verschlüsselung). Weitere Details siehe {0}.",
|
||||||
"apiKeysDisabledMsg": "API-Schlüssel sind deaktiviert, da die Authentifizierung deaktiviert ist."
|
"apiKeysDisabledMsg": "API-Schlüssel sind deaktiviert, da die Authentifizierung deaktiviert ist.",
|
||||||
|
"wayToGetOnesenderUrlandToken": "Du kannst die URL und den Token auf der Onesender-Website erhalten. Weitere Infos {0}",
|
||||||
|
"Lost connection to the socket server.": "Verbindung zum Socket-Server verloren.",
|
||||||
|
"conditionDeleteGroup": "Gruppe löschen",
|
||||||
|
"greater than": "mehr als",
|
||||||
|
"snmpOIDHelptext": "Gib die OID für den zu überwachenden Sensor oder Status ein. Verwende Netzwerkverwaltungstools wie MIB-Browser oder SNMP-Software, wenn du bezüglich OID unsicher bist.",
|
||||||
|
"signl4Docs": "Weitere Informationen zur Konfiguration von SIGNL4 und zum Abrufen der SIGNL4-Webhook-URL siehe {0}.",
|
||||||
|
"now": "jetzt",
|
||||||
|
"time ago": "vor {0}",
|
||||||
|
"Json Query Expression": "Json Query Ausdrck",
|
||||||
|
"-year": "-Jahr",
|
||||||
|
"and": "und",
|
||||||
|
"jsonQueryDescription": "Parsen und Extrahieren spezifischer Daten aus der JSON-Antwort des Servers mittels JSON-Abfrage oder Verwendung von \"$\" für die rohe Antwort, wenn kein JSON erwartet wird. Das Ergebnis wird dann mit dem erwarteten Wert in Form von Strings verglichen. Siehe {0} für die Dokumentation und verwende {1}, um mit Abfragen zu experimentieren.",
|
||||||
|
"cacheBusterParamDescription": "Zufällig generierter Parameter um den Cache zu umgehen.",
|
||||||
|
"cacheBusterParam": "Den Parameter {0} hinzufügen",
|
||||||
|
"Community String": "Gemeinschaftliche Zeichenkette",
|
||||||
|
"snmpCommunityStringHelptext": "Diese Zeichenfolge dient als Passwort zur Authentifizierung und Kontrolle des Zugriffs auf SNMP-fähigen Geräten. Pass sie an die Konfiguration des SNMP-Geräts an.",
|
||||||
|
"OID (Object Identifier)": "OID (Objekt-Identifikator)",
|
||||||
|
"Condition": "Bedingung",
|
||||||
|
"SNMP Version": "SNMP Version",
|
||||||
|
"Please enter a valid OID.": "Gib eine gültige OID ein.",
|
||||||
|
"Host Onesender": "Host Onesender",
|
||||||
|
"Token Onesender": "Token Onesender",
|
||||||
|
"Recipient Type": "Empfänger Typ",
|
||||||
|
"Private Number": "Private Nummer",
|
||||||
|
"Group ID": "Gruppen ID",
|
||||||
|
"privateOnesenderDesc": "Stell sicher, dass die Telefonnummer gültig ist. Um Nachrichten an private Telefonnummer zu senden, z. B.: 628123456789",
|
||||||
|
"groupOnesenderDesc": "Stell sicher, dass die GroupID gültig ist. Um Nachricht an die Gruppe zu senden, z.B.: 628123456789-342345",
|
||||||
|
"Add Remote Browser": "Remote-Browser hinzufügen",
|
||||||
|
"New Group": "Neue Gruppe",
|
||||||
|
"Group Name": "Gruppenname",
|
||||||
|
"OAuth2: Client Credentials": "OAuth2: Client-Anmeldeinformationen",
|
||||||
|
"Authentication Method": "Authentifizierungsmethode",
|
||||||
|
"Authorization Header": "Autorisierungs-Header",
|
||||||
|
"Form Data Body": "Formular Data Body",
|
||||||
|
"OAuth Token URL": "OAuth Token URL",
|
||||||
|
"Client ID": "Client ID",
|
||||||
|
"Client Secret": "Client Secret",
|
||||||
|
"OAuth Scope": "OAuth Scope",
|
||||||
|
"Optional: Space separated list of scopes": "Optional: Durch Leerzeichen getrennte Liste der Scopes",
|
||||||
|
"Go back to home page.": "Zurück zur Startseite.",
|
||||||
|
"No tags found.": "Keine Tags gefunden.",
|
||||||
|
"Cannot connect to the socket server.": "Es kann keine Verbindung zum Socket-Server hergestellt werden.",
|
||||||
|
"SIGNL4": "SIGNL4",
|
||||||
|
"SIGNL4 Webhook URL": "SIGNL4 Webhook URL",
|
||||||
|
"Conditions": "Bedingungen",
|
||||||
|
"conditionAdd": "Bedingung hinzufügen",
|
||||||
|
"conditionDelete": "Bedingung löschen",
|
||||||
|
"conditionAddGroup": "Gruppe hinzufügen",
|
||||||
|
"conditionValuePlaceholder": "Wert",
|
||||||
|
"equals": "ist gleich",
|
||||||
|
"not equals": "ist nicht gleich",
|
||||||
|
"contains": "enthält",
|
||||||
|
"not contains": "enthält nicht",
|
||||||
|
"starts with": "beginnt mit",
|
||||||
|
"not starts with": "beginnt nicht mit",
|
||||||
|
"ends with": "endet mit",
|
||||||
|
"not ends with": "endet nicht mit",
|
||||||
|
"less than": "weniger als",
|
||||||
|
"less than or equal to": "kleiner als oder gleich",
|
||||||
|
"greater than or equal to": "größer als oder gleich",
|
||||||
|
"record": "Eintrag",
|
||||||
|
"shrinkDatabaseDescriptionSqlite": "Datenbank {vacuum} für SQLite auslösen. {auto_vacuum} ist bereits aktiviert, aber dies defragmentiert die Datenbank nicht und packt auch nicht einzelne Datenbankseiten neu, wie es der Befehl {vacuum} tut.",
|
||||||
|
"ignoredTLSError": "TLS/SSL-Fehler wurden ignoriert",
|
||||||
|
"Message format": "Nachrichtenformat",
|
||||||
|
"Notification Channel": "Benachrichtigungskanal",
|
||||||
|
"Custom sound to override default notification sound": "Benutzerdefinierter Ton, um den standardmäßigen Benachrichtigungston zu ersetzen",
|
||||||
|
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Zeitkritische Benachrichtigungen werden sofort zugestellt, auch wenn sich das Gerät im Nicht stören-Modus befindet.",
|
||||||
|
"Debug": "Debug",
|
||||||
|
"Copy": "Kopieren",
|
||||||
|
"CopyToClipboardError": "Konnte nicht in die Zwischenablage kopiert werden: {error}",
|
||||||
|
"CopyToClipboardSuccess": "Kopiert!",
|
||||||
|
"CurlDebugInfo": "Um den Monitor zu debuggen, kannst du dies entweder in das Terminal deines eigenen Rechners oder in das Terminal der Maschine, auf der Uptime Kuma läuft, einfügen und überprüfen, was du anforderst.{newiline}Bitte beachte Netzwerkunterschiede wie {firewalls}, {dns_resolvers} oder {docker_networks}.",
|
||||||
|
"firewalls": "Firewalls",
|
||||||
|
"dns resolvers": "DNS-Resolver",
|
||||||
|
"docker networks": "Docker-Netzwerke",
|
||||||
|
"CurlDebugInfoOAuth2CCUnsupported": "Der vollständige OAuth-Client-Credential-Flow wird in {curl} nicht unterstützt.{newline}Bitte besorge dir ein Bearer-Token und übergebe ihn über die {oauth2_bearer}-Option.",
|
||||||
|
"CurlDebugInfoProxiesUnsupported": "Die Unterstützung von Proxys im oben genannten {curl}-Befehl ist derzeit nicht implementiert.",
|
||||||
|
"Alphanumerical string and hyphens only": "Nur alphanumerische Zeichen und Bindestriche",
|
||||||
|
"Correct": "Korrekt",
|
||||||
|
"Harp": "Harfe",
|
||||||
|
"Doorbell": "Türklingel",
|
||||||
|
"Flute": "Flöte",
|
||||||
|
"Money": "Geld",
|
||||||
|
"Scifi": "Science Fiction",
|
||||||
|
"Elevator": "Aufzug",
|
||||||
|
"Guitar": "Gitarre",
|
||||||
|
"Sound": "Benachrichtigungston",
|
||||||
|
"Time Sensitive (iOS Only)": "Zeitkritisch (nur iOS)",
|
||||||
|
"From": "Von",
|
||||||
|
"Can be found on:": "Ist zu finden auf: {0}",
|
||||||
|
"The phone number of the recipient in E.164 format.": "Die Telefonnummer des Empfängers im E.164-Format.",
|
||||||
|
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.": "Entweder eine Text-Absender-ID oder eine Telefonnummer im E.164-Format, wenn du Antworten erhalten möchtest.",
|
||||||
|
"Send rich messages": "Sende Rich-Text-Nachrichten",
|
||||||
|
"Fail": "Fehlgeschlagen",
|
||||||
|
"Reveal": "Enthüllen",
|
||||||
|
"Bubble": "Blase",
|
||||||
|
"Clear": "Klar",
|
||||||
|
"Pop": "Pop",
|
||||||
|
"Arcade": "Spielhalle"
|
||||||
}
|
}
|
||||||
|
|
|
@ -363,7 +363,6 @@
|
||||||
"Discard": "Απορρίψει",
|
"Discard": "Απορρίψει",
|
||||||
"Cancel": "Ακυρο",
|
"Cancel": "Ακυρο",
|
||||||
"Powered by": "Με την υποστήριξη του",
|
"Powered by": "Με την υποστήριξη του",
|
||||||
"shrinkDatabaseDescription": "Ενεργοποίηση βάσης δεδομένων VACUUM για SQLite. Εάν η βάση δεδομένων σας έχει δημιουργηθεί μετά την έκδοση 1.10.0, το AUTO_VACUUM είναι ήδη ενεργοποιημένο και αυτή η ενέργεια δεν χρειάζεται.",
|
|
||||||
"serwersms": "SerwerSMS.pl",
|
"serwersms": "SerwerSMS.pl",
|
||||||
"serwersmsAPIUser": "API Username (incl. webapi_ prefix)",
|
"serwersmsAPIUser": "API Username (incl. webapi_ prefix)",
|
||||||
"serwersmsAPIPassword": "API κωδικός πρόσβασης",
|
"serwersmsAPIPassword": "API κωδικός πρόσβασης",
|
||||||
|
|
|
@ -82,6 +82,7 @@
|
||||||
"resendEveryXTimes": "Resend every {0} times",
|
"resendEveryXTimes": "Resend every {0} times",
|
||||||
"resendDisabled": "Resend disabled",
|
"resendDisabled": "Resend disabled",
|
||||||
"retriesDescription": "Maximum retries before the service is marked as down and a notification is sent",
|
"retriesDescription": "Maximum retries before the service is marked as down and a notification is sent",
|
||||||
|
"ignoredTLSError": "TLS/SSL errors have been ignored",
|
||||||
"ignoreTLSError": "Ignore TLS/SSL errors for HTTPS websites",
|
"ignoreTLSError": "Ignore TLS/SSL errors for HTTPS websites",
|
||||||
"ignoreTLSErrorGeneral": "Ignore TLS/SSL error for connection",
|
"ignoreTLSErrorGeneral": "Ignore TLS/SSL error for connection",
|
||||||
"upsideDownModeDescription": "Flip the status upside down. If the service is reachable, it is DOWN.",
|
"upsideDownModeDescription": "Flip the status upside down. If the service is reachable, it is DOWN.",
|
||||||
|
@ -96,6 +97,8 @@
|
||||||
"pushOthers": "Others",
|
"pushOthers": "Others",
|
||||||
"programmingLanguages": "Programming Languages",
|
"programmingLanguages": "Programming Languages",
|
||||||
"Save": "Save",
|
"Save": "Save",
|
||||||
|
"Debug": "Debug",
|
||||||
|
"Copy": "Copy",
|
||||||
"Notifications": "Notifications",
|
"Notifications": "Notifications",
|
||||||
"Not available, please setup.": "Not available, please set up.",
|
"Not available, please setup.": "Not available, please set up.",
|
||||||
"Setup Notification": "Set Up Notification",
|
"Setup Notification": "Set Up Notification",
|
||||||
|
@ -248,6 +251,14 @@
|
||||||
"PushUrl": "Push URL",
|
"PushUrl": "Push URL",
|
||||||
"HeadersInvalidFormat": "The request headers are not valid JSON: ",
|
"HeadersInvalidFormat": "The request headers are not valid JSON: ",
|
||||||
"BodyInvalidFormat": "The request body is not valid JSON: ",
|
"BodyInvalidFormat": "The request body is not valid JSON: ",
|
||||||
|
"CopyToClipboardError": "Couldn't copy to clipboard: {error}",
|
||||||
|
"CopyToClipboardSuccess": "Copied!",
|
||||||
|
"CurlDebugInfo": "To debug the monitor, you can either paste this into your own machines terminal or into the machines terminal which uptime kuma is running on and see what you are requesting.{newiline}Please be aware of networking differences like {firewalls}, {dns_resolvers} or {docker_networks}.",
|
||||||
|
"firewalls": "firewalls",
|
||||||
|
"dns resolvers": "dns resolvers",
|
||||||
|
"docker networks": "docker networks",
|
||||||
|
"CurlDebugInfoOAuth2CCUnsupported": "Full oauth client credential flow is not supported in {curl}.{newline}Please get a bearer token and pass it via the {oauth2_bearer} option.",
|
||||||
|
"CurlDebugInfoProxiesUnsupported": "Proxy support in the above {curl} command is currently not implemented.",
|
||||||
"Monitor History": "Monitor History",
|
"Monitor History": "Monitor History",
|
||||||
"clearDataOlderThan": "Keep monitor history data for {0} days.",
|
"clearDataOlderThan": "Keep monitor history data for {0} days.",
|
||||||
"PasswordsDoNotMatch": "Passwords do not match.",
|
"PasswordsDoNotMatch": "Passwords do not match.",
|
||||||
|
@ -266,6 +277,7 @@
|
||||||
"Security": "Security",
|
"Security": "Security",
|
||||||
"Steam API Key": "Steam API Key",
|
"Steam API Key": "Steam API Key",
|
||||||
"Shrink Database": "Shrink Database",
|
"Shrink Database": "Shrink Database",
|
||||||
|
"shrinkDatabaseDescriptionSqlite": "Trigger database {vacuum} for SQLite. {auto_vacuum} is already enabled but this does not defragment the database nor repack individual database pages the way that the {vacuum} command does.",
|
||||||
"Pick a RR-Type...": "Pick a RR-Type…",
|
"Pick a RR-Type...": "Pick a RR-Type…",
|
||||||
"Pick Accepted Status Codes...": "Pick Accepted Status Codes…",
|
"Pick Accepted Status Codes...": "Pick Accepted Status Codes…",
|
||||||
"Default": "Default",
|
"Default": "Default",
|
||||||
|
@ -302,7 +314,6 @@
|
||||||
"selectedMonitorCount": "Selected: {0}",
|
"selectedMonitorCount": "Selected: {0}",
|
||||||
"Check/Uncheck": "Check/Uncheck",
|
"Check/Uncheck": "Check/Uncheck",
|
||||||
"Powered by": "Powered by",
|
"Powered by": "Powered by",
|
||||||
"shrinkDatabaseDescription": "Trigger database VACUUM for SQLite. If your database is created after 1.10.0, AUTO_VACUUM is already enabled and this action is not needed.",
|
|
||||||
"Customize": "Customize",
|
"Customize": "Customize",
|
||||||
"Custom Footer": "Custom Footer",
|
"Custom Footer": "Custom Footer",
|
||||||
"Custom CSS": "Custom CSS",
|
"Custom CSS": "Custom CSS",
|
||||||
|
@ -884,6 +895,8 @@
|
||||||
"cacheBusterParamDescription": "Randomly generated parameter to skip caches.",
|
"cacheBusterParamDescription": "Randomly generated parameter to skip caches.",
|
||||||
"gamedigGuessPort": "Gamedig: Guess Port",
|
"gamedigGuessPort": "Gamedig: Guess Port",
|
||||||
"gamedigGuessPortDescription": "The port used by Valve Server Query Protocol may be different from the client port. Try this if the monitor cannot connect to your server.",
|
"gamedigGuessPortDescription": "The port used by Valve Server Query Protocol may be different from the client port. Try this if the monitor cannot connect to your server.",
|
||||||
|
"Message format": "Message format",
|
||||||
|
"Send rich messages": "Send rich messages",
|
||||||
"Bitrix24 Webhook URL": "Bitrix24 Webhook URL",
|
"Bitrix24 Webhook URL": "Bitrix24 Webhook URL",
|
||||||
"wayToGetBitrix24Webhook": "You can create a webhook by following the steps at {0}",
|
"wayToGetBitrix24Webhook": "You can create a webhook by following the steps at {0}",
|
||||||
"bitrix24SupportUserID": "Enter your user ID in Bitrix24. You can find out the ID from the link by going to the user's profile.",
|
"bitrix24SupportUserID": "Enter your user ID in Bitrix24. You can find out the ID from the link by going to the user's profile.",
|
||||||
|
@ -976,7 +989,7 @@
|
||||||
"privateOnesenderDesc": "Make sure the number phone is valid. To send message into private number phone, ex: 628123456789",
|
"privateOnesenderDesc": "Make sure the number phone is valid. To send message into private number phone, ex: 628123456789",
|
||||||
"groupOnesenderDesc": "Make sure the GroupID is valid. To send message into Group, ex: 628123456789-342345",
|
"groupOnesenderDesc": "Make sure the GroupID is valid. To send message into Group, ex: 628123456789-342345",
|
||||||
"Group ID": "Group ID",
|
"Group ID": "Group ID",
|
||||||
"wayToGetOnesenderUrlandToken":"You can get the URL and Token by going to the Onesender website. More info {0}",
|
"wayToGetOnesenderUrlandToken": "You can get the URL and Token by going to the Onesender website. More info {0}",
|
||||||
"Add Remote Browser": "Add Remote Browser",
|
"Add Remote Browser": "Add Remote Browser",
|
||||||
"New Group": "New Group",
|
"New Group": "New Group",
|
||||||
"Group Name": "Group Name",
|
"Group Name": "Group Name",
|
||||||
|
@ -1014,5 +1027,38 @@
|
||||||
"greater than": "greater than",
|
"greater than": "greater than",
|
||||||
"less than or equal to": "less than or equal to",
|
"less than or equal to": "less than or equal to",
|
||||||
"greater than or equal to": "greater than or equal to",
|
"greater than or equal to": "greater than or equal to",
|
||||||
"record": "record"
|
"record": "record",
|
||||||
|
"Notification Channel": "Notification Channel",
|
||||||
|
"Sound": "Sound",
|
||||||
|
"Alphanumerical string and hyphens only": "Alphanumerical string and hyphens only",
|
||||||
|
"Arcade": "Arcade",
|
||||||
|
"Correct": "Correct",
|
||||||
|
"Fail": "Fail",
|
||||||
|
"Harp": "Harp",
|
||||||
|
"Reveal": "Reveal",
|
||||||
|
"Bubble": "Bubble",
|
||||||
|
"Doorbell": "Doorbell",
|
||||||
|
"Flute": "Flute",
|
||||||
|
"Money": "Money",
|
||||||
|
"Scifi": "Scifi",
|
||||||
|
"Clear": "Clear",
|
||||||
|
"Elevator": "Elevator",
|
||||||
|
"Guitar": "Guitar",
|
||||||
|
"Pop": "Pop",
|
||||||
|
"Custom sound to override default notification sound": "Custom sound to override default notification sound",
|
||||||
|
"Time Sensitive (iOS Only)": "Time Sensitive (iOS Only)",
|
||||||
|
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.",
|
||||||
|
"From": "From",
|
||||||
|
"Can be found on:": "Can be found on: {0}",
|
||||||
|
"The phone number of the recipient in E.164 format.": "The phone number of the recipient in E.164 format.",
|
||||||
|
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.": "Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.",
|
||||||
|
"RabbitMQ Nodes": "RabbitMQ Management Nodes",
|
||||||
|
"rabbitmqNodesDescription": "Enter the URL for the RabbitMQ management nodes including protocol and port. Example: {0}",
|
||||||
|
"rabbitmqNodesRequired": "Please set the nodes for this monitor.",
|
||||||
|
"rabbitmqNodesInvalid": "Please use a fully qualified (starting with 'http') URL for RabbitMQ nodes.",
|
||||||
|
"RabbitMQ Username": "RabbitMQ Username",
|
||||||
|
"RabbitMQ Password": "RabbitMQ Password",
|
||||||
|
"rabbitmqHelpText": "To use the monitor, you will need to enable the Management Plugin in your RabbitMQ setup. For more information, please consult the {rabitmq_documentation}.",
|
||||||
|
"SendGrid API Key": "SendGrid API Key",
|
||||||
|
"Separate multiple email addresses with commas": "Separate multiple email addresses with commas"
|
||||||
}
|
}
|
||||||
|
|
1
src/lang/enm.json
Normal file
1
src/lang/enm.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{}
|
|
@ -396,7 +396,6 @@
|
||||||
"successMessage": "Mensaje de éxito",
|
"successMessage": "Mensaje de éxito",
|
||||||
"Pick Accepted Status Codes...": "Seleccione Códigos de Estado Aceptados…",
|
"Pick Accepted Status Codes...": "Seleccione Códigos de Estado Aceptados…",
|
||||||
"Post": "Post",
|
"Post": "Post",
|
||||||
"shrinkDatabaseDescription": "Activar VACUUM para SQLite. Si tu base de datos fue creada después 1.10.0, AUTO_VACUUM ya está habilitada y esta acción no es necesaria.",
|
|
||||||
"deleteStatusPageMsg": "¿Estas seguro que quieres eliminar esta página de estado?",
|
"deleteStatusPageMsg": "¿Estas seguro que quieres eliminar esta página de estado?",
|
||||||
"default": "Predeterminado",
|
"default": "Predeterminado",
|
||||||
"enabled": "Habilitado",
|
"enabled": "Habilitado",
|
||||||
|
|
|
@ -242,7 +242,6 @@
|
||||||
"Running": "Töötab",
|
"Running": "Töötab",
|
||||||
"resendEveryXTimes": "Saada uuesti {0} korda",
|
"resendEveryXTimes": "Saada uuesti {0} korda",
|
||||||
"statusMaintenance": "Hooldus",
|
"statusMaintenance": "Hooldus",
|
||||||
"Webhook URL": "",
|
|
||||||
"Server URL": "Serveri URL",
|
"Server URL": "Serveri URL",
|
||||||
"Priority": "Tähtsus",
|
"Priority": "Tähtsus",
|
||||||
"emojiCheatSheet": "Emotikoni spikker: {0}",
|
"emojiCheatSheet": "Emotikoni spikker: {0}",
|
||||||
|
@ -254,7 +253,6 @@
|
||||||
"PushUrl": "Lükka URL",
|
"PushUrl": "Lükka URL",
|
||||||
"Monitor History": "Monitori ajalugu",
|
"Monitor History": "Monitori ajalugu",
|
||||||
"PasswordsDoNotMatch": "Paroolid ei ühti.",
|
"PasswordsDoNotMatch": "Paroolid ei ühti.",
|
||||||
"records": "",
|
|
||||||
"Current User": "Praegune kasutaja",
|
"Current User": "Praegune kasutaja",
|
||||||
"topic": "Teema",
|
"topic": "Teema",
|
||||||
"successMessage": "Edukas sõnum",
|
"successMessage": "Edukas sõnum",
|
||||||
|
@ -308,7 +306,6 @@
|
||||||
"General Monitor Type": "Üldine monitori tüüp",
|
"General Monitor Type": "Üldine monitori tüüp",
|
||||||
"webhookAdditionalHeadersDesc": "Lisab täiendavad päised saadetud webhookiga.",
|
"webhookAdditionalHeadersDesc": "Lisab täiendavad päised saadetud webhookiga.",
|
||||||
"Read more": "Loe rohkem",
|
"Read more": "Loe rohkem",
|
||||||
"HeadersInvalidFormat": "",
|
|
||||||
"clearDataOlderThan": "Hoia monitori ajalugu alles {0} päeva.",
|
"clearDataOlderThan": "Hoia monitori ajalugu alles {0} päeva.",
|
||||||
"steamApiKeyDescription": "Steam Game Serveri monitoorimiseks on vaja sul Steam Web-API võtit. Sa saad registreerida enda API võtme siin: ",
|
"steamApiKeyDescription": "Steam Game Serveri monitoorimiseks on vaja sul Steam Web-API võtit. Sa saad registreerida enda API võtme siin: ",
|
||||||
"Done": "Tehtud",
|
"Done": "Tehtud",
|
||||||
|
|
|
@ -106,7 +106,7 @@
|
||||||
"disableauth.message2": "Egoera jakin batzuetarako diseinatuta dago, Uptime Kumaren {intendThirdPartyAuth} (Cloudflare Access, Authelia edo beste autentifikazio-mekanismo batzuk).",
|
"disableauth.message2": "Egoera jakin batzuetarako diseinatuta dago, Uptime Kumaren {intendThirdPartyAuth} (Cloudflare Access, Authelia edo beste autentifikazio-mekanismo batzuk).",
|
||||||
"where you intend to implement third-party authentication": "aurrean hirugarrengo autentifikazio batzuek jartzeko",
|
"where you intend to implement third-party authentication": "aurrean hirugarrengo autentifikazio batzuek jartzeko",
|
||||||
"Please use this option carefully!": "Mesedez, kontuz erabili aukera hau!",
|
"Please use this option carefully!": "Mesedez, kontuz erabili aukera hau!",
|
||||||
"Logout": "Saioa amaitu",
|
"Logout": "Itxi saioa",
|
||||||
"Leave": "Utzi",
|
"Leave": "Utzi",
|
||||||
"I understand, please disable": "Ulertzen dut, mesedez desgaitu",
|
"I understand, please disable": "Ulertzen dut, mesedez desgaitu",
|
||||||
"Confirm": "Baieztatu",
|
"Confirm": "Baieztatu",
|
||||||
|
@ -115,7 +115,7 @@
|
||||||
"Username": "Erabiltzailea",
|
"Username": "Erabiltzailea",
|
||||||
"Password": "Pasahitza",
|
"Password": "Pasahitza",
|
||||||
"Remember me": "Gogora nazazu",
|
"Remember me": "Gogora nazazu",
|
||||||
"Login": "Saioa hasi",
|
"Login": "Hasi saioa",
|
||||||
"No Monitors, please": "Monitorizaziorik ez, mesedez",
|
"No Monitors, please": "Monitorizaziorik ez, mesedez",
|
||||||
"add one": "gehitu bat",
|
"add one": "gehitu bat",
|
||||||
"Notification Type": "Jakinarazpen mota",
|
"Notification Type": "Jakinarazpen mota",
|
||||||
|
@ -164,11 +164,11 @@
|
||||||
"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 name already exist.": "Izen hau duen etiketa dagoeneko badago.",
|
||||||
"Tag with this value already exist.": "Balio hau duen etiketa dagoeneko badago.",
|
"Tag with this value already exist.": "Balio hau duen etiketa dagoeneko badago.",
|
||||||
"color": "kolorea",
|
"color": "Kolorea",
|
||||||
"value (optional)": "balioa (hautazkoa)",
|
"value (optional)": "balioa (hautazkoa)",
|
||||||
"Gray": "Grisa",
|
"Gray": "Grisa",
|
||||||
"Red": "Gorria",
|
"Red": "Gorria",
|
||||||
"Orange": "Naranja",
|
"Orange": "Laranja",
|
||||||
"Green": "Berdea",
|
"Green": "Berdea",
|
||||||
"Blue": "Urdina",
|
"Blue": "Urdina",
|
||||||
"Indigo": "Indigo",
|
"Indigo": "Indigo",
|
||||||
|
@ -190,7 +190,7 @@
|
||||||
"Status Page": "Egoera orria",
|
"Status Page": "Egoera orria",
|
||||||
"Status Pages": "Egoera orriak",
|
"Status Pages": "Egoera orriak",
|
||||||
"defaultNotificationName": "Nire {notification} Alerta ({number})",
|
"defaultNotificationName": "Nire {notification} Alerta ({number})",
|
||||||
"here": "Hemen",
|
"here": "hemen",
|
||||||
"Required": "Beharrezkoa",
|
"Required": "Beharrezkoa",
|
||||||
"telegram": "Telegram",
|
"telegram": "Telegram",
|
||||||
"ZohoCliq": "ZohoCliq",
|
"ZohoCliq": "ZohoCliq",
|
||||||
|
@ -360,7 +360,6 @@
|
||||||
"Discard": "Baztertu",
|
"Discard": "Baztertu",
|
||||||
"Cancel": "Ezeztatu",
|
"Cancel": "Ezeztatu",
|
||||||
"Powered by": "Honekin egina:",
|
"Powered by": "Honekin egina:",
|
||||||
"shrinkDatabaseDescription": "Trigger database VACUUM for SQLite. If your database is created after 1.10.0, AUTO_VACUUM is already enabled and this action is not needed.",
|
|
||||||
"serwersms": "SerwerSMS.pl",
|
"serwersms": "SerwerSMS.pl",
|
||||||
"serwersmsAPIUser": "API erabiltzailea (webapi_ aurre-hizkia barne)",
|
"serwersmsAPIUser": "API erabiltzailea (webapi_ aurre-hizkia barne)",
|
||||||
"serwersmsAPIPassword": "API pasahitza",
|
"serwersmsAPIPassword": "API pasahitza",
|
||||||
|
@ -583,6 +582,10 @@
|
||||||
"Mechanism": "Mekanismoa",
|
"Mechanism": "Mekanismoa",
|
||||||
"Home": "Hasiera",
|
"Home": "Hasiera",
|
||||||
"filterActive": "Aktibo",
|
"filterActive": "Aktibo",
|
||||||
"filterActivePaused": "Geldituta",
|
"filterActivePaused": "Pausatua",
|
||||||
"Expected Value": "Esperotako balioa"
|
"Expected Value": "Esperotako balioa",
|
||||||
|
"statusPageRefreshIn": "{0} barru freskatuko da.",
|
||||||
|
"now": "orain",
|
||||||
|
"time ago": "duela {0}",
|
||||||
|
"-year": "-urte"
|
||||||
}
|
}
|
||||||
|
|
|
@ -335,7 +335,6 @@
|
||||||
"About": "درباره آپتایم کوما",
|
"About": "درباره آپتایم کوما",
|
||||||
"wayToGetCloudflaredURL": "(دریافت Cloudflared از {0})",
|
"wayToGetCloudflaredURL": "(دریافت Cloudflared از {0})",
|
||||||
"cloudflareWebsite": "وب سایت کلادفلر",
|
"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:": "پیام:",
|
"Message:": "پیام:",
|
||||||
"HTTP Headers": "هدر های HTTP",
|
"HTTP Headers": "هدر های HTTP",
|
||||||
"Bot Token": "توکن بات",
|
"Bot Token": "توکن بات",
|
||||||
|
@ -960,5 +959,66 @@
|
||||||
"From Phone Number / Transmission Path Originating Address (TPOA)": "از شماره تلفن / آدرس مبدأ مسیر انتقال (TPOA)",
|
"From Phone Number / Transmission Path Originating Address (TPOA)": "از شماره تلفن / آدرس مبدأ مسیر انتقال (TPOA)",
|
||||||
"Alphanumeric (recommended)": "الفبا و اعداد (پیشنهاد شده)",
|
"Alphanumeric (recommended)": "الفبا و اعداد (پیشنهاد شده)",
|
||||||
"Telephone number": "شماره تلفن",
|
"Telephone number": "شماره تلفن",
|
||||||
"cellsyntOriginatortypeAlphanumeric": "الفبا و اعداد (حداکثر 11 کاراکتر). گیرندگان نمی توانند به پیام پاسخ دهند."
|
"cellsyntOriginatortypeAlphanumeric": "الفبا و اعداد (حداکثر 11 کاراکتر). گیرندگان نمی توانند به پیام پاسخ دهند.",
|
||||||
|
"cacheBusterParam": "افزودن پارامتر {0}",
|
||||||
|
"Private Number": "شماره شخصی",
|
||||||
|
"jsonQueryDescription": "با استفاده از JSON query پاسخ JSON سرور را تحلیل و استخراج کنید یا از «$» برای پاسخ خام استفاده کنید، در صورتی که JSON نباشد. سپس نتیجه با مقدار مورد نظر به عنوان رشته مقایسه میشود. برای مستندات به {0} بروید و از {1} برای آزمایش کوئریها استفاده کنید.",
|
||||||
|
"now": "الان",
|
||||||
|
"-year": "-سال",
|
||||||
|
"time ago": "{0} پیش",
|
||||||
|
"and": "و",
|
||||||
|
"Condition": "شرط",
|
||||||
|
"SNMP Version": "نسخه SNMP",
|
||||||
|
"privateOnesenderDesc": "مطمئن شوید که شماره تلفن درست است. برای ارسال پیام به شماره شخصی، مثل: 09123456789",
|
||||||
|
"Group ID": "شناسه گروه",
|
||||||
|
"Add Remote Browser": "افزودن مرورگر ریموت",
|
||||||
|
"New Group": "گروه جدید",
|
||||||
|
"Group Name": "نام گروه",
|
||||||
|
"Authentication Method": "روش احراز هویت",
|
||||||
|
"Client ID": "شناسه کلاینت",
|
||||||
|
"Client Secret": "رمز کلاینت",
|
||||||
|
"Go back to home page.": "بازگشت به صفحه اصلی.",
|
||||||
|
"No tags found.": "برچسبی یافت نشد.",
|
||||||
|
"Lost connection to the socket server.": "اتصال به سوکت سرور از دست رفت.",
|
||||||
|
"Cannot connect to the socket server.": "نمیتوان به سوکت سرور متصل شد.",
|
||||||
|
"SIGNL4": "SIGNL4",
|
||||||
|
"SIGNL4 Webhook URL": "پیوند وبهوک SIGNL4",
|
||||||
|
"Conditions": "شرایط",
|
||||||
|
"conditionAdd": "افزودن شرط",
|
||||||
|
"conditionDelete": "حذف شرط",
|
||||||
|
"conditionAddGroup": "افزودن گروه",
|
||||||
|
"conditionDeleteGroup": "حذف گروه",
|
||||||
|
"conditionValuePlaceholder": "مقدار",
|
||||||
|
"equals": "برابر",
|
||||||
|
"not equals": "نابرابر",
|
||||||
|
"contains": "شامل",
|
||||||
|
"starts with": "شروع با",
|
||||||
|
"not starts with": "عدم شروع با",
|
||||||
|
"not contains": "شامل نباشد",
|
||||||
|
"ends with": "پایان با",
|
||||||
|
"not ends with": "عدم پایان با",
|
||||||
|
"less than": "کمتر از",
|
||||||
|
"greater than": "بیشتر از",
|
||||||
|
"less than or equal to": "کمتر یا مساوی با",
|
||||||
|
"greater than or equal to": "بیشتر یا مساوی با",
|
||||||
|
"record": "رکورد",
|
||||||
|
"snmpCommunityStringHelptext": "این رشته به عنوان پسورد برای احراز هویت و کنترل دسترسی به دستگاههای آماده SNMP استفاده میشود. این پسورد را با توجه به تنظیمات دستگاه SNMP خود مطابقت دهید.",
|
||||||
|
"snmpOIDHelptext": "OID برای سنسور یا وضعیتی که میخواهید مانیتور کنید را وارد کنید. اگر درباره مقدار OID اطمینان ندارید از ابزارهای مدیریت شبکهای مانند MIB یا SNMP استفاده کنید.",
|
||||||
|
"Json Query Expression": "عبارت کوئری JSON",
|
||||||
|
"cacheBusterParamDescription": "پارامتر تولید شده رندوم برای رد کردن کش.",
|
||||||
|
"Community String": "رشته انجمن",
|
||||||
|
"OID (Object Identifier)": "OID (شناسه شئ)",
|
||||||
|
"Please enter a valid OID.": "لطفا یک OID معتبر وارد کنید.",
|
||||||
|
"Host Onesender": "هاست Onesender",
|
||||||
|
"Token Onesender": "توکن Onesender",
|
||||||
|
"Recipient Type": "نوع دریافت کننده",
|
||||||
|
"groupOnesenderDesc": "از معتبر بودن GroupID اطمینان حاصل کنید. برای ارسال پیام در گروه، برای مثال: 628123456789-342345",
|
||||||
|
"wayToGetOnesenderUrlandToken": "برای دریافت URL و توکن میتوانید به وبسایت Onesender مراجعه گنید. اطلاعات بیشتر {0}",
|
||||||
|
"OAuth2: Client Credentials": "OAuth2: گواهی مشتری",
|
||||||
|
"Authorization Header": "هدر Authorization",
|
||||||
|
"Form Data Body": "از اطلاعات بدنه",
|
||||||
|
"OAuth Token URL": "URL توکن OAuth",
|
||||||
|
"OAuth Scope": "اسکوپ OAuth",
|
||||||
|
"Optional: Space separated list of scopes": "اختیاری: لیست جدا شده با فاصله از اسکوپها",
|
||||||
|
"signl4Docs": "شما میتوانید اطلاعات بیشتر در رابطه با نحوه تنظیم SIGNL4 و به دست آوردن URL وبهوک SIGNL4 را در {0} پیدا کنید."
|
||||||
}
|
}
|
||||||
|
|
102
src/lang/fi.json
102
src/lang/fi.json
|
@ -640,7 +640,6 @@
|
||||||
"light": "vaalea",
|
"light": "vaalea",
|
||||||
"Switch to Light Theme": "Vaihda vaaleaan teemaan",
|
"Switch to Light Theme": "Vaihda vaaleaan teemaan",
|
||||||
"Powered by": "Voimanlähteenä",
|
"Powered by": "Voimanlähteenä",
|
||||||
"shrinkDatabaseDescription": "Käynnistä tietokannan VACUUM SQLitelle. Jos tietokanta on luotu 1.10.0:n jälkeen, AUTO_VACUUM on jo käytössä eikä tätä toimintoa tarvita.",
|
|
||||||
"Accept characters:": "Hyväksy merkit:",
|
"Accept characters:": "Hyväksy merkit:",
|
||||||
"New Status Page": "Uusi tilasivu",
|
"New Status Page": "Uusi tilasivu",
|
||||||
"wayToGetCloudflaredURL": "(Lataa cloudflared osoitteesta {0})",
|
"wayToGetCloudflaredURL": "(Lataa cloudflared osoitteesta {0})",
|
||||||
|
@ -988,5 +987,104 @@
|
||||||
"threemaRecipientTypeIdentity": "Threema-tunnus (Threema-ID)",
|
"threemaRecipientTypeIdentity": "Threema-tunnus (Threema-ID)",
|
||||||
"threemaSenderIdentityFormat": "8 merkkiä, alkaa useimmiten merkillä *",
|
"threemaSenderIdentityFormat": "8 merkkiä, alkaa useimmiten merkillä *",
|
||||||
"threemaBasicModeInfo": "Huomio: Tämä integraatio käyttää Threema Gateway:tä perustilassa (palvelinpohjainen salaus). Lisätietoja löytyy {0}.",
|
"threemaBasicModeInfo": "Huomio: Tämä integraatio käyttää Threema Gateway:tä perustilassa (palvelinpohjainen salaus). Lisätietoja löytyy {0}.",
|
||||||
"apiKeysDisabledMsg": "API-avaimet eivät ole käytössä koska tunnistautuminen ei ole käytössä."
|
"apiKeysDisabledMsg": "API-avaimet eivät ole käytössä koska tunnistautuminen ei ole käytössä.",
|
||||||
|
"snmpCommunityStringHelptext": "Tämä merkkijono toimii salasanana ja pääsyoikeutena SNMP-laitteisiin. Aseta se samaksi kuin SNMP-laitteen asetuksissa.",
|
||||||
|
"privateOnesenderDesc": "Varmista, että puhelinnumero on kelvollinen. Lähettääksesi viestin yksityiseen numeroon esim. 628123456789",
|
||||||
|
"Authorization Header": "Tunnistautumisen otsikko (Authorization Header)",
|
||||||
|
"Optional: Space separated list of scopes": "Valinnainen: Luettelo näkyvyysalueista (scope) välilyönnillä erotettuna",
|
||||||
|
"jsonQueryDescription": "Valitse sisältö palvelimen JSON-vastauksesta käyttämällä JSON-kyselyä tai käytä alkuperäistä sisältöä merkillä \"$\", jos palvelimen vastaus ei ole JSON-muodossa. Tämän jälkeen sisältöä verrataan odotettuun arvoon (merkkijonona). Katso kyselykielen ohjeita osoitteesta {0}. Leikkikenttä löytyy osoitteesta {1}.",
|
||||||
|
"now": "nyt",
|
||||||
|
"time ago": "{0} sitten",
|
||||||
|
"-year": "-vuosi",
|
||||||
|
"Json Query Expression": "Json-kyselylauseke",
|
||||||
|
"and": "ja",
|
||||||
|
"cacheBusterParam": "Lisää parametri {0}",
|
||||||
|
"cacheBusterParamDescription": "Satunnaisesti luotu parametri välimuistien ohittamiseksi.",
|
||||||
|
"Community String": "Yhteisömerkkijono",
|
||||||
|
"OID (Object Identifier)": "OID (Objektin tunniste)",
|
||||||
|
"snmpOIDHelptext": "Syötä OID anturille tai tilalle jota haluat valvoa. Käytä verkonvalvontatyökaluja kuten MIB-selaimia tai SNMP-ohjelmistoja jos olet epävarma OID:n valinnasta.",
|
||||||
|
"Condition": "Ehto",
|
||||||
|
"SNMP Version": "SNMP-versio",
|
||||||
|
"Please enter a valid OID.": "Ole hyvä ja syötä kelvollinen OID.",
|
||||||
|
"Host Onesender": "OneSender-isäntä",
|
||||||
|
"Token Onesender": "OneSender-tokeni",
|
||||||
|
"Recipient Type": "Vastaanottajan tyyppi",
|
||||||
|
"Private Number": "Yksityinen numero",
|
||||||
|
"Group ID": "Ryhmän tunnus (Group ID)",
|
||||||
|
"groupOnesenderDesc": "Varmista, että ryhmän tunnus (Group ID) on kelvollinen. Lähettääksesi viestin ryhmälle, esim. 628123456789-342345",
|
||||||
|
"wayToGetOnesenderUrlandToken": "Saat URL-osoitteen ja tokenin vierailemalla OneSenderin verkkosivulla. Lisätietoja osoitteesta {0}",
|
||||||
|
"Add Remote Browser": "Lisää etäselain",
|
||||||
|
"New Group": "Uusi ryhmä",
|
||||||
|
"Group Name": "Ryhmän nimi",
|
||||||
|
"OAuth2: Client Credentials": "OAuth2: Asiakkaan tunnukset",
|
||||||
|
"Authentication Method": "Tunnistustapa",
|
||||||
|
"Form Data Body": "Lomakkeen tietosisältö (Form data body)",
|
||||||
|
"OAuth Token URL": "OAuth tokenin URL",
|
||||||
|
"Client ID": "Asiakkaan ID",
|
||||||
|
"Client Secret": "Asiakkaan salaisuus",
|
||||||
|
"OAuth Scope": "OAuth-näkyvyysalue (scope)",
|
||||||
|
"Go back to home page.": "Palaa etusivulle.",
|
||||||
|
"No tags found.": "Tunnisteita ei löytynyt.",
|
||||||
|
"Lost connection to the socket server.": "Menetettiin yhteys Socket-palvelimeen.",
|
||||||
|
"Cannot connect to the socket server.": "Socket-palvelimeen ei voi yhdistää.",
|
||||||
|
"SIGNL4": "SIGNL4",
|
||||||
|
"SIGNL4 Webhook URL": "SIGNL4 Webhookin URL",
|
||||||
|
"signl4Docs": "Löydät lisätietoja SIGNL4-asetuksista ja webhook URLin hankinnasta osoitteesta {0}.",
|
||||||
|
"Conditions": "Ehdot",
|
||||||
|
"conditionAdd": "Lisää ehto",
|
||||||
|
"conditionDelete": "Poista ehto",
|
||||||
|
"conditionAddGroup": "Lisää ryhmä",
|
||||||
|
"conditionDeleteGroup": "Poista ryhmä",
|
||||||
|
"conditionValuePlaceholder": "Arvo",
|
||||||
|
"contains": "sisältää",
|
||||||
|
"not contains": "ei sisällä",
|
||||||
|
"not equals": "ei yhtä suuri kuin",
|
||||||
|
"equals": "yhtä suuri kuin",
|
||||||
|
"starts with": "alkaa",
|
||||||
|
"not starts with": "ei ala",
|
||||||
|
"ends with": "päättyy",
|
||||||
|
"not ends with": "ei pääty",
|
||||||
|
"less than": "vähemmän kuin",
|
||||||
|
"greater than": "enemmän kuin",
|
||||||
|
"less than or equal to": "vähemmän tai yhtä paljon kuin",
|
||||||
|
"greater than or equal to": "enemmän tai yhtä paljon kuin",
|
||||||
|
"record": "tietue",
|
||||||
|
"Notification Channel": "Ilmoituskanava",
|
||||||
|
"Custom sound to override default notification sound": "Mukautettu ääni oletusäänen sijaan",
|
||||||
|
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Aikaherkät ilmoitukset toimitetaan välittömästi vaikka laite olisi Älä Häiritse-tilassa.",
|
||||||
|
"ignoredTLSError": "TLS/SSL-virheitä ei huomioida",
|
||||||
|
"Debug": "Debug",
|
||||||
|
"Copy": "Kopioi",
|
||||||
|
"CopyToClipboardError": "Leikepöydälle kopiointi ei onnistunut: {error}",
|
||||||
|
"CopyToClipboardSuccess": "Kopioitu!",
|
||||||
|
"CurlDebugInfo": "Debugataksesi seurainta, voit joko kopioida tämän oman tietokoneesi komentoriville tai sen tietokoneen komentoriville jossa uptime kuma on käynissä nähdäksesi pyyntösi.{newiline}Otathan huomioon verkkoympäristöjen erot kuten {firewalls}, {dns_resolvers} tai {docker_networks}.",
|
||||||
|
"firewalls": "palomuurit",
|
||||||
|
"dns resolvers": "DNS-palvelimet",
|
||||||
|
"docker networks": "Docker-verkot",
|
||||||
|
"CurlDebugInfoOAuth2CCUnsupported": "{curl} ei tue täydellistä oauth-asiakkaan kirjautumistietojen välittämistä.{newline}Hanki bearer token -tunnus ja välitä se {oauth2_bearer}-vaihtoehdon kautta.",
|
||||||
|
"CurlDebugInfoProxiesUnsupported": "Välityspalvelimen tukea yllä olevassa {curl}-komennossa ei ole tällä hetkellä toteutettu.",
|
||||||
|
"shrinkDatabaseDescriptionSqlite": "Käynnistä tietokannan {vacuum} SQLitelle. {auto_vacuum} on jo käytössä, mutta se ei eheytä tietokantaa tai uudelleenpakkaa yksittäisiä tietokantasivuja samoin kuin {vacuum}.",
|
||||||
|
"Message format": "Viestin muoto",
|
||||||
|
"Send rich messages": "Lähetä monipuolisia viestejä (rich messages)",
|
||||||
|
"Sound": "Ääni",
|
||||||
|
"Alphanumerical string and hyphens only": "Vain aakkosnumeerinen merkkijono ja yhdysmerkit",
|
||||||
|
"Arcade": "Pelihalli",
|
||||||
|
"Harp": "Harppu",
|
||||||
|
"Fail": "Virhe",
|
||||||
|
"Correct": "Onnistuminen",
|
||||||
|
"Reveal": "Ilmestys",
|
||||||
|
"Bubble": "Kupla",
|
||||||
|
"Doorbell": "Ovikello",
|
||||||
|
"Flute": "Huilu",
|
||||||
|
"Money": "Raha",
|
||||||
|
"Clear": "Kirkas",
|
||||||
|
"Elevator": "Hissi",
|
||||||
|
"Guitar": "Kitara",
|
||||||
|
"Time Sensitive (iOS Only)": "Aikaherkkä (vain iOS)",
|
||||||
|
"From": "Lähettäjä",
|
||||||
|
"Can be found on:": "Löydettävissä: {0}",
|
||||||
|
"The phone number of the recipient in E.164 format.": "Vastaanottajan puhelinnumero E.164-muodossa.",
|
||||||
|
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.": "Joko lähettäjätunnus (sender ID) tai puhelinnumero E-164-muodossa jos haluat pystyä vastaanottamaan vastausviestejä.",
|
||||||
|
"Scifi": "Tieteisseikkailu",
|
||||||
|
"Pop": "Poksahdus"
|
||||||
}
|
}
|
||||||
|
|
|
@ -384,7 +384,6 @@
|
||||||
"Discard": "Abandonner",
|
"Discard": "Abandonner",
|
||||||
"Cancel": "Annuler",
|
"Cancel": "Annuler",
|
||||||
"Powered by": "Propulsé par",
|
"Powered by": "Propulsé par",
|
||||||
"shrinkDatabaseDescription": "Déclenche la commande VACUUM pour SQLite. Si votre base de données a été créée après la version 1.10.0, AUTO_VACUUM est déjà activé et cette action n'est pas nécessaire.",
|
|
||||||
"serwersms": "SerwerSMS.pl",
|
"serwersms": "SerwerSMS.pl",
|
||||||
"serwersmsAPIUser": "Nom d'utilisateur de l'API (incl. webapi_ prefix)",
|
"serwersmsAPIUser": "Nom d'utilisateur de l'API (incl. webapi_ prefix)",
|
||||||
"serwersmsAPIPassword": "Mot de passe API",
|
"serwersmsAPIPassword": "Mot de passe API",
|
||||||
|
@ -990,5 +989,104 @@
|
||||||
"threemaApiAuthenticationSecret": "Secret de l'identifiant de la passerelle",
|
"threemaApiAuthenticationSecret": "Secret de l'identifiant de la passerelle",
|
||||||
"wayToGetThreemaGateway": "Vous pouvez vous inscrire à Threema Gateway {0}.",
|
"wayToGetThreemaGateway": "Vous pouvez vous inscrire à Threema Gateway {0}.",
|
||||||
"threemaBasicModeInfo": "Note : Cette intégration utilise Threema Gateway en mode basique (chiffrement basé sur le serveur). Vous pouvez trouver plus de détails {0}.",
|
"threemaBasicModeInfo": "Note : Cette intégration utilise Threema Gateway en mode basique (chiffrement basé sur le serveur). Vous pouvez trouver plus de détails {0}.",
|
||||||
"apiKeysDisabledMsg": "Les clés API sont désactivées car l'authentification est désactivée."
|
"apiKeysDisabledMsg": "Les clés API sont désactivées car l'authentification est désactivée.",
|
||||||
|
"cacheBusterParam": "Ajoutez le paramètre {0}",
|
||||||
|
"snmpCommunityStringHelptext": "Cette chaîne fonctionne comme un mot de passe pour authentifier et contrôler l'accès aux appareils compatibles SNMP. Faites-le correspondre à la configuration de votre périphérique SNMP.",
|
||||||
|
"privateOnesenderDesc": "Assurez-vous que le numéro de téléphone est valide. Pour envoyer un message vers un numéro de téléphone privé, ex : 628123456789",
|
||||||
|
"groupOnesenderDesc": "Assurez-vous que le GroupID est valide. Pour envoyer un message dans le groupe, ex : 628123456789-342345",
|
||||||
|
"Optional: Space separated list of scopes": "Facultatif : liste des étendues séparées par des espaces",
|
||||||
|
"jsonQueryDescription": "Analyser et extraire des données spécifiques de la réponse JSON du serveur en utilisant une requête JSON ou utiliser « $ » pour la réponse brute, si un JSON n'est pas attendu. Le résultat est ensuite comparé à la valeur attendue, en tant que chaînes de caractères. Consultez {0} pour la documentation et utilisez {1} pour expérimenter avec des requêtes.",
|
||||||
|
"now": "Maintenant",
|
||||||
|
"time ago": "il y a {0}",
|
||||||
|
"-year": "-année",
|
||||||
|
"Json Query Expression": "Expression de requête JSON",
|
||||||
|
"and": "et",
|
||||||
|
"cacheBusterParamDescription": "Paramètre généré aléatoirement pour ignorer les caches.",
|
||||||
|
"Community String": "Chaîne de communauté",
|
||||||
|
"OID (Object Identifier)": "OID (identifiant d'objet)",
|
||||||
|
"snmpOIDHelptext": "Entrez l'OID du capteur ou de l'état que vous souhaitez surveiller. Utilisez des outils de gestion de réseau tels que les navigateurs MIB ou le logiciel SNMP si vous n'êtes pas sûr de l'OID.",
|
||||||
|
"Condition": "Condition",
|
||||||
|
"SNMP Version": "Version SNMP",
|
||||||
|
"Please enter a valid OID.": "Veuillez saisir un OID valide.",
|
||||||
|
"Host Onesender": "Héberger un expéditeur",
|
||||||
|
"Token Onesender": "Jeton Onesender",
|
||||||
|
"Recipient Type": "Type de destinataire",
|
||||||
|
"Private Number": "Numéro privé",
|
||||||
|
"Group ID": "Identifiant du groupe",
|
||||||
|
"wayToGetOnesenderUrlandToken": "Vous pouvez obtenir l'URL et le jeton en accédant au site Web Onesender. Plus d'informations {0}",
|
||||||
|
"Add Remote Browser": "Ajouter un navigateur distant",
|
||||||
|
"New Group": "Nouveau groupe",
|
||||||
|
"Group Name": "Nom du groupe",
|
||||||
|
"OAuth2: Client Credentials": "OAuth2 : informations d'identification du client",
|
||||||
|
"Authentication Method": "Méthode d'authentification",
|
||||||
|
"Authorization Header": "En-tête d'autorisation",
|
||||||
|
"Form Data Body": "Corps des données du formulaire",
|
||||||
|
"OAuth Token URL": "URL du jeton OAuth",
|
||||||
|
"Client ID": "Identifiant client",
|
||||||
|
"Client Secret": "Secret client",
|
||||||
|
"OAuth Scope": "Portée OAuth",
|
||||||
|
"Go back to home page.": "Retournez à la page d'accueil.",
|
||||||
|
"No tags found.": "Aucune étiquettes trouvée.",
|
||||||
|
"Lost connection to the socket server.": "Connexion au serveur de socket perdue.",
|
||||||
|
"Cannot connect to the socket server.": "Impossible de se connecter au serveur de socket.",
|
||||||
|
"SIGNL4": "SIGNL4",
|
||||||
|
"SIGNL4 Webhook URL": "URL du webhook SIGNL4",
|
||||||
|
"signl4Docs": "Vous pouvez trouver plus d'informations sur la façon de configurer SIGNL4 et sur la façon d'obtenir l'URL du webhook SIGNL4 dans le {0}.",
|
||||||
|
"Conditions": "Conditions",
|
||||||
|
"conditionAdd": "Ajouter une condition",
|
||||||
|
"conditionDelete": "Supprimer la condition",
|
||||||
|
"conditionAddGroup": "Ajouter un groupe",
|
||||||
|
"conditionDeleteGroup": "Supprimer le groupe",
|
||||||
|
"conditionValuePlaceholder": "Valeur",
|
||||||
|
"equals": "est égal",
|
||||||
|
"not equals": "pas égal",
|
||||||
|
"contains": "contient",
|
||||||
|
"not contains": "ne contient pas",
|
||||||
|
"starts with": "commence par",
|
||||||
|
"not starts with": "ne commence pas par",
|
||||||
|
"ends with": "se termine par",
|
||||||
|
"not ends with": "ne se termine pas par",
|
||||||
|
"less than": "moins que",
|
||||||
|
"greater than": "supérieur à",
|
||||||
|
"less than or equal to": "inférieur ou égal à",
|
||||||
|
"greater than or equal to": "supérieur ou égal à",
|
||||||
|
"record": "enregistrer",
|
||||||
|
"shrinkDatabaseDescriptionSqlite": "Déclencher la commande {vacuum} pour la base de données SQLite. {auto_vacuum} est déjà activé, mais cela ne défragmente pas la base de données ni ne réorganise les pages individuelles de la base de données de la même manière que la commande {vacuum}.",
|
||||||
|
"ignoredTLSError": "Les erreurs TLS/SSL ont été ignorées",
|
||||||
|
"CopyToClipboardError": "Impossible de copier dans le presse-papiers : {error}",
|
||||||
|
"CurlDebugInfo": "Pour déboguer la sonde, vous pouvez soit le coller dans le terminal de votre propre machine, soit dans le terminal de la machine sur lequel kuma de disponibilité s'exécute et voir ce qui vous est demandé. {newiline} Veuillez être conscient des différences de réseau telles que {firewalls}, {dns_resolvers}. ou {docker_networks}.",
|
||||||
|
"Notification Channel": "Canal de notification",
|
||||||
|
"Alphanumerical string and hyphens only": "Chaîne alphanumérique et tirets uniquement",
|
||||||
|
"Custom sound to override default notification sound": "Son personnalisé pour remplacer le son de notification par défaut",
|
||||||
|
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Les notifications urgentes seront envoyées immédiatement, même si l'appareil est en mode Ne pas déranger.",
|
||||||
|
"Debug": "Déboguer",
|
||||||
|
"Copy": "Copie",
|
||||||
|
"CopyToClipboardSuccess": "Copié !",
|
||||||
|
"firewalls": "pare-feu",
|
||||||
|
"dns resolvers": "résolveurs DNS",
|
||||||
|
"docker networks": "réseaux dockers",
|
||||||
|
"CurlDebugInfoOAuth2CCUnsupported": "Le flux complet des informations d'identification du client OAuth n'est pas pris en charge dans {curl}. {newline}Veuillez obtenir un jeton d'accès bearer token et le transmettre via l'option {oauth2_bearer}.",
|
||||||
|
"CurlDebugInfoProxiesUnsupported": "La prise en charge du proxy dans la commande {curl} ci-dessus n'est actuellement pas implémentée.",
|
||||||
|
"Message format": "Format des messages",
|
||||||
|
"Send rich messages": "Envoyer des messages enrichis",
|
||||||
|
"Sound": "Son",
|
||||||
|
"Arcade": "Arcade",
|
||||||
|
"Correct": "Correcte",
|
||||||
|
"Fail": "Échouer",
|
||||||
|
"Harp": "Harpe",
|
||||||
|
"Reveal": "Révéler",
|
||||||
|
"Bubble": "Bulle",
|
||||||
|
"Doorbell": "Sonnette",
|
||||||
|
"Flute": "Flûte",
|
||||||
|
"Money": "Argent",
|
||||||
|
"Scifi": "Science-fiction",
|
||||||
|
"Clear": "Effacer",
|
||||||
|
"Elevator": "Ascenseur",
|
||||||
|
"Guitar": "Guitare",
|
||||||
|
"Pop": "Pop",
|
||||||
|
"Time Sensitive (iOS Only)": "Sensible au temps (iOS uniquement)",
|
||||||
|
"From": "De",
|
||||||
|
"Can be found on:": "Disponible sur : {0}",
|
||||||
|
"The phone number of the recipient in E.164 format.": "Le numéro de téléphone du destinataire au format E.164.",
|
||||||
|
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.": "Soit un identifiant d'expéditeur de texte, soit un numéro de téléphone au format E.164 si vous souhaitez pouvoir recevoir des réponses."
|
||||||
}
|
}
|
||||||
|
|
|
@ -397,7 +397,6 @@
|
||||||
"steamApiKeyDescription": "Chun monatóireacht a dhéanamh ar Fhreastalaí Cluiche Gaile is gá duit eochair Steam Web-API. Is féidir leat d’eochair API a chlárú anseo: ",
|
"steamApiKeyDescription": "Chun monatóireacht a dhéanamh ar Fhreastalaí Cluiche Gaile is gá duit eochair Steam Web-API. Is féidir leat d’eochair API a chlárú anseo: ",
|
||||||
"Shrink Database": "Laghdaigh Bunachar Sonraí",
|
"Shrink Database": "Laghdaigh Bunachar Sonraí",
|
||||||
"No monitors available.": "Níl monatóirí ar fáil.",
|
"No monitors available.": "Níl monatóirí ar fáil.",
|
||||||
"shrinkDatabaseDescription": "Truicear bunachar sonraí Folamh do SQLite. Má chruthaítear do bhunachar sonraí tar éis 1.10.0, tá AUTO_VACUUM cumasaithe cheana féin agus níl an gníomh seo de dhíth.",
|
|
||||||
"deleteStatusPageMsg": "An bhfuil tú cinnte gur mhaith leat an leathanach stádais seo a scriosadh?",
|
"deleteStatusPageMsg": "An bhfuil tú cinnte gur mhaith leat an leathanach stádais seo a scriosadh?",
|
||||||
"deleteProxyMsg": "An bhfuil tú cinnte gur mhaith leat an seachfhreastalaí seo a scriosadh do gach monatóir?",
|
"deleteProxyMsg": "An bhfuil tú cinnte gur mhaith leat an seachfhreastalaí seo a scriosadh do gach monatóir?",
|
||||||
"enableProxyDescription": "Ní bheidh éifeacht ag an seachfhreastalaí seo ar iarratais ar mhonatóir go dtí go ngníomhófar é. Is féidir leat an seachfhreastalaí a dhíchumasú go sealadach ó gach monatóir de réir stádas gníomhachtaithe.",
|
"enableProxyDescription": "Ní bheidh éifeacht ag an seachfhreastalaí seo ar iarratais ar mhonatóir go dtí go ngníomhófar é. Is féidir leat an seachfhreastalaí a dhíchumasú go sealadach ó gach monatóir de réir stádas gníomhachtaithe.",
|
||||||
|
@ -955,5 +954,67 @@
|
||||||
"smspartnerPhoneNumberHelptext": "Caithfidh an uimhir a bheith san fhormáid idirnáisiúnta {0}, {1}. Ní mór uimhreacha iolracha a dheighilt le {2}",
|
"smspartnerPhoneNumberHelptext": "Caithfidh an uimhir a bheith san fhormáid idirnáisiúnta {0}, {1}. Ní mór uimhreacha iolracha a dheighilt le {2}",
|
||||||
"wayToGetThreemaGateway": "Is féidir leat clárú le haghaidh Threema Gateway {0}.",
|
"wayToGetThreemaGateway": "Is féidir leat clárú le haghaidh Threema Gateway {0}.",
|
||||||
"threemaSenderIdentityFormat": "8 gcarachtar, a thosaíonn le * de ghnáth",
|
"threemaSenderIdentityFormat": "8 gcarachtar, a thosaíonn le * de ghnáth",
|
||||||
"threemaBasicModeInfo": "Nóta: Úsáideann an comhtháthú seo Threema Gateway i mód bunúsach (criptiúchán freastalaí-bhunaithe). Is féidir tuilleadh sonraí a fháil {0}."
|
"threemaBasicModeInfo": "Nóta: Úsáideann an comhtháthú seo Threema Gateway i mód bunúsach (criptiúchán freastalaí-bhunaithe). Is féidir tuilleadh sonraí a fháil {0}.",
|
||||||
|
"jsonQueryDescription": "Parsáil agus bain sonraí ar leith ó fhreagra JSON an fhreastalaí ag baint úsáide as ceist JSON nó úsáid \"$\" don fhreagra amh, mura bhfuil tú ag súil le JSON. Cuirtear an toradh i gcomparáid ansin leis an luach ionchais, mar teaghráin. Féach ar {0} le haghaidh doiciméadú agus úsáid {1} chun triail a bhaint as fiosruithe.",
|
||||||
|
"snmpCommunityStringHelptext": "Feidhmíonn an teaghrán seo mar phasfhocal chun rochtain ar ghléasanna SNMP-chumasaithe a fhíordheimhniú agus a rialú. Meaitseáil sé le cumraíocht do ghléis SNMP.",
|
||||||
|
"snmpOIDHelptext": "Cuir isteach an OID don braiteoir nó don stádas ar mhaith leat monatóireacht a dhéanamh air. Úsáid uirlisí bainistíochta líonra ar nós brabhsálaithe MIB nó bogearraí SNMP mura bhfuil tú cinnte faoin OID.",
|
||||||
|
"wayToGetOnesenderUrlandToken": "Is féidir leat an URL agus Token a fháil ach dul chuig láithreán gréasáin Onesender. Tuilleadh eolais {0}",
|
||||||
|
"Lost connection to the socket server.": "Ceangal caillte leis an bhfreastalaí soicéad.",
|
||||||
|
"signl4Docs": "Is féidir leat tuilleadh faisnéise a fháil faoi conas SIGNL4 a chumrú agus conas an URL SIGNL4 a fháil sa {0}.",
|
||||||
|
"not starts with": "ní thosaíonn le",
|
||||||
|
"greater than or equal to": "níos mó ná nó cothrom le",
|
||||||
|
"now": "anois",
|
||||||
|
"time ago": "{0} ó shin",
|
||||||
|
"-year": "-bliain",
|
||||||
|
"Json Query Expression": "Léiriú Ceist Json",
|
||||||
|
"and": "agus",
|
||||||
|
"cacheBusterParam": "Cuir an {0} paraiméadar leis",
|
||||||
|
"cacheBusterParamDescription": "Paraiméadar ginte go randamach chun scipeáil a dhéanamh ar thaisce.",
|
||||||
|
"Community String": "Teaghrán Pobail",
|
||||||
|
"OID (Object Identifier)": "OID (Aitheantóir Oibiachta)",
|
||||||
|
"Condition": "Coinníoll",
|
||||||
|
"SNMP Version": "Leagan SNMP",
|
||||||
|
"Please enter a valid OID.": "Cuir isteach OID bailí.",
|
||||||
|
"Host Onesender": "Óstach Onesender",
|
||||||
|
"Token Onesender": "Token Onesender",
|
||||||
|
"Recipient Type": "Cineál Faighteoir",
|
||||||
|
"Private Number": "Uimhir Phríobháideach",
|
||||||
|
"privateOnesenderDesc": "Cinntigh go bhfuil an uimhir theileafóin bailí. Chun teachtaireacht a sheoladh chuig uimhir ghutháin phríobháideach, sean: 628123456789",
|
||||||
|
"groupOnesenderDesc": "Cinntigh go bhfuil an GroupID bailí. Chun teachtaireacht a sheoladh chuig an nGrúpa, sean: 628123456789-342345",
|
||||||
|
"Group ID": "ID grúpa",
|
||||||
|
"Add Remote Browser": "Cuir Brabhsálaí Cianda leis",
|
||||||
|
"New Group": "Grúpa Nua",
|
||||||
|
"Group Name": "Ainm an Ghrúpa",
|
||||||
|
"OAuth2: Client Credentials": "OAuth2: Dintiúir Cliant",
|
||||||
|
"Authentication Method": "Modh Fíordheimhnithe",
|
||||||
|
"Authorization Header": "Ceanntásc Údaraithe",
|
||||||
|
"Form Data Body": "Comhlacht Sonraí Foirm",
|
||||||
|
"OAuth Token URL": "URL OAuth Token",
|
||||||
|
"Client ID": "ID Cliant",
|
||||||
|
"Client Secret": "Rúnda Cliant",
|
||||||
|
"OAuth Scope": "OAuth Scóip",
|
||||||
|
"Optional: Space separated list of scopes": "Roghnach: Liosta scóip spásscartha",
|
||||||
|
"Go back to home page.": "Téigh ar ais go dtí an leathanach baile.",
|
||||||
|
"No tags found.": "Níor aimsíodh clibeanna.",
|
||||||
|
"Cannot connect to the socket server.": "Ní féidir ceangal leis an bhfreastalaí soicéad.",
|
||||||
|
"SIGNL4": "SIGNL4",
|
||||||
|
"SIGNL4 Webhook URL": "URL SIGNL4 Cabhrán Gréasáin",
|
||||||
|
"Conditions": "Coinníollacha",
|
||||||
|
"conditionAdd": "Cuir Coinníoll leis",
|
||||||
|
"conditionDelete": "Scrios Coinníoll",
|
||||||
|
"conditionAddGroup": "Cuir Grúpa leis",
|
||||||
|
"conditionDeleteGroup": "Scrios Grúpa",
|
||||||
|
"conditionValuePlaceholder": "Luach",
|
||||||
|
"equals": "comhionann",
|
||||||
|
"not equals": "ní ionann",
|
||||||
|
"contains": "ina bhfuil",
|
||||||
|
"not contains": "nach bhfuil",
|
||||||
|
"starts with": "thosaíonn le",
|
||||||
|
"ends with": "chríochnaíonn le",
|
||||||
|
"not ends with": "ní chríochnaíonn le",
|
||||||
|
"less than": "níos lú ná",
|
||||||
|
"greater than": "níos mó ná",
|
||||||
|
"less than or equal to": "níos lú ná nó cothrom le",
|
||||||
|
"record": "taifead",
|
||||||
|
"shrinkDatabaseDescriptionSqlite": "Bunachar sonraí truicear {vacuum} le haghaidh SQLite. Tá {auto_vacuum} cumasaithe cheana féin ach ní dhéanann sé seo scoilt ar an mbunachar sonraí ná athphacáil leathanaigh aonair an bhunachair sonraí mar a dhéanann an t-ordú {vacuum}."
|
||||||
}
|
}
|
||||||
|
|
|
@ -378,7 +378,6 @@
|
||||||
"Discard": "להשליך",
|
"Discard": "להשליך",
|
||||||
"Cancel": "לְבַטֵל",
|
"Cancel": "לְבַטֵל",
|
||||||
"Powered by": "פועל על",
|
"Powered by": "פועל על",
|
||||||
"shrinkDatabaseDescription": "ואקום מסד נתונים להפעיל עבור SQLITE.אם בסיס הנתונים שלך נוצר לאחר 1.10.0, Auto_VACUUM כבר מופעל ואין צורך בפעולה זו.",
|
|
||||||
"serwersms": "SerwerSMS.pl",
|
"serwersms": "SerwerSMS.pl",
|
||||||
"serwersmsAPIUser": "שם משתמש ל-API (כולל webapi_prefix)",
|
"serwersmsAPIUser": "שם משתמש ל-API (כולל webapi_prefix)",
|
||||||
"serwersmsAPIPassword": "סיסמת API",
|
"serwersmsAPIPassword": "סיסמת API",
|
||||||
|
|
|
@ -40,5 +40,23 @@
|
||||||
"Down": "बंद",
|
"Down": "बंद",
|
||||||
"Passive Monitor Type": "निष्क्रिय मॉनिटर प्रकार",
|
"Passive Monitor Type": "निष्क्रिय मॉनिटर प्रकार",
|
||||||
"Status": "स्थिति",
|
"Status": "स्थिति",
|
||||||
"showCertificateExpiry": "प्रमाणपत्र समाप्ति दिखाएँ"
|
"showCertificateExpiry": "प्रमाणपत्र समाप्ति दिखाएँ",
|
||||||
|
"setupDatabaseEmbeddedMariaDB": "आपको कुछ भी सेट करने की जरूरत नहीं है. इस डॉकर छवि ने आपके लिए मारियाडीबी को स्वचालित रूप से एम्बेड और कॉन्फ़िगर किया है। अपटाइम कुमा यूनिक्स सॉकेट के माध्यम से इस डेटाबेस से कनेक्ट होगा।",
|
||||||
|
"setupDatabaseChooseDatabase": "आप किस डेटाबेस का उपयोग करना चाहेंगे?",
|
||||||
|
"setupDatabaseMariaDB": "किसी बाहरी मारियाडीबी डेटाबेस से कनेक्ट करें। आपको डेटाबेस कनेक्शन जानकारी सेट करने की आवश्यकता है।",
|
||||||
|
"setupDatabaseSQLite": "एक सरल डेटाबेस फ़ाइल, जो छोटे पैमाने पर तैनाती के लिए अनुशंसित है। V2.0.0 से पहले, अपटाइम कुमा ने डिफ़ॉल्ट डेटाबेस के रूप में SQLite का उपयोग किया था।",
|
||||||
|
"settingUpDatabaseMSG": "डेटाबेस की स्थापना. इसमें थोड़ा समय लग सकता है, कृपया धैर्य रखें।",
|
||||||
|
"markdownSupported": "मार्कडाउन सिंटैक्स समर्थित",
|
||||||
|
"time ago": "समय पहले",
|
||||||
|
"day": "दिन",
|
||||||
|
"Primary Base URL": "प्राथमिक आधार यूआरएल",
|
||||||
|
"-hour": "-घंटा",
|
||||||
|
"-day": "-दिन",
|
||||||
|
"hour": "घंटे",
|
||||||
|
"DateTime": "दिनांक समय",
|
||||||
|
"Uptime": "उपरिकाल",
|
||||||
|
"Cert Exp.": "प्रमाणपत्र अनुभव.",
|
||||||
|
"dbName": "डेटाबेस का नाम",
|
||||||
|
"now": "अभी",
|
||||||
|
"-year": "-वर्ष"
|
||||||
}
|
}
|
||||||
|
|
|
@ -353,7 +353,6 @@
|
||||||
"Saved.": "Spremljeno.",
|
"Saved.": "Spremljeno.",
|
||||||
"PushByTechulus": "Push by Techulus",
|
"PushByTechulus": "Push by Techulus",
|
||||||
"GoogleChat": "Google Chat (preko platforme Google Workspace)",
|
"GoogleChat": "Google Chat (preko platforme Google Workspace)",
|
||||||
"shrinkDatabaseDescription": "Pokreni VACUUM operaciju za SQLite. Ako je baza podataka kreirana nakon inačice 1.10.0, AUTO_VACUUM opcija već je uključena te ova akcija nije nužna.",
|
|
||||||
"serwersms": "SerwerSMS.pl",
|
"serwersms": "SerwerSMS.pl",
|
||||||
"serwersmsAPIUser": "API korisničko ime (uključujući webapi_ prefiks)",
|
"serwersmsAPIUser": "API korisničko ime (uključujući webapi_ prefiks)",
|
||||||
"serwersmsAPIPassword": "API lozinka",
|
"serwersmsAPIPassword": "API lozinka",
|
||||||
|
@ -984,5 +983,104 @@
|
||||||
"threemaRecipientTypePhone": "Telefonski broj",
|
"threemaRecipientTypePhone": "Telefonski broj",
|
||||||
"threemaRecipientTypePhoneFormat": "E.164, bez vodećeg znaka +",
|
"threemaRecipientTypePhoneFormat": "E.164, bez vodećeg znaka +",
|
||||||
"threemaBasicModeInfo": "Napomena: Ova integracija koristi Threema Gateway u osnovnom načinu rada (enkripcija temeljena na poslužitelju). Dodatne pojedinosti možete pronaći na {0}.",
|
"threemaBasicModeInfo": "Napomena: Ova integracija koristi Threema Gateway u osnovnom načinu rada (enkripcija temeljena na poslužitelju). Dodatne pojedinosti možete pronaći na {0}.",
|
||||||
"smspartnerSenderNameInfo": "Mora biti između 3 i 11 znakova"
|
"smspartnerSenderNameInfo": "Mora biti između 3 i 11 znakova",
|
||||||
|
"shrinkDatabaseDescriptionSqlite": "Pokreni {vacuum} baze podataka za SQLite. Opcija {auto_vacuum} je već omogućena, ali to ne defragmentira bazu podataka niti ponovno pakira pojedinačne stranice baze podataka na način na koji to radi naredba {vacuum}.",
|
||||||
|
"ignoredTLSError": "TLS/SSL greške se ignoriraju",
|
||||||
|
"cacheBusterParam": "Dodaj parametar {0}",
|
||||||
|
"cacheBusterParamDescription": "Nasumično generirani parametar, za preskakanje predmemorije.",
|
||||||
|
"snmpCommunityStringHelptext": "Ovaj niz funkcionira kao lozinka za provjeru autentičnosti i kontrolu pristupa uređajima s omogućenim SNMP-om. Uskladite ga sa svojom konfiguracijom SNMP uređaja.",
|
||||||
|
"privateOnesenderDesc": "Provjerite je li broj telefona valjan. Za slanje poruke na privatni telefonski broj, npr. 628123456789",
|
||||||
|
"Go back to home page.": "Vratite se na početnu stranicu.",
|
||||||
|
"signl4Docs": "Više informacija o tome kako konfigurirati SIGNL4 i kako dobiti SIGNL4 URL webhooka možete pronaći na {0}.",
|
||||||
|
"not starts with": "ne počinje s",
|
||||||
|
"less than or equal to": "manje od ili jednako",
|
||||||
|
"Doorbell": "Zvono na vratima",
|
||||||
|
"Custom sound to override default notification sound": "Prilagođeni zvuk za zamjenu zadanog zvuka obavijesti",
|
||||||
|
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Vremenski osjetljive obavijesti bit će isporučene odmah, čak i ako je uređaj u načinu rada bez ometanja.",
|
||||||
|
"now": "sada",
|
||||||
|
"time ago": "prije {0}",
|
||||||
|
"-year": "-godišnje",
|
||||||
|
"Json Query Expression": "Upit u JSON obliku",
|
||||||
|
"Community String": "Zajednički niz teksta",
|
||||||
|
"conditionAddGroup": "Dodaj grupu",
|
||||||
|
"conditionDeleteGroup": "Obriši grupu",
|
||||||
|
"Debug": "Otklanjanje grešaka",
|
||||||
|
"Copy": "Kopirati",
|
||||||
|
"CopyToClipboardError": "Greška pri kopiranju u međuspremnik: {error}",
|
||||||
|
"CopyToClipboardSuccess": "Kopirano!",
|
||||||
|
"dns resolvers": "DNS razrješivači",
|
||||||
|
"firewalls": "vatrozidi",
|
||||||
|
"CurlDebugInfo": "Za otklanjanje grešaka u Monitoru, možete zalijepiti ovo u terminal na vlastitom računalu ili računalu na kojem se Uptime Kuma pokreće, kako biste isprobali Vaš mrežni zahtjev.{newiline}Budite svjesni mrežnih razlika koje mogu činiti {firewalls}, {dns_resolvers} ili {docker_networks}.",
|
||||||
|
"docker networks": "Dockerove mreže",
|
||||||
|
"CurlDebugInfoOAuth2CCUnsupported": "Potpuni tok vjerodajnica klijenta OAuth nije podržan u {curl}.{newline}Nabavite token nositelja i proslijedite ga koristeći opciju {oauth2_bearer}.",
|
||||||
|
"CurlDebugInfoProxiesUnsupported": "Proxy podrška u gornjoj naredbi {curl} trenutno nije implementirana.",
|
||||||
|
"and": "i",
|
||||||
|
"Message format": "Format poruke",
|
||||||
|
"Send rich messages": "Slanje poruka s obogaćenim tekstom",
|
||||||
|
"OID (Object Identifier)": "OID (Identifikator objekta)",
|
||||||
|
"snmpOIDHelptext": "Unesite OID za senzor ili status kojeg želite monitorirati. Koristite alate za upravljanje mrežom poput MIB preglednika ili SNMP programa ako niste sigurni koja je vrijednost OID-a.",
|
||||||
|
"Condition": "Uvjet",
|
||||||
|
"SNMP Version": "Inačica SNMP-a",
|
||||||
|
"Please enter a valid OID.": "Unesite važeći OID.",
|
||||||
|
"Recipient Type": "Tip primatelja",
|
||||||
|
"Private Number": "Privatni broj",
|
||||||
|
"wayToGetOnesenderUrlandToken": "URL i token možete dobiti odlaskom na OneSender web stranicu. Više informacija na {0}",
|
||||||
|
"Token Onesender": "OneSender Token",
|
||||||
|
"Host Onesender": "Adresa OneSender domaćina",
|
||||||
|
"Group ID": "Identifikator Grupe",
|
||||||
|
"groupOnesenderDesc": "Provjerite je li Identifikator Grupe valjan. Za slanje poruke na Grupu, npr. 628123456789-342345",
|
||||||
|
"Add Remote Browser": "Dodaj udaljeni preglednik",
|
||||||
|
"New Group": "Nova grupa",
|
||||||
|
"Group Name": "Naziv grupe",
|
||||||
|
"OAuth2: Client Credentials": "OAuth2: vjerodajnice klijenta",
|
||||||
|
"Authentication Method": "Metoda provjere autentičnosti",
|
||||||
|
"Authorization Header": "Zaglavlje autorizacije",
|
||||||
|
"Form Data Body": "Tijelo podataka obrasca",
|
||||||
|
"OAuth Token URL": "URL OAuth tokena",
|
||||||
|
"Client ID": "Klijentski identifikator",
|
||||||
|
"Client Secret": "Klijentska tajna",
|
||||||
|
"OAuth Scope": "OAuth opseg",
|
||||||
|
"Optional: Space separated list of scopes": "Neobavezno: popis opsega odvojen razmakom",
|
||||||
|
"No tags found.": "Nema pronađenih oznaka.",
|
||||||
|
"Lost connection to the socket server.": "Izgubljena veza sa socket poslužiteljem.",
|
||||||
|
"Cannot connect to the socket server.": "Nije moguće spojiti se na socket poslužitelj.",
|
||||||
|
"SIGNL4": "SIGNL4",
|
||||||
|
"SIGNL4 Webhook URL": "SIGNL4 URL webhooka",
|
||||||
|
"Conditions": "Uvjeti",
|
||||||
|
"conditionAdd": "Dodaj uvjet",
|
||||||
|
"conditionDelete": "Obriši uvjet",
|
||||||
|
"conditionValuePlaceholder": "Vrijednost",
|
||||||
|
"equals": "je jednako",
|
||||||
|
"not equals": "nije jednako",
|
||||||
|
"contains": "sadržava",
|
||||||
|
"not contains": "ne sadržava",
|
||||||
|
"starts with": "počinje s",
|
||||||
|
"ends with": "završava s",
|
||||||
|
"not ends with": "ne završava s",
|
||||||
|
"less than": "manje od",
|
||||||
|
"greater than": "veće od",
|
||||||
|
"greater than or equal to": "veće od ili jednako",
|
||||||
|
"record": "zapis",
|
||||||
|
"Notification Channel": "Kanal obavijesti",
|
||||||
|
"Sound": "Zvuk",
|
||||||
|
"Alphanumerical string and hyphens only": "Samo alfanumerički niz i crtice",
|
||||||
|
"Arcade": "Arkadno",
|
||||||
|
"Fail": "Neuspjeh",
|
||||||
|
"Correct": "Ispravno",
|
||||||
|
"Harp": "Harfa",
|
||||||
|
"Reveal": "Otkrivanje",
|
||||||
|
"Bubble": "Mjehurić",
|
||||||
|
"Flute": "Flauta",
|
||||||
|
"Money": "Novac",
|
||||||
|
"Scifi": "Znanstvena fantastika",
|
||||||
|
"Clear": "Čisto",
|
||||||
|
"Elevator": "Dizalo",
|
||||||
|
"Guitar": "Gitara",
|
||||||
|
"Pop": "Pop",
|
||||||
|
"Time Sensitive (iOS Only)": "Vremenski osjetljivo (samo iOS)",
|
||||||
|
"From": "Od",
|
||||||
|
"Can be found on:": "Može se pronaći na: {0}",
|
||||||
|
"The phone number of the recipient in E.164 format.": "Telefonski broj primatelja u formatu E.164.",
|
||||||
|
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.": "Ili identifikator pošiljatelja tekstualne poruke ili telefonski broj u formatu E.164 ako želite primati odgovore.",
|
||||||
|
"jsonQueryDescription": "Izvršite JSON upit nad primljenim odgovorom i provjerite očekivanu povratnu vrijednost. Koristite \"$\" za zahtjeve u kojima ne očekujete JSON odgovor. Povratna vrijednost će se za usporedbu pretvoriti u niz znakova (string). Pogledajte stranicu {0} za dokumentaciju o jeziku upita. Testno okruženje možete pronaći na {1}."
|
||||||
}
|
}
|
||||||
|
|
|
@ -349,7 +349,6 @@
|
||||||
"Discard": "Elvet",
|
"Discard": "Elvet",
|
||||||
"Cancel": "Mégsem",
|
"Cancel": "Mégsem",
|
||||||
"Powered by": "A megoldást szállítja az",
|
"Powered by": "A megoldást szállítja az",
|
||||||
"shrinkDatabaseDescription": "VACUUM futtatása az SQLite-on. Ha az adatbázisod 1.10.0-nál újabb, akkor az AUTO_VACUUM engedélyezve van, nincs szükség a műveletre.",
|
|
||||||
"serwersms": "SerwerSMS.pl",
|
"serwersms": "SerwerSMS.pl",
|
||||||
"serwersmsAPIUser": "API felhasználónév (webapi_ előtaggal együtt)",
|
"serwersmsAPIUser": "API felhasználónév (webapi_ előtaggal együtt)",
|
||||||
"serwersmsAPIPassword": "API jelszó",
|
"serwersmsAPIPassword": "API jelszó",
|
||||||
|
|
|
@ -361,7 +361,6 @@
|
||||||
"Discard": "Buang",
|
"Discard": "Buang",
|
||||||
"Cancel": "Batal",
|
"Cancel": "Batal",
|
||||||
"Powered by": "Dipersembahkan oleh",
|
"Powered by": "Dipersembahkan oleh",
|
||||||
"shrinkDatabaseDescription": "Trigger database VACUUM untuk SQLite. Jika database Anda dibuat setelah 1.10.0, AUTO_VACUUM sudah otomatis diaktifkan dan aksi berikut tidak dibutuhkan.",
|
|
||||||
"serwersms": "SerwerSMS.pl",
|
"serwersms": "SerwerSMS.pl",
|
||||||
"serwersmsAPIUser": "Nama Pengguna API ( termamsuk awalan webapi_ )",
|
"serwersmsAPIUser": "Nama Pengguna API ( termamsuk awalan webapi_ )",
|
||||||
"serwersmsAPIPassword": "Kata Sandi API",
|
"serwersmsAPIPassword": "Kata Sandi API",
|
||||||
|
@ -985,5 +984,73 @@
|
||||||
"apiKeySevenIO": "Kunci API SevenIO",
|
"apiKeySevenIO": "Kunci API SevenIO",
|
||||||
"locally configured mail transfer agent": "transfer email agent yang dikonfigurasi secara lokal",
|
"locally configured mail transfer agent": "transfer email agent yang dikonfigurasi secara lokal",
|
||||||
"Either enter the hostname of the server you want to connect to or localhost if you intend to use a locally configured mail transfer agent": "Masukkan nama host server yang ingin Anda sambungkan atau {localhost} jika Anda ingin menggunakan {local_mta}",
|
"Either enter the hostname of the server you want to connect to or localhost if you intend to use a locally configured mail transfer agent": "Masukkan nama host server yang ingin Anda sambungkan atau {localhost} jika Anda ingin menggunakan {local_mta}",
|
||||||
"ignoreTLSErrorGeneral": "Abaikan kesalahan TLS/SSL untuk koneksi"
|
"ignoreTLSErrorGeneral": "Abaikan kesalahan TLS/SSL untuk koneksi",
|
||||||
|
"jsonQueryDescription": "Mengurai dan mengekstrak data spesifik dari respons JSON server menggunakan query JSON atau menggunakan “$” untuk respons mentah, jika tidak menginginkan JSON. Hasilnya kemudian dibandingkan dengan nilai yang diharapkan, sebagai string. Lihat {0} untuk dokumentasi dan gunakan {1} untuk bereksperimen dengan query.",
|
||||||
|
"now": "sekarang",
|
||||||
|
"time ago": "{0} yang lalu",
|
||||||
|
"-year": "-tahun",
|
||||||
|
"Json Query Expression": "Ekspresi Kueri Json",
|
||||||
|
"and": "dan",
|
||||||
|
"cacheBusterParam": "Tambahkan parameter {0}",
|
||||||
|
"cacheBusterParamDescription": "Parameter yang dihasilkan secara acak untuk melewati cache.",
|
||||||
|
"Community String": "Community String",
|
||||||
|
"snmpCommunityStringHelptext": "String ini berfungsi sebagai kata sandi untuk autentikasi dan mengontrol akses ke perangkat SNMP. Cocokkan dengan konfigurasi perangkat SNMP Anda.",
|
||||||
|
"OID (Object Identifier)": "OID (Object Identifier)",
|
||||||
|
"snmpOIDHelptext": "Masukkan OID untuk sensor atau status yang ingin Anda pantau. Gunakan alat manajemen jaringan seperti browser MIB atau perangkat lunak SNMP jika Anda tidak yakin tentang OID.",
|
||||||
|
"signl4Docs": "Anda dapat menemukan informasi selengkapnya tentang cara mengonfigurasi SIGNL4 dan cara mendapatkan URL webhook SIGNL4 di {0}.",
|
||||||
|
"greater than or equal to": "lebih dari atau sama dengan",
|
||||||
|
"wayToGetOnesenderUrlandToken": "Anda bisa mendapatkan URL dan Token dengan mengunjungi situs Onesender. Informasi lebih lanjut {0}",
|
||||||
|
"Condition": "Kondisi",
|
||||||
|
"SNMP Version": "Versi SNMP",
|
||||||
|
"Please enter a valid OID.": "Silakan masukkan OID yang tepat.",
|
||||||
|
"Host Onesender": "Host Onesender",
|
||||||
|
"Token Onesender": "Token Onesender",
|
||||||
|
"Recipient Type": "Tipe Penerima",
|
||||||
|
"Private Number": "Nomor Pribadi",
|
||||||
|
"privateOnesenderDesc": "Pastikan nomor telepon valid. Untuk mengirim pesan ke nomor telepon pribadi, contoh: 628123456789",
|
||||||
|
"Authorization Header": "Authorization Header",
|
||||||
|
"Form Data Body": "Form Data Body",
|
||||||
|
"OAuth Token URL": "OAuth Token URL",
|
||||||
|
"Client ID": "ID Klien",
|
||||||
|
"Client Secret": "Client Secret",
|
||||||
|
"OAuth Scope": "OAuth Scope",
|
||||||
|
"Optional: Space separated list of scopes": "Opsional: Daftar cakupan yang dipisahkan spasi",
|
||||||
|
"Go back to home page.": "Kembali ke halaman beranda.",
|
||||||
|
"No tags found.": "Tidak ada tag yang ditemukan.",
|
||||||
|
"Lost connection to the socket server.": "Kehilangan koneksi ke server soket.",
|
||||||
|
"Cannot connect to the socket server.": "Tidak dapat terhubung ke server soket.",
|
||||||
|
"SIGNL4": "SIGNL4",
|
||||||
|
"SIGNL4 Webhook URL": "SIGNL4 Webhook URL",
|
||||||
|
"Conditions": "Kondisi",
|
||||||
|
"conditionAdd": "Tambahkan Kondisi",
|
||||||
|
"conditionDelete": "Menghapus Kondisi",
|
||||||
|
"conditionAddGroup": "Tambahkan Grup",
|
||||||
|
"conditionDeleteGroup": "Menghapus Grup",
|
||||||
|
"conditionValuePlaceholder": "Nilai",
|
||||||
|
"equals": "sama dengan",
|
||||||
|
"not equals": "tidak sama dengan",
|
||||||
|
"contains": "berisi",
|
||||||
|
"not contains": "tidak berisi",
|
||||||
|
"starts with": "dimulai dengan",
|
||||||
|
"not starts with": "tidak dimulai dengan",
|
||||||
|
"ends with": "diakhiri dengan",
|
||||||
|
"not ends with": "tidak diakhiri dengan",
|
||||||
|
"less than": "kurang dari",
|
||||||
|
"greater than": "lebih dari",
|
||||||
|
"less than or equal to": "kurang dari atau sama dengan",
|
||||||
|
"record": "rekaman",
|
||||||
|
"groupOnesenderDesc": "Pastikan GroupIDnya valid. Untuk mengirim pesan ke Grup, misal: 628123456789-342345",
|
||||||
|
"Group ID": "ID Grup",
|
||||||
|
"Add Remote Browser": "Tambahkan Remote Browser",
|
||||||
|
"New Group": "Grup Baru",
|
||||||
|
"Group Name": "Nama Grup",
|
||||||
|
"OAuth2: Client Credentials": "OAuth2: Kredensial Klien",
|
||||||
|
"Authentication Method": "Metode Autentikasi",
|
||||||
|
"shrinkDatabaseDescriptionSqlite": "Memicu pangkalan data {vacuum} untuk SQLite. {auto_vacuum} sudah diaktifkan, tetapi tidak mendefragmentasi pangkalan data atau mengemas ulang halaman individual dari pangkalan data seperti yang dilakukan oleh perintah {vacuum}.",
|
||||||
|
"ignoredTLSError": "Galat TLS/SSL sudah diabaikan",
|
||||||
|
"Debug": "Awakutu",
|
||||||
|
"Copy": "Salin",
|
||||||
|
"CopyToClipboardError": "Tidak bisa menyalin ke papan klip: {galat}",
|
||||||
|
"CopyToClipboardSuccess": "Tersalin!",
|
||||||
|
"CurlDebugInfo": "Untuk pengawakutuan monitor, Anda bisa menempelkan ini ke terminal mesin Anda sendiri atau ke terminal mesin di mana uptime kuma sedang berjalan dan melihat apa yang Anda harapkan. Mohon perhatikan perbedaan jaringan seperti {firewalls}, {dns_resolvers}, atau {docker_networks}."
|
||||||
}
|
}
|
||||||
|
|
|
@ -349,7 +349,6 @@
|
||||||
"Discard": "Scarta modifiche",
|
"Discard": "Scarta modifiche",
|
||||||
"Cancel": "Annulla",
|
"Cancel": "Annulla",
|
||||||
"Powered by": "Powered by",
|
"Powered by": "Powered by",
|
||||||
"shrinkDatabaseDescription": "Lancia il comando \"VACUUM\" sul database SQLite. Se il database è stato creato dopo la versione 1.10.0, la funzione \"AUTO_VACUUM\" è già abilitata di default e quindi questa azione non è necessaria.",
|
|
||||||
"serwersms": "SerwerSMS.pl",
|
"serwersms": "SerwerSMS.pl",
|
||||||
"serwersmsAPIUser": "Nome utente API (incl. prefisso webapi_)",
|
"serwersmsAPIUser": "Nome utente API (incl. prefisso webapi_)",
|
||||||
"serwersmsAPIPassword": "Password API",
|
"serwersmsAPIPassword": "Password API",
|
||||||
|
@ -686,7 +685,7 @@
|
||||||
"forumPostName": "Nome forum del post",
|
"forumPostName": "Nome forum del post",
|
||||||
"threadForumPostID": "ID della conversazione / Forum del post",
|
"threadForumPostID": "ID della conversazione / Forum del post",
|
||||||
"e.g. {discordThreadID}": "es. {discordThreadID}",
|
"e.g. {discordThreadID}": "es. {discordThreadID}",
|
||||||
"Either enter the hostname of the server you want to connect to or localhost if you intend to use a locally configured mail transfer agent": "Inserisci il nome host del server a cui vuoi connetterti oppure {localhost} se intendi utilizzare un {local_mta}",
|
"Either enter the hostname of the server you want to connect to or localhost if you intend to use a locally configured mail transfer agent": "Inserisci l'hostname del server a cui ti vuoi connettere o {localhost} se vuoi usare {local_mta}",
|
||||||
"templateLimitedToUpDownCertNotifications": "disponibile solo per le notifiche UP/DOWN/Scadenza certificato",
|
"templateLimitedToUpDownCertNotifications": "disponibile solo per le notifiche UP/DOWN/Scadenza certificato",
|
||||||
"emailTemplateMsg": "Messaggio della notifica",
|
"emailTemplateMsg": "Messaggio della notifica",
|
||||||
"emailTemplateLimitedToUpDownNotification": "disponibile solo per controlli UP/DOWN, altrimenti nullo",
|
"emailTemplateLimitedToUpDownNotification": "disponibile solo per controlli UP/DOWN, altrimenti nullo",
|
||||||
|
@ -698,5 +697,45 @@
|
||||||
"emailTemplateStatus": "Stato",
|
"emailTemplateStatus": "Stato",
|
||||||
"ignoreTLSErrorGeneral": "Ignora gli errori TLS/SSL per la connessione",
|
"ignoreTLSErrorGeneral": "Ignora gli errori TLS/SSL per la connessione",
|
||||||
"statusPageSpecialSlugDesc": "Slug speciale {0}: questa pagina verrà mostrata quando non viene fornito alcuno slug",
|
"statusPageSpecialSlugDesc": "Slug speciale {0}: questa pagina verrà mostrata quando non viene fornito alcuno slug",
|
||||||
"successKeywordExplanation": "Parola chiave MQTT che verrà considerata come corretta"
|
"successKeywordExplanation": "Parola chiave MQTT che verrà considerata come corretta",
|
||||||
|
"signl4Docs": "Puoi trovare maggiori informazioni su come configurare SIGNL4 e su come ottenere l'URL del webhook SIGNL4 in {0}.",
|
||||||
|
"contains": "contiene",
|
||||||
|
"not contains": "non contiene",
|
||||||
|
"starts with": "inizia con",
|
||||||
|
"equals": "uguale",
|
||||||
|
"not equals": "non uguale",
|
||||||
|
"less than": "meno di",
|
||||||
|
"greater than": "maggiore di",
|
||||||
|
"successKeyword": "Parola chiave corretta",
|
||||||
|
"templateMonitorJSON": "oggetto che descrive il monitor",
|
||||||
|
"Cannot connect to the socket server.": "Impossibile connettersi al server socket.",
|
||||||
|
"wayToGetDiscordThreadId": "Ottenere l'ID di un thread/post del forum è simile a ottenere un ID di canale. Scopri di più su come ottenere gli ID {0}",
|
||||||
|
"pushoversounds pushover": "Pushover (predefinita)",
|
||||||
|
"pushoversounds none": "Nessuno (silenzioso)",
|
||||||
|
"Don't mention people": "Non menzionare le persone",
|
||||||
|
"Mention group": "Menzionare {group}",
|
||||||
|
"Host URL": "URL dell'host",
|
||||||
|
"emailTemplateMonitorJSON": "oggetto che descrive il monitor",
|
||||||
|
"Channel access token (Long-lived)": "Token di accesso al canale (di lunga durata)",
|
||||||
|
"Your User ID": "Il tuo ID utente",
|
||||||
|
"pushoverMessageTtl": "Messaggio TTL (secondi)",
|
||||||
|
"SendKey": "SendKey",
|
||||||
|
"Sms template must contain parameters: ": "Il modello SMS deve contenere i parametri: ",
|
||||||
|
"Bark API Version": "Versione API Bark",
|
||||||
|
"Bark Group": "Gruppo Bark",
|
||||||
|
"Bark Endpoint": "Endpoint Bark",
|
||||||
|
"Bark Sound": "Suono Bark",
|
||||||
|
"aboutNotifyChannel": "Notify channel attiverà una notifica desktop o mobile per tutti i membri del canale, indipendentemente dal fatto che la loro disponibilità sia impostata su attivo o assente.",
|
||||||
|
"promosmsPassword": "Password API",
|
||||||
|
"You can divide numbers with": "Puoi dividere i numeri con",
|
||||||
|
"Base URL": "URL base",
|
||||||
|
"-year": "-anno",
|
||||||
|
"now": "ora",
|
||||||
|
"time ago": "fa",
|
||||||
|
"Json Query Expression": "Espressione di query JSON",
|
||||||
|
"and": "e",
|
||||||
|
"whatHappensAtForumPost": "Crea un nuovo post sul forum. Questo NON pubblica messaggi in post esistenti. Per pubblicare in un post esistente utilizzare \"{option}\"",
|
||||||
|
"invertKeywordDescription": "Cerca la parola chiave essere assente anziché presente.",
|
||||||
|
"octopushAPIKey": "\"API Key\" dalle credenziali API HTTP nel pannello di controllo",
|
||||||
|
"Enable TLS": "Abilita TLS"
|
||||||
}
|
}
|
||||||
|
|
|
@ -349,7 +349,6 @@
|
||||||
"No monitors available.": "監視対象がありません。",
|
"No monitors available.": "監視対象がありません。",
|
||||||
"Add one": "追加",
|
"Add one": "追加",
|
||||||
"No Monitors": "監視対象なし",
|
"No Monitors": "監視対象なし",
|
||||||
"shrinkDatabaseDescription": "SQLiteデーターベースにVACUUMを実行します。データーベースがv1.10.0以降に作成されている場合、AUTO_VACUUMが既に有効なため実行する必要はありません。",
|
|
||||||
"enableProxyDescription": "このプロキシは有効化されない限り、監視リクエストには影響しません。無効化した場合、一時的にプロキシをすべての監視から無効化することができます。",
|
"enableProxyDescription": "このプロキシは有効化されない限り、監視リクエストには影響しません。無効化した場合、一時的にプロキシをすべての監視から無効化することができます。",
|
||||||
"Certificate Chain": "証明書チェーン",
|
"Certificate Chain": "証明書チェーン",
|
||||||
"Valid": "有効",
|
"Valid": "有効",
|
||||||
|
@ -673,5 +672,35 @@
|
||||||
"emailCustomBody": "カスタム本文",
|
"emailCustomBody": "カスタム本文",
|
||||||
"emailTemplateServiceName": "サービス名",
|
"emailTemplateServiceName": "サービス名",
|
||||||
"smtpLiquidIntroduction": "次の 2 つのテンプレート・フィールドは Liquid テンプレート言語で記述できます。これらの使い方は {0} にあります。以下は利用可能な変数です:",
|
"smtpLiquidIntroduction": "次の 2 つのテンプレート・フィールドは Liquid テンプレート言語で記述できます。これらの使い方は {0} にあります。以下は利用可能な変数です:",
|
||||||
"emailTemplateStatus": "ステータス"
|
"emailTemplateStatus": "ステータス",
|
||||||
|
"now": "現在",
|
||||||
|
"time ago": "{0}前",
|
||||||
|
"-year": "年",
|
||||||
|
"Json Query Expression": "Jsonクエリ表現",
|
||||||
|
"ignoredTLSError": "TLS/SSLエラーは無視されました",
|
||||||
|
"locally configured mail transfer agent": "独自設定されたメール転送エージェント",
|
||||||
|
"ignoreTLSErrorGeneral": "接続時のTLS/SSLエラーを無視する",
|
||||||
|
"successKeyword": "成功時のキーワード",
|
||||||
|
"pushViewCode": "Push モニターの使い方(コードを見る)",
|
||||||
|
"Reset Token": "トークンのリセット",
|
||||||
|
"templateLimitedToUpDownCertNotifications": "監視対象の UP/DOWN と証明書の有効期限通知でのみ利用可能",
|
||||||
|
"templateLimitedToUpDownNotifications": "監視対象の UP/DOWN の通知でのみ利用可能",
|
||||||
|
"webhookBodyPresetOption": "プリセット - {0}",
|
||||||
|
"Optional": "オプション",
|
||||||
|
"and": "かつ",
|
||||||
|
"From Email": "Emailより",
|
||||||
|
"CurlDebugInfoProxiesUnsupported": "上記コマンド {curl} のProxyサポートは現在、実装されていません。",
|
||||||
|
"Your User ID": "あなたのユーザーID",
|
||||||
|
"programmingLanguages": "プログラミング言語",
|
||||||
|
"Debug": "デバッグ",
|
||||||
|
"Copy": "コピー",
|
||||||
|
"CopyToClipboardError": "クリップボードにコピーできません: {error}",
|
||||||
|
"CopyToClipboardSuccess": "コピーしました!",
|
||||||
|
"firewalls": "ファイアウォール",
|
||||||
|
"dns resolvers": "DNSリゾルバ",
|
||||||
|
"docker networks": "Dockerネットワーク",
|
||||||
|
"sameAsServerTimezone": "サーバータイムゾーンと同じ",
|
||||||
|
"cronExpression": "Cron表記",
|
||||||
|
"invalidCronExpression": "不正なCron表記です: {0}",
|
||||||
|
"Single Maintenance Window": "シングルメンテナンスウィンドウ"
|
||||||
}
|
}
|
||||||
|
|
|
@ -349,7 +349,6 @@
|
||||||
"Discard": "취소",
|
"Discard": "취소",
|
||||||
"Cancel": "취소",
|
"Cancel": "취소",
|
||||||
"Powered by": "Powered by",
|
"Powered by": "Powered by",
|
||||||
"shrinkDatabaseDescription": "SQLite 데이터베이스 VACUUM을 트리거해요. 만약 데이터베이스가 1.10.0 버전 이후에 생성되었다면 AUTO_VACUUM이 설정되어 있어 이 작업은 필요 없을 거에요.",
|
|
||||||
"serwersms": "SerwerSMS.pl",
|
"serwersms": "SerwerSMS.pl",
|
||||||
"serwersmsAPIUser": "API Usename (webapi_ 접두사 포함)",
|
"serwersmsAPIUser": "API Usename (webapi_ 접두사 포함)",
|
||||||
"serwersmsAPIPassword": "API 비밀번호",
|
"serwersmsAPIPassword": "API 비밀번호",
|
||||||
|
@ -779,5 +778,12 @@
|
||||||
"Add a domain": "도메인 추가",
|
"Add a domain": "도메인 추가",
|
||||||
"Remove domain": "도메인 '{0}' 제거",
|
"Remove domain": "도메인 '{0}' 제거",
|
||||||
"Monitor Group": "모니터링 그룹",
|
"Monitor Group": "모니터링 그룹",
|
||||||
"Monitor Setting": "{0}의 모니터 설정"
|
"Monitor Setting": "{0}의 모니터 설정",
|
||||||
|
"now": "지금",
|
||||||
|
"time ago": "{0} 전",
|
||||||
|
"Either enter the hostname of the server you want to connect to or localhost if you intend to use a locally configured mail transfer agent": "연결하려는 서버의 호스트 이름을 입력하거나 {local_mta}를 사용하려는 경우 {localhost}를 입력합니다",
|
||||||
|
"-year": "-연도",
|
||||||
|
"Json Query Expression": "Json 쿼리 표현식",
|
||||||
|
"Host URL": "호스트 URL",
|
||||||
|
"locally configured mail transfer agent": "로컬로 구성된 메일 전송 에이전트"
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
"Down": "ကွန်ရက်ပြတ်တောက်နေသည်",
|
"Down": "ကွန်ရက်ပြတ်တောက်နေသည်",
|
||||||
"Pending": "ဆိုင်းငံ့ဆဲ",
|
"Pending": "ဆိုင်းငံ့ဆဲ",
|
||||||
"statusMaintenance": "ပြုပြင်ထိန်းသိမ်းခြင်း",
|
"statusMaintenance": "ပြုပြင်ထိန်းသိမ်းခြင်း",
|
||||||
"Cannot connect to the socket server": "Socket Server အားချိတ်ဆက်မှုမအောင်မြင်ပါ",
|
"Cannot connect to the socket server": "Why Socket Server အားချိတ်ဆက်မှုမအောင်မြင်ပါ",
|
||||||
"Passive Monitor Type": "Passive စောင့်ကြည့်မှုပုံစံ",
|
"Passive Monitor Type": "Passive စောင့်ကြည့်မှုပုံစံ",
|
||||||
"Specific Monitor Type": "သီးခြားစောင့်ကြည့်မှုပုံစံ",
|
"Specific Monitor Type": "သီးခြားစောင့်ကြည့်မှုပုံစံ",
|
||||||
"Resume": "ဆက်သွားမည်",
|
"Resume": "ဆက်သွားမည်",
|
||||||
|
@ -89,5 +89,7 @@
|
||||||
"Heartbeat Retry Interval": "ကွန်ယက်ချိတ်ဆက်နိုင်မှုတိုင်းတာခြင်း ထပ်မံကြိုးစားခြင်း အချိန်ကွာဟချက်",
|
"Heartbeat Retry Interval": "ကွန်ယက်ချိတ်ဆက်နိုင်မှုတိုင်းတာခြင်း ထပ်မံကြိုးစားခြင်း အချိန်ကွာဟချက်",
|
||||||
"Resend Notification if Down X times consecutively": "ကွန်ယက်ချိတ်ဆက်မှု X အကြိမ်ထိ ဆက်တိုက်ကျနေပါက သတိပေးချက်ထပ်မံပေးပို့ရန်",
|
"Resend Notification if Down X times consecutively": "ကွန်ယက်ချိတ်ဆက်မှု X အကြိမ်ထိ ဆက်တိုက်ကျနေပါက သတိပေးချက်ထပ်မံပေးပို့ရန်",
|
||||||
"retriesDescription": "ဝန်ဆောင်မှုကွန်ယက်ပြတ်တောက်နေ၍ သတိပေးချက်ပေးပို့ပြီး အများဆုံးထပ်မံကြိုးစားနိုင်မှု",
|
"retriesDescription": "ဝန်ဆောင်မှုကွန်ယက်ပြတ်တောက်နေ၍ သတိပေးချက်ပေးပို့ပြီး အများဆုံးထပ်မံကြိုးစားနိုင်မှု",
|
||||||
"Search Engine Visibility": "ရှာဖွေမှုအင်ဂျင်များ၏မြင်နိုင်စွမ်း"
|
"Search Engine Visibility": "ရှာဖွေမှုအင်ဂျင်များ၏မြင်နိုင်စွမ်း",
|
||||||
|
"setupDatabaseEmbeddedMariaDB": "You don't need to set anything. This docker image has embedded and configured MariaDB for you automatically. Uptime Kuma will connect to this database via unix socket.",
|
||||||
|
"setupDatabaseChooseDatabase": "English"
|
||||||
}
|
}
|
||||||
|
|
|
@ -254,7 +254,7 @@
|
||||||
"Channel Name": "Kanaal Naam",
|
"Channel Name": "Kanaal Naam",
|
||||||
"Uptime Kuma URL": "Uptime Kuma URL",
|
"Uptime Kuma URL": "Uptime Kuma URL",
|
||||||
"aboutWebhooks": "Meer info over Webhooks op: {0}",
|
"aboutWebhooks": "Meer info over Webhooks op: {0}",
|
||||||
"aboutChannelName": "Voer de kanaal naam in op {0} Kannaal Naam veld als je het Webhook kanaal wilt omzeilen. Bv: #other-channel",
|
"aboutChannelName": "Voer de kanaal naam in op {0} Kanaal naam veld als je het Webhook kanaal wilt omzeilen. Bijv: #other-channel",
|
||||||
"aboutKumaURL": "Als je de Uptime Kuma URL veld leeg laat, wordt standaard het GitHub project pagina weergegeven.",
|
"aboutKumaURL": "Als je de Uptime Kuma URL veld leeg laat, wordt standaard het GitHub project pagina weergegeven.",
|
||||||
"emojiCheatSheet": "Emoji cheat sheet: {0}",
|
"emojiCheatSheet": "Emoji cheat sheet: {0}",
|
||||||
"PushByTechulus": "Push door Techulus",
|
"PushByTechulus": "Push door Techulus",
|
||||||
|
@ -356,7 +356,6 @@
|
||||||
"Discard": "Weggooien",
|
"Discard": "Weggooien",
|
||||||
"Cancel": "Annuleren",
|
"Cancel": "Annuleren",
|
||||||
"Powered by": "Mogelijk gemaakt door",
|
"Powered by": "Mogelijk gemaakt door",
|
||||||
"shrinkDatabaseDescription": "Activeer database VACUUM voor SQLite. Als de database na 1.10.0 aangemaakt is, dan staat AUTO_VACUUM al aan en is deze actie niet nodig.",
|
|
||||||
"serwersms": "SerwerSMS.pl",
|
"serwersms": "SerwerSMS.pl",
|
||||||
"serwersmsAPIUser": "API Gebruikersnaam (incl. webapi_ prefix)",
|
"serwersmsAPIUser": "API Gebruikersnaam (incl. webapi_ prefix)",
|
||||||
"serwersmsAPIPassword": "API Wachtwoord",
|
"serwersmsAPIPassword": "API Wachtwoord",
|
||||||
|
@ -432,7 +431,7 @@
|
||||||
"Next": "Volgende",
|
"Next": "Volgende",
|
||||||
"The slug is already taken. Please choose another slug.": "De slug is al in gebruik. Kies een andere slug.",
|
"The slug is already taken. Please choose another slug.": "De slug is al in gebruik. Kies een andere slug.",
|
||||||
"No Proxy": "Geen Proxy",
|
"No Proxy": "Geen Proxy",
|
||||||
"HTTP Basic Auth": "HTTP Basic Auth",
|
"HTTP Basic Auth": "HTTP Basis Authenticatie",
|
||||||
"New Status Page": "Nieuwe Status Pagina",
|
"New Status Page": "Nieuwe Status Pagina",
|
||||||
"Page Not Found": "Pagina Niet gevonden",
|
"Page Not Found": "Pagina Niet gevonden",
|
||||||
"Reverse Proxy": "Reverse Proxy",
|
"Reverse Proxy": "Reverse Proxy",
|
||||||
|
@ -529,9 +528,9 @@
|
||||||
"Workstation": "Werkstation",
|
"Workstation": "Werkstation",
|
||||||
"disableCloudflaredNoAuthMsg": "De \"Geen authenticatie\" modus staat aan, wachtwoord is niet vereist.",
|
"disableCloudflaredNoAuthMsg": "De \"Geen authenticatie\" modus staat aan, wachtwoord is niet vereist.",
|
||||||
"backupOutdatedWarning": "Deprecated: Er zijn een hoop nieuwe functies toegevoegd en daarom is de backup functie niet onderhouden, het is op dit moment niet mogelijk om een volledige backup te maken en te herstellen.",
|
"backupOutdatedWarning": "Deprecated: Er zijn een hoop nieuwe functies toegevoegd en daarom is de backup functie niet onderhouden, het is op dit moment niet mogelijk om een volledige backup te maken en te herstellen.",
|
||||||
"RadiusSecret": "Radius Secret",
|
"RadiusSecret": "Radius Geheim",
|
||||||
"RadiusSecretDescription": "Shared Secret tussen client en server",
|
"RadiusSecretDescription": "Shared Secret tussen client en server",
|
||||||
"API Key": "API Key",
|
"API Key": "API Sleutel",
|
||||||
"Connection Type": "Verbindingstype",
|
"Connection Type": "Verbindingstype",
|
||||||
"Docker Daemon": "Docker Daemon",
|
"Docker Daemon": "Docker Daemon",
|
||||||
"Trust Proxy": "Trust Proxy",
|
"Trust Proxy": "Trust Proxy",
|
||||||
|
@ -964,7 +963,7 @@
|
||||||
"threemaRecipientTypeIdentityFormat": "8 Karakters",
|
"threemaRecipientTypeIdentityFormat": "8 Karakters",
|
||||||
"threemaRecipientTypePhone": "Telefoonnummer",
|
"threemaRecipientTypePhone": "Telefoonnummer",
|
||||||
"threemaRecipientTypePhoneFormat": "E.164, zonder voorgaande +",
|
"threemaRecipientTypePhoneFormat": "E.164, zonder voorgaande +",
|
||||||
"threemaSenderIdentityFormat": "8 karakters, begint normaliter met een *",
|
"threemaSenderIdentityFormat": "8 karakters, begint normaal met een *",
|
||||||
"smspartnerPhoneNumber": "Telefoonnummer(s)",
|
"smspartnerPhoneNumber": "Telefoonnummer(s)",
|
||||||
"smspartnerSenderNameInfo": "Moet tussen 3..=11 reguliere karakters zijn",
|
"smspartnerSenderNameInfo": "Moet tussen 3..=11 reguliere karakters zijn",
|
||||||
"Bitrix24 Webhook URL": "Bitrix24 Webhook URL",
|
"Bitrix24 Webhook URL": "Bitrix24 Webhook URL",
|
||||||
|
@ -990,6 +989,57 @@
|
||||||
"wayToGetDiscordThreadId": "Verkrijgen van een thread / forum bericht id is vergelijkbaar met het verkrijgen van een kanaal id. Lees meer over het verkrijgen van ids {0}",
|
"wayToGetDiscordThreadId": "Verkrijgen van een thread / forum bericht id is vergelijkbaar met het verkrijgen van een kanaal id. Lees meer over het verkrijgen van ids {0}",
|
||||||
"threemaRecipientTypeEmail": "E-mailadres",
|
"threemaRecipientTypeEmail": "E-mailadres",
|
||||||
"threemaSenderIdentity": "Gateway ID",
|
"threemaSenderIdentity": "Gateway ID",
|
||||||
"threemaApiAuthenticationSecret": "Gateway ID Secret",
|
"threemaApiAuthenticationSecret": "Gateway ID Geheim",
|
||||||
"smspartnerSenderName": "SMS afzender naam"
|
"smspartnerSenderName": "SMS afzender naam",
|
||||||
|
"and": "en",
|
||||||
|
"snmpCommunityStringHelptext": "Deze string fungeert als een wachtwoord om toegang tot SNMP-apparaten te verifiëren en te beheren. Match het met de configuratie van uw SNMP-apparaat.",
|
||||||
|
"groupOnesenderDesc": "Zorg ervoor dat de GroupID juist is. Om een bericht naar een groep te sturen, bijvoorbeeld: 628123456789-342345",
|
||||||
|
"privateOnesenderDesc": "Zorg ervoor dat het telefoonnummer juist is. Om een bericht te sturen naar een privé telefoonnummer, bijvoorbeeld: 628123456789",
|
||||||
|
"now": "nu",
|
||||||
|
"time ago": "{0} geleden",
|
||||||
|
"-year": "-jaar",
|
||||||
|
"cacheBusterParamDescription": "Willekeurig gegenereerde parameter om caches over te slaan.",
|
||||||
|
"OID (Object Identifier)": "OID (Object indentificatie)",
|
||||||
|
"snmpOIDHelptext": "Voer de OID in voor de sensor of status die u wilt monitoren. Gebruik netwerkbeheertools zoals MIB-browsers of SNMP-software als u niet zeker bent over de OID.",
|
||||||
|
"Condition": "Conditie",
|
||||||
|
"SNMP Version": "SNMP Versie",
|
||||||
|
"Please enter a valid OID.": "Voer a.u.b. een geldige OID in.",
|
||||||
|
"Host Onesender": "Host Onesender",
|
||||||
|
"Token Onesender": "Token Onesender",
|
||||||
|
"Recipient Type": "Ontvanger Type",
|
||||||
|
"Private Number": "Privé Nummer",
|
||||||
|
"Group ID": "Groep ID",
|
||||||
|
"wayToGetOnesenderUrlandToken": "U kunt de URL en Token krijgen door naar de Onesender website te gaan. Meer informatie {0}",
|
||||||
|
"Add Remote Browser": "Externe browser toevoegen",
|
||||||
|
"New Group": "Nieuwe Groep",
|
||||||
|
"Group Name": "Groeps naam",
|
||||||
|
"OAuth2: Client Credentials": "OAuth2: Client referenties",
|
||||||
|
"Authentication Method": "Authenticatie methode",
|
||||||
|
"Authorization Header": "Autorisatie Header",
|
||||||
|
"OAuth Token URL": "OAuth Token URL",
|
||||||
|
"Client ID": "Client ID",
|
||||||
|
"Client Secret": "Client geheim",
|
||||||
|
"Go back to home page.": "Ga terug naar de home pagina.",
|
||||||
|
"No tags found.": "Geen tags gevonden.",
|
||||||
|
"Lost connection to the socket server.": "Verbinding met de socketserver verloren.",
|
||||||
|
"Cannot connect to the socket server.": "Kan geen verbinding maken met de socketserver.",
|
||||||
|
"SIGNL4": "SIGNL4",
|
||||||
|
"SIGNL4 Webhook URL": "SIGNL4 Webhook URL",
|
||||||
|
"signl4Docs": "Meer informatie over het configureren van SIGNL4 en het verkrijgen van de SIGNL4-webhook-URL kunt u hier vinden {0}.",
|
||||||
|
"Conditions": "Voorwaarden",
|
||||||
|
"conditionAdd": "Voorwaarde toevoegen",
|
||||||
|
"conditionDelete": "Voorwaarde verweideren",
|
||||||
|
"conditionAddGroup": "Groep toevoegen",
|
||||||
|
"conditionDeleteGroup": "Groep verweideren",
|
||||||
|
"conditionValuePlaceholder": "Waarde",
|
||||||
|
"contains": "bevat",
|
||||||
|
"not contains": "bevat niet",
|
||||||
|
"starts with": "start met",
|
||||||
|
"not starts with": "start niet met",
|
||||||
|
"ends with": "eindigt met",
|
||||||
|
"not ends with": "eindigt niet met",
|
||||||
|
"less than": "minder dan",
|
||||||
|
"greater than": "meer dan",
|
||||||
|
"record": "dossier",
|
||||||
|
"jsonQueryDescription": "Parseer en haal specifieke gegevens uit de JSON-respons van de server met behulp van JSON-query of gebruik \"$\" voor de onbewerkte respons, als u geen JSON verwacht. Het resultaat wordt vervolgens vergeleken met de verwachte waarde, als strings. Zie {0} voor documentatie en gebruik {1} om te experimenteren met query's."
|
||||||
}
|
}
|
||||||
|
|
106
src/lang/pl.json
106
src/lang/pl.json
|
@ -294,7 +294,7 @@
|
||||||
"emailCustomSubject": "Niestandardowy temat",
|
"emailCustomSubject": "Niestandardowy temat",
|
||||||
"checkPrice": "Sprawdź ceny {0}:",
|
"checkPrice": "Sprawdź ceny {0}:",
|
||||||
"octopushLegacyHint": "Czy używasz starszej wersji Octopush (2011-2020) czy nowej wersji?",
|
"octopushLegacyHint": "Czy używasz starszej wersji Octopush (2011-2020) czy nowej wersji?",
|
||||||
"Feishu WebHookUrl": "Feishu WebHookURL",
|
"Feishu WebHookUrl": "Adres webhooka Feishu",
|
||||||
"matrixHomeserverURL": "Adres URL serwera domowego (z http(s):// i opcjonalnie port)",
|
"matrixHomeserverURL": "Adres URL serwera domowego (z http(s):// i opcjonalnie port)",
|
||||||
"Internal Room Id": "Wewnętrzne ID pokoju",
|
"Internal Room Id": "Wewnętrzne ID pokoju",
|
||||||
"matrixDesc1": "Możesz znaleźć wewnętrzne ID pokoju, patrząc w zaawansowanej sekcji ustawień pokoju w twoim kliencie Matrix. Powinien on wyglądać jak !QMdRCpUIfLwsfjxye6:home.server.",
|
"matrixDesc1": "Możesz znaleźć wewnętrzne ID pokoju, patrząc w zaawansowanej sekcji ustawień pokoju w twoim kliencie Matrix. Powinien on wyglądać jak !QMdRCpUIfLwsfjxye6:home.server.",
|
||||||
|
@ -354,7 +354,6 @@
|
||||||
"Discard": "Odrzuć",
|
"Discard": "Odrzuć",
|
||||||
"Cancel": "Anuluj",
|
"Cancel": "Anuluj",
|
||||||
"Powered by": "Napędzane przez",
|
"Powered by": "Napędzane przez",
|
||||||
"shrinkDatabaseDescription": "Uruchom VACUUM na bazie SQLite. Jeżeli twoja baza została stworzona po wersji 1.10.0, to ma już włączoną opcję AUTO_VACUUM i stosowanie ręcznego oczyszczania nie jest potrzebne.",
|
|
||||||
"clicksendsms": "ClickSend SMS",
|
"clicksendsms": "ClickSend SMS",
|
||||||
"apiCredentials": "Poświadczenia API",
|
"apiCredentials": "Poświadczenia API",
|
||||||
"serwersms": "SerwerSMS.pl",
|
"serwersms": "SerwerSMS.pl",
|
||||||
|
@ -411,7 +410,7 @@
|
||||||
"SignName": "Podpis",
|
"SignName": "Podpis",
|
||||||
"Sms template must contain parameters: ": "Szablon sms musi posiadać parametry: ",
|
"Sms template must contain parameters: ": "Szablon sms musi posiadać parametry: ",
|
||||||
"Bark Endpoint": "Punkt końcowy Bark",
|
"Bark Endpoint": "Punkt końcowy Bark",
|
||||||
"WebHookUrl": "WebHookUrl",
|
"WebHookUrl": "Adres webhooka",
|
||||||
"SecretKey": "Tajny klucz",
|
"SecretKey": "Tajny klucz",
|
||||||
"For safety, must use secret key": "Ze względów bezpieczeństwa musisz użyć tajnego klucza",
|
"For safety, must use secret key": "Ze względów bezpieczeństwa musisz użyć tajnego klucza",
|
||||||
"Device Token": "Token urządzenia",
|
"Device Token": "Token urządzenia",
|
||||||
|
@ -990,5 +989,104 @@
|
||||||
"wayToGetThreemaGateway": "Możesz zarejestrować się w Threema Gateway {0}.",
|
"wayToGetThreemaGateway": "Możesz zarejestrować się w Threema Gateway {0}.",
|
||||||
"threemaSenderIdentityFormat": "8 znaków, zwykle zaczyna się od *",
|
"threemaSenderIdentityFormat": "8 znaków, zwykle zaczyna się od *",
|
||||||
"threemaBasicModeInfo": "Uwaga: Ta integracja korzysta z Threema Gateway w trybie podstawowym (szyfrowanie po stronie serwera). Więcej szczegółów można znaleźć {0}.",
|
"threemaBasicModeInfo": "Uwaga: Ta integracja korzysta z Threema Gateway w trybie podstawowym (szyfrowanie po stronie serwera). Więcej szczegółów można znaleźć {0}.",
|
||||||
"apiKeysDisabledMsg": "Klucze API są wyłączone, ponieważ wyłączone jest uwierzytelnianie."
|
"apiKeysDisabledMsg": "Klucze API są wyłączone, ponieważ wyłączone jest uwierzytelnianie.",
|
||||||
|
"-year": "-rok",
|
||||||
|
"and": "i",
|
||||||
|
"now": "teraz",
|
||||||
|
"cacheBusterParam": "Dodaj parametr {0}",
|
||||||
|
"CopyToClipboardError": "Błąd podczas kopiowania do schowka: {error}",
|
||||||
|
"CurlDebugInfo": "Aby zdebugować monitor, możesz wkleić ten ciąg do terminala na swojej maszynie lub na maszynie gdzie uptime kuma jest uruchomina aby zobaczyć co żądasz.{newiline} Miej na uwadzę różnice sieciowe takich jak {firewalls}, {dns_resolvers} lub {docker_networks}.",
|
||||||
|
"shrinkDatabaseDescriptionSqlite": "Zażądaj {vacuum} bazy dla SQLite. {auto_vacuum} jest już włączone jednak nie defragmentuje to bazy ani nie przepakowuje indywidualnych stron bazy w taki sam sposób jak robi to polecenie {vacuum}.",
|
||||||
|
"cacheBusterParamDescription": "Losowo wygenerowany parametr w celu pominięcia pamięci podręcznej.",
|
||||||
|
"Group Name": "Nazwa grupy",
|
||||||
|
"OAuth2: Client Credentials": "OAuth2: Poświadczenia klienta",
|
||||||
|
"signl4Docs": "Aby zdobyć więcej informacji jak skonfigurować SIGNL4 i jak zdobyć odnośnik webhooka SIGNL4 udaj się do {0}.",
|
||||||
|
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Czasowo wrażliwe powiadomienia zostaną dostarczone natychmiastowo, nawet jeśli urzadzenie jest w trybie nie przeszkadzać.",
|
||||||
|
"time ago": "{0} temu",
|
||||||
|
"Json Query Expression": "Wyrażenie zapytania JSON",
|
||||||
|
"Condition": "Warunek",
|
||||||
|
"snmpCommunityStringHelptext": "Ten ciąg funkcjonuje jako hasło do uwierzytelnienia i kontroli dostęp do urządzeń SNMP. Dopasuj go do konfiguracji urządzenia SNMP.",
|
||||||
|
"SNMP Version": "Wersja SNMP",
|
||||||
|
"OID (Object Identifier)": "OID (Identyfikator obiektu)",
|
||||||
|
"snmpOIDHelptext": "Wprowadź OID sensora lub statusu który chcesz monitorować. Użyj narzędzia do zarządzania siecią jak przeglądarki MIB lub oprogramowanie SNMP jeśli nie masz pewności co do OID.",
|
||||||
|
"Please enter a valid OID.": "Wprowadź poprawny OID.",
|
||||||
|
"Host Onesender": "Serwer Onesender",
|
||||||
|
"Token Onesender": "Token Onesender",
|
||||||
|
"Recipient Type": "Typ odbiorcy",
|
||||||
|
"Go back to home page.": "Powróć do strony domowej.",
|
||||||
|
"No tags found.": "Nie znaleziono etykiet.",
|
||||||
|
"Authorization Header": "Nagłówek autoryzacji",
|
||||||
|
"Form Data Body": "Zawartość formularza danych",
|
||||||
|
"OAuth Token URL": "Odnośnik tokena OAuth",
|
||||||
|
"Client ID": "Identyfikator klienta",
|
||||||
|
"Client Secret": "Sekret klienta",
|
||||||
|
"OAuth Scope": "Zakres OAuth",
|
||||||
|
"Optional: Space separated list of scopes": "Opcjonalne: Oddzielona spacją lista zakresów",
|
||||||
|
"SIGNL4 Webhook URL": "Odnośnik webhooka SIGNL4",
|
||||||
|
"Lost connection to the socket server.": "Utracono połączenie do serwera.",
|
||||||
|
"Cannot connect to the socket server.": "Nie można połączyć z serwerem.",
|
||||||
|
"SIGNL4": "SIGNL4",
|
||||||
|
"less than": "mniej niż",
|
||||||
|
"Conditions": "Warunek",
|
||||||
|
"conditionAdd": "Dodaj warunek",
|
||||||
|
"conditionDelete": "Usuń warunek",
|
||||||
|
"conditionAddGroup": "Dodaj grupę",
|
||||||
|
"conditionDeleteGroup": "Usuń grupę",
|
||||||
|
"conditionValuePlaceholder": "Wartość",
|
||||||
|
"equals": "równa się",
|
||||||
|
"not equals": "nie jest równe",
|
||||||
|
"contains": "zawiera",
|
||||||
|
"not contains": "nie zawiera",
|
||||||
|
"starts with": "zaczyna się od",
|
||||||
|
"not starts with": "nie zaczyna się od",
|
||||||
|
"ends with": "kończy się",
|
||||||
|
"ignoredTLSError": "Błędy TLS/SSL zostały zignorowane",
|
||||||
|
"Debug": "Debuguj",
|
||||||
|
"Copy": "Kopiuj",
|
||||||
|
"CopyToClipboardSuccess": "Skopiowano!",
|
||||||
|
"firewalls": "zapory sieciowe",
|
||||||
|
"dns resolvers": "serwery rozwiązywania nazw domenowych",
|
||||||
|
"docker networks": "sieci docker",
|
||||||
|
"CurlDebugInfoOAuth2CCUnsupported": "Pełen ciąg poświadczeń klienta oauth nie jest obsługiwany w {curl}.{newline}Zdobądź bearer token i przekaż go przez opcję {oauth2_bearer}.",
|
||||||
|
"CurlDebugInfoProxiesUnsupported": "Obsługa proxy w powyższym poleceniu {curl} nie jest aktualnie zaimplementowana.",
|
||||||
|
"Message format": "Format wiadomości",
|
||||||
|
"Send rich messages": "Wysyłaj bogate wiadomości",
|
||||||
|
"Community String": "Ciąg community",
|
||||||
|
"Private Number": "Numer prywatny",
|
||||||
|
"groupOnesenderDesc": "Upewnij się, że GroupID jest poprawne. Aby wysłać wiadomość do grupy, np.: 628123456789-342345",
|
||||||
|
"privateOnesenderDesc": "Upewnij się, że numer telefonu jest poprawny. Aby wysłać wiadomość do prywatnego numeru, np.: 628123456789",
|
||||||
|
"Group ID": "ID grupy",
|
||||||
|
"wayToGetOnesenderUrlandToken": "Aby zdobyć odnośnik i token udaj się na stronę Onesender. Więcej informacji {0}",
|
||||||
|
"Add Remote Browser": "Dodaj zdalną przeglądarkę",
|
||||||
|
"New Group": "Nowa grupa",
|
||||||
|
"Authentication Method": "Metoda uwierzytelnienia",
|
||||||
|
"not ends with": "nie kończy się",
|
||||||
|
"greater than": "więcej niż",
|
||||||
|
"less than or equal to": "mniej lub równe",
|
||||||
|
"greater than or equal to": "więcej lub równe",
|
||||||
|
"record": "rekord",
|
||||||
|
"Notification Channel": "Kanał powiadomień",
|
||||||
|
"Sound": "Dźwięk",
|
||||||
|
"Alphanumerical string and hyphens only": "Tylko ciąg alfanumeryczny i dywiz (kreska)",
|
||||||
|
"Time Sensitive (iOS Only)": "Czasowo wrażliwe (tylko iOS)",
|
||||||
|
"From": "Od",
|
||||||
|
"Can be found on:": "Może być znalezione w: {0}",
|
||||||
|
"The phone number of the recipient in E.164 format.": "Numer telefonu odbiorcy w formacie E.164.",
|
||||||
|
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.": "Identyfikator wysyłającego wiadomość lub numer telefonu w formacie E.164 jeśli chcesz otrzymywać odpowiedzi.",
|
||||||
|
"jsonQueryDescription": "Przetwórz i wyciągnij specyficzne dane z odpowiedzi JSON serwera używając zapytania JSON lub użyj \"$\" dla nieprzetworzonej odpowiedzi, jeśli nie spodziewasz się formatu JSON. Wynik jest następnie porównywany, jako ciągi, do spodziewanej wartości. Po dokumetnację zobacz {0} lub użyj {1} aby poeksperymentować z zapytaniami.",
|
||||||
|
"Custom sound to override default notification sound": "Własny dzwięk nadpisujący domyślny dzwięk powiadomień",
|
||||||
|
"Arcade": "Arcade",
|
||||||
|
"Correct": "Correct",
|
||||||
|
"Fail": "Fail",
|
||||||
|
"Harp": "Harp",
|
||||||
|
"Reveal": "Reveal",
|
||||||
|
"Bubble": "Bubble",
|
||||||
|
"Doorbell": "Doorbell",
|
||||||
|
"Flute": "Flute",
|
||||||
|
"Money": "Money",
|
||||||
|
"Scifi": "Scifi",
|
||||||
|
"Clear": "Clear",
|
||||||
|
"Elevator": "Elevator",
|
||||||
|
"Guitar": "Guitar",
|
||||||
|
"Pop": "Pop"
|
||||||
}
|
}
|
||||||
|
|
|
@ -287,7 +287,6 @@
|
||||||
"Don't know how to get the token? Please read the guide:": "Não sabe com pegar o token? Por favor, leia o guia:",
|
"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)",
|
"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.",
|
"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",
|
"Powered by": "Fornecido por",
|
||||||
"deleteProxyMsg": "Você tem certeza que deseja deletar este proxy para todos os monitores?",
|
"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.",
|
"proxyDescription": "Os proxies devem ser atribuídos a um monitor para funcionar.",
|
||||||
|
@ -958,5 +957,35 @@
|
||||||
"whatHappensAtForumPost": "Criar um novo tópico no fórum. Isto NÃO publica mensagens em tópicos existentes. Para publicar em um tópico existente, use \"{option}\"",
|
"whatHappensAtForumPost": "Criar um novo tópico no fórum. Isto NÃO publica mensagens em tópicos existentes. Para publicar em um tópico existente, use \"{option}\"",
|
||||||
"e.g. {discordThreadID}": "Por exemplo {discordThreadID}",
|
"e.g. {discordThreadID}": "Por exemplo {discordThreadID}",
|
||||||
"receiverInfoSevenIO": "Se o número de destino não estiver localizado na Alemanha, você deve adicionar o código do país antes do número (por exemplo, para o código de país 1 dos EUA, use 117612121212 em vez de 017612121212)",
|
"receiverInfoSevenIO": "Se o número de destino não estiver localizado na Alemanha, você deve adicionar o código do país antes do número (por exemplo, para o código de país 1 dos EUA, use 117612121212 em vez de 017612121212)",
|
||||||
"apiKeysDisabledMsg": "As chaves de API estão desativadas porque a autenticação está desativada."
|
"apiKeysDisabledMsg": "As chaves de API estão desativadas porque a autenticação está desativada.",
|
||||||
|
"now": "agora",
|
||||||
|
"and": "e",
|
||||||
|
"jsonQueryDescription": "Analise e extraia dados específicos da resposta JSON do servidor usando uma query JSON ou use \"$\" para a resposta em raw, se não estiver esperando JSON. O resultado é então comparado ao valor esperado, como strings. Veja {0} para documentação e use {1} para experimentar com queries.",
|
||||||
|
"time ago": "atrás",
|
||||||
|
"-year": "-ano",
|
||||||
|
"Json Query Expression": "Expressão Json Query",
|
||||||
|
"cacheBusterParam": "Adicionar o parâmetro {0}",
|
||||||
|
"snmpCommunityStringHelptext": "Esta string funciona como uma senha para autenticar e controlar o acesso a dispositivos habilitados para SNMP. Combine-a com a configuração do seu dispositivo SNMP.",
|
||||||
|
"OID (Object Identifier)": "OID (Identificador do Objeto)",
|
||||||
|
"Condition": "Condição",
|
||||||
|
"Recipient Type": "Tipo de destinatário",
|
||||||
|
"wayToGetOnesenderUrlandToken": "Você pode obter a URL e o Token acessando o site da Onesender. Mais informações {0}",
|
||||||
|
"cacheBusterParamDescription": "Parâmetro gerado randomicamente para ignorar cache.",
|
||||||
|
"SNMP Version": "Versão SNMP",
|
||||||
|
"Please enter a valid OID.": "Por favor, insira um OID válido.",
|
||||||
|
"Private Number": "Número Privado",
|
||||||
|
"Group ID": "ID do Grupo",
|
||||||
|
"Add Remote Browser": "Adicionar Navegador Remoto",
|
||||||
|
"New Group": "Novo Grupo",
|
||||||
|
"Group Name": "Nome do Grupo",
|
||||||
|
"OAuth2: Client Credentials": "OAuth2: Client Credentials",
|
||||||
|
"Authentication Method": "Método de Autenticação",
|
||||||
|
"Authorization Header": "Header de Autorização",
|
||||||
|
"ignoredTLSError": "Erros TLS/SSL foram ignorados",
|
||||||
|
"Debug": "Depurar",
|
||||||
|
"Copy": "Copiar",
|
||||||
|
"CopyToClipboardError": "Não foi possível copiar para a área de transferência: {error}",
|
||||||
|
"CopyToClipboardSuccess": "Copiado!",
|
||||||
|
"firewalls": "firewalls",
|
||||||
|
"docker networks": "redes docker"
|
||||||
}
|
}
|
||||||
|
|
|
@ -405,7 +405,6 @@
|
||||||
"Please input title and content": "Por favor insira o título e o conteúdo",
|
"Please input title and content": "Por favor insira o título e o conteúdo",
|
||||||
"Hide Tags": "Ocultar Tags",
|
"Hide Tags": "Ocultar Tags",
|
||||||
"Description": "Descrição",
|
"Description": "Descrição",
|
||||||
"shrinkDatabaseDescription": "Acionar banco de dados VACUUM para SQLite. Se seu banco de dados for criado após 1.10.0, AUTO_VACUUM já está ativado e esta ação não é necessária.",
|
|
||||||
"proxyDescription": "Os proxies devem ser atribuídos a um monitor para funcionar.",
|
"proxyDescription": "Os proxies devem ser atribuídos a um monitor para funcionar.",
|
||||||
"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.",
|
"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.",
|
||||||
"Don't know how to get the token? Please read the guide:": "Não sabe como obter o token? Por favor, leia o guia:",
|
"Don't know how to get the token? Please read the guide:": "Não sabe como obter o token? Por favor, leia o guia:",
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue