Merge branch 'master' into issue/1138

# Conflicts:
#	server/server.js
pull/1141/head
Louis Lam 3 years ago
commit 6179f6c982

@ -28,6 +28,8 @@ SECURITY.md
tsconfig.json tsconfig.json
.env .env
/tmp /tmp
/babel.config.js
/ecosystem.config.js
### .gitignore content (commented rules are duplicated) ### .gitignore content (commented rules are duplicated)
@ -42,4 +44,6 @@ dist-ssr
#!/data/.gitkeep #!/data/.gitkeep
#.vscode #.vscode
### End of .gitignore content ### End of .gitignore content

@ -4,10 +4,10 @@ Fixes #(issue)
## Type of change ## Type of change
Please delete options that are not relevant. Please delete any options that are not relevant.
- Bug fix (non-breaking change which fixes an issue) - Bug fix (non-breaking change which fixes an issue)
- User Interface - User interface (UI)
- New feature (non-breaking change which adds functionality) - New feature (non-breaking change which adds functionality)
- Breaking change (fix or feature that would cause existing functionality to not work as expected) - Breaking change (fix or feature that would cause existing functionality to not work as expected)
- Translation update - Translation update
@ -18,7 +18,7 @@ Please delete options that are not relevant.
- [ ] My code follows the style guidelines of this project - [ ] My code follows the style guidelines of this project
- [ ] I ran ESLint and other linters for modified files - [ ] I ran ESLint and other linters for modified files
- [ ] I have performed a self-review of my own code and test it - [ ] I have performed a self-review of my own code and tested it
- [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] My changes generate no new warnings - [ ] My changes generate no new warnings
- [ ] My code needed automated testing. I have added them (this is optional task) - [ ] My code needed automated testing. I have added them (this is optional task)

@ -1,22 +0,0 @@
name: 'Automatically close stale issues and PRs'
on:
schedule:
- cron: '0 0 * * *'
#Run once a day at midnight
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v4
with:
stale-issue-message: 'We are clearing up our old issues and your ticket has been open for 6 months with no activity. Remove stale label or comment or this will be closed in 7 days.'
stale-pr-message: 'We are clearing up our old Pull Requests and yours has been open for 6 months with no activity. Remove stale label or comment or this will be closed in 7 days.'
close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity.'
close-pr-message: 'This PR was closed because it has been stalled for 7 days with no activity.'
days-before-stale: 180
days-before-close: 7
exempt-issue-labels: 'News,Medium,High,discussion,bug,doc,'
exempt-pr-labels: 'awaiting-approval,work-in-progress,enhancement,'
exempt-issue-assignees: 'louislam'
exempt-pr-assignees: 'louislam'

@ -0,0 +1 @@
legacy-peer-deps=true

@ -27,12 +27,25 @@ The frontend code build into "dist" directory. The server (express.js) exposes t
## Can I create a pull request for Uptime Kuma? ## Can I create a pull request for Uptime Kuma?
Generally, if the pull request is working fine, and it does not affect any existing logic, workflow and performance, I will merge into the master branch once it is tested. ⚠️ 2022-03-02 Update:
If you are not sure whether I will accept your pull request, feel free to create an empty pull request draft first. Since I found that merging pull requests is a pretty heavy task for me, I try to rearrange it.
✅ Accept:
- Bug/Security fix
- Translations
- Adding notification providers
❌ Avoid:
- Large pull requests
- New big features
My long story here: https://www.reddit.com/r/UptimeKuma/comments/t1t6or/comment/hynyijx/
### Recommended Pull Request Guideline ### Recommended Pull Request Guideline
Before deep into coding, disscussion first is preferred. Creating an empty pull request for disscussion would be recommended.
1. Fork the project 1. Fork the project
1. Clone your fork repo to local 1. Clone your fork repo to local
1. Create a new branch 1. Create a new branch
@ -42,42 +55,7 @@ If you are not sure whether I will accept your pull request, feel free to create
1. Create a pull request: https://github.com/louislam/uptime-kuma/compare 1. Create a pull request: https://github.com/louislam/uptime-kuma/compare
1. Write a proper description 1. Write a proper description
1. Click "Change to draft" 1. Click "Change to draft"
1. Discussion
### Pull Request Examples
Here are some example situations in the past.
#### ✅ High - Medium Priority
Easy to review, no breaking change and not touching the existing code
- Add a new notification
- Add a chart
- Fix a bug
- Translations
- Add a independent new feature
#### *️⃣ Requires one more reviewer
I do not have such knowledge to test it.
- Add k8s supports
#### ⚠ Low Priority - Harsh Mode
Some pull requests are required to modify the core. To be honest, I do not want anyone to try to do that, because it would spend a lot of your time. I will review your pull request harshly. Also, you may need to write a lot of unit tests to ensure that there is no breaking change.
- Touch large parts of code of any very important features
- Touch monitoring logic
- Drop a table or drop a column for any reason
- Touch the entry point of Docker or Node.js
- Modify auth
#### *️⃣ Low Priority
It changed my current workflow and require further studies.
- Change my release approach
#### ❌ Won't Merge #### ❌ Won't Merge
@ -221,14 +199,13 @@ https://github.com/louislam/uptime-kuma/issues?q=sort%3Aupdated-desc
### Release Procedures ### Release Procedures
1. Draft a release note 1. Draft a release note
1. Make sure the repo is cleared 2. Make sure the repo is cleared
1. `npm run update-version 1.X.X` 3. `npm run release-final with env vars: `VERSION` and `GITHUB_TOKEN`
1. `npm run build` 4. Wait until the `Press any key to continue`
1. `npm run build-docker` 5. `git push`
1. `git push` 6. Publish the release note as 1.X.X
1. Publish the release note as 1.X.X 7. Press any key to continue
1. `npm run upload-artifacts` 8. SSH to demo site server and update to 1.X.X
1. SSH to demo site server and update to 1.X.X
Checking: Checking:
@ -236,6 +213,15 @@ Checking:
- Try the Docker image with tag 1.X.X (Clean install / amd64 / arm64 / armv7) - Try the Docker image with tag 1.X.X (Clean install / amd64 / arm64 / armv7)
- Try clean installation with Node.js - Try clean installation with Node.js
### Release Beta Procedures
1. Draft a release note, check "This is a pre-release"
2. Make sure the repo is cleared
3. `npm run release-beta` with env vars: `VERSION` and `GITHUB_TOKEN`
4. Wait until the `Press any key to continue`
5. Publish the release note as 1.X.X-beta.X
6. Press any key to continue
### Release Wiki ### Release Wiki
#### Setup Repo #### Setup Repo

@ -37,7 +37,6 @@ VPS is sponsored by Uptime Kuma sponsors on [Open Collective](https://opencollec
### 🐳 Docker ### 🐳 Docker
```bash ```bash
docker volume create uptime-kuma
docker run -d --restart=always -p 3001:3001 -v uptime-kuma:/app/data --name uptime-kuma louislam/uptime-kuma:1 docker run -d --restart=always -p 3001:3001 -v uptime-kuma:/app/data --name uptime-kuma louislam/uptime-kuma:1
``` ```
@ -47,7 +46,10 @@ Browse to http://localhost:3001 after starting.
### 💪🏻 Non-Docker ### 💪🏻 Non-Docker
Required Tools: Node.js >= 14, git and pm2. Required Tools:
- [Node.js](https://nodejs.org/en/download/) >= 14
- [Git](https://git-scm.com/downloads)
- [pm2](https://pm2.keymetrics.io/) - For run in background
```bash ```bash
# Update your npm to the latest version # Update your npm to the latest version
@ -61,12 +63,26 @@ npm run setup
node server/server.js node server/server.js
# (Recommended) Option 2. Run in background using PM2 # (Recommended) Option 2. Run in background using PM2
# Install PM2 if you don't have it: npm install pm2 -g # Install PM2 if you don't have it:
npm install pm2 -g && pm2 install pm2-logrotate
# Start Server
pm2 start server/server.js --name uptime-kuma pm2 start server/server.js --name uptime-kuma
```
```
Browse to http://localhost:3001 after starting. Browse to http://localhost:3001 after starting.
More useful PM2 Commands
```bash
# If you want to see the current console output
pm2 monit
# If you want to add it to startup
pm2 save && pm2 startup
```
### Advanced Installation ### Advanced Installation
If you need more options or need to browse via a reverse proxy, please read: If you need more options or need to browse via a reverse proxy, please read:
@ -93,7 +109,7 @@ https://github.com/louislam/uptime-kuma/projects/1
Thank you so much! (GitHub Sponsors will be updated manually. OpenCollective sponsors will be updated automatically, the list will be cached by GitHub though. It may need some time to be updated) Thank you so much! (GitHub Sponsors will be updated manually. OpenCollective sponsors will be updated automatically, the list will be cached by GitHub though. It may need some time to be updated)
<img src="https://uptime.kuma.pet/sponsors?v=3" alt /> <img src="https://uptime.kuma.pet/sponsors?v=6" alt />
## 🖼 More Screenshots ## 🖼 More Screenshots
@ -115,7 +131,7 @@ Telegram Notification Sample:
## Motivation ## Motivation
* I was looking for a self-hosted monitoring tool like "Uptime Robot", but it is hard to find a suitable one. One of the close ones is statping. Unfortunately, it is not stable and unmaintained. * I was looking for a self-hosted monitoring tool like "Uptime Robot", but it is hard to find a suitable one. One of the close ones is statping. Unfortunately, it is not stable and no longer maintained.
* Want to build a fancy UI. * Want to build a fancy UI.
* Learn Vue 3 and vite.js. * Learn Vue 3 and vite.js.
* Show the power of Bootstrap 5. * Show the power of Bootstrap 5.
@ -144,4 +160,4 @@ If you want to translate Uptime Kuma into your language, please read: https://gi
If you want to modify Uptime Kuma, this guideline may be useful for you: https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md If you want to modify Uptime Kuma, this guideline may be useful for you: https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md
English proofreading is needed too because my grammar is not that great, sadly. Feel free to correct my grammar in this README, source code, or wiki. Unfortunately, English proofreading is needed too because my grammar is not that great. Feel free to correct my grammar in this README, source code, or wiki.

@ -0,0 +1,7 @@
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
BEGIN TRANSACTION;
ALTER TABLE monitor
ADD expiry_notification BOOLEAN default 1;
COMMIT;

@ -0,0 +1,23 @@
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
BEGIN TRANSACTION;
CREATE TABLE proxy (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
user_id INT NOT NULL,
protocol VARCHAR(10) NOT NULL,
host VARCHAR(255) NOT NULL,
port SMALLINT NOT NULL,
auth BOOLEAN NOT NULL,
username VARCHAR(255) NULL,
password VARCHAR(255) NULL,
active BOOLEAN NOT NULL DEFAULT 1,
'default' BOOLEAN NOT NULL DEFAULT 0,
created_date DATETIME DEFAULT (DATETIME('now')) NOT NULL
);
ALTER TABLE monitor ADD COLUMN proxy_id INTEGER REFERENCES proxy(id);
CREATE INDEX proxy_id ON monitor (proxy_id);
CREATE INDEX proxy_user_id ON proxy (user_id);
COMMIT;

@ -0,0 +1,31 @@
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
BEGIN TRANSACTION;
CREATE TABLE [status_page](
[id] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
[slug] VARCHAR(255) NOT NULL UNIQUE,
[title] VARCHAR(255) NOT NULL,
[description] TEXT,
[icon] VARCHAR(255) NOT NULL,
[theme] VARCHAR(30) NOT NULL,
[published] BOOLEAN NOT NULL DEFAULT 1,
[search_engine_index] BOOLEAN NOT NULL DEFAULT 1,
[show_tags] BOOLEAN NOT NULL DEFAULT 0,
[password] VARCHAR,
[created_date] DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
[modified_date] DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE UNIQUE INDEX [slug] ON [status_page]([slug]);
CREATE TABLE [status_page_cname](
[id] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
[status_page_id] INTEGER NOT NULL REFERENCES [status_page]([id]) ON DELETE CASCADE ON UPDATE CASCADE,
[domain] VARCHAR NOT NULL UNIQUE
);
ALTER TABLE incident ADD status_page_id INTEGER;
ALTER TABLE [group] ADD status_page_id INTEGER;
COMMIT;

@ -1,8 +1,8 @@
# DON'T UPDATE TO alpine3.13, 1.14, see #41. # DON'T UPDATE TO alpine3.13, 1.14, see #41.
FROM node:14-alpine3.12 FROM node:16-alpine3.12
WORKDIR /app WORKDIR /app
# Install apprise, iputils for non-root ping, setpriv # Install apprise, iputils for non-root ping, setpriv
RUN apk add --no-cache iputils setpriv dumb-init python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib && \ RUN apk add --no-cache iputils setpriv dumb-init python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib && \
pip3 --no-cache-dir install apprise==0.9.6 && \ pip3 --no-cache-dir install apprise==0.9.7 && \
rm -rf /root/.cache rm -rf /root/.cache

@ -1,12 +1,26 @@
# DON'T UPDATE TO node:14-bullseye-slim, see #372. # DON'T UPDATE TO node:14-bullseye-slim, see #372.
# 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:14-buster-slim FROM node:16-buster-slim
ARG TARGETPLATFORM
WORKDIR /app WORKDIR /app
# Install Curl
# Install Apprise, add sqlite3 cli for debugging in the future, iputils-ping for ping, util-linux for setpriv # Install Apprise, add sqlite3 cli for debugging in the future, iputils-ping for ping, util-linux for setpriv
# Stupid python3 and python3-pip actually install a lot of useless things into Debian, specify --no-install-recommends to skip them, make the base even smaller than alpine! # Stupid python3 and python3-pip actually install a lot of useless things into Debian, specify --no-install-recommends to skip them, make the base even smaller than alpine!
RUN apt update && \ RUN apt update && \
apt --yes --no-install-recommends install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \ apt --yes --no-install-recommends install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \
sqlite3 iputils-ping util-linux dumb-init && \ sqlite3 iputils-ping util-linux dumb-init && \
pip3 --no-cache-dir install apprise==0.9.6 && \ pip3 --no-cache-dir install apprise==0.9.7 && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
# Install cloudflared
# dpkg --add-architecture arm: cloudflared do not provide armhf, this is workaround. Read more: https://github.com/cloudflare/cloudflared/issues/583
COPY extra/download-cloudflared.js ./extra/download-cloudflared.js
RUN node ./extra/download-cloudflared.js $TARGETPLATFORM && \
dpkg --add-architecture arm && \
apt update && \
apt --yes --no-install-recommends install ./cloudflared.deb && \
rm -rf /var/lib/apt/lists/* && \
rm -f cloudflared.deb

@ -5,7 +5,7 @@ version: '3.3'
services: services:
uptime-kuma: uptime-kuma:
image: louislam/uptime-kuma image: louislam/uptime-kuma:1
container_name: uptime-kuma container_name: uptime-kuma
volumes: volumes:
- ./uptime-kuma:/app/data - ./uptime-kuma:/app/data

@ -0,0 +1,71 @@
const pkg = require("../../package.json");
const fs = require("fs");
const child_process = require("child_process");
const util = require("../../src/util");
util.polyfill();
const oldVersion = pkg.version;
const version = process.env.VERSION;
console.log("Beta Version: " + version);
if (!version || !version.includes("-beta.")) {
console.error("invalid version, beta version only");
process.exit(1);
}
const exists = tagExists(version);
if (! exists) {
// Process package.json
pkg.version = version;
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n");
commit(version);
tag(version);
} else {
console.log("version tag exists, please delete the tag or use another tag");
process.exit(1);
}
function commit(version) {
let msg = "Update to " + version;
let res = child_process.spawnSync("git", ["commit", "-m", msg, "-a"]);
let stdout = res.stdout.toString().trim();
console.log(stdout);
if (stdout.includes("no changes added to commit")) {
throw new Error("commit error");
}
res = child_process.spawnSync("git", ["push", "origin", "master"]);
console.log(res.stdout.toString().trim());
}
function tag(version) {
let res = child_process.spawnSync("git", ["tag", version]);
console.log(res.stdout.toString().trim());
res = child_process.spawnSync("git", ["push", "origin", version]);
console.log(res.stdout.toString().trim());
}
function tagExists(version) {
if (! version) {
throw new Error("invalid version");
}
let res = child_process.spawnSync("git", ["tag", "-l", version]);
return res.stdout.toString().trim() === version;
}
function safeDelete(dir) {
if (fs.existsSync(dir)) {
fs.rmdirSync(dir, {
recursive: true,
});
}
}

@ -0,0 +1,44 @@
//
const http = require("https"); // or 'https' for https:// URLs
const fs = require("fs");
const platform = process.argv[2];
if (!platform) {
console.error("No platform??");
process.exit(1);
}
let arch = null;
if (platform === "linux/amd64") {
arch = "amd64";
} else if (platform === "linux/arm64") {
arch = "arm64";
} else if (platform === "linux/arm/v7") {
arch = "arm";
} else {
console.error("Invalid platform?? " + platform);
}
const file = fs.createWriteStream("cloudflared.deb");
get("https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-" + arch + ".deb");
function get(url) {
http.get(url, function (res) {
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
console.log("Redirect to " + res.headers.location);
get(res.headers.location);
} else if (res.statusCode >= 200 && res.statusCode < 300) {
res.pipe(file);
res.on("end", function () {
console.log("Downloaded");
});
} else {
console.error(res.statusCode);
process.exit(1);
}
});
}

@ -4,6 +4,7 @@ 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";
@ -21,7 +22,7 @@ function download(url) {
if (fs.existsSync("./dist")) { if (fs.existsSync("./dist")) {
if (fs.existsSync("./dist-backup")) { if (fs.existsSync("./dist-backup")) {
fs.rmdirSync("./dist-backup", { rmSync("./dist-backup", {
recursive: true recursive: true
}); });
} }
@ -35,7 +36,7 @@ function download(url) {
tarStream.on("close", () => { tarStream.on("close", () => {
if (fs.existsSync("./dist-backup")) { if (fs.existsSync("./dist-backup")) {
fs.rmdirSync("./dist-backup", { rmSync("./dist-backup", {
recursive: true recursive: true
}); });
} }

@ -0,0 +1,19 @@
#!/usr/bin/env node
const childProcess = require("child_process");
let env = process.env;
let cmd = process.argv[2];
let args = process.argv.slice(3);
let replacedArgs = [];
for (let arg of args) {
for (let key in env) {
arg = arg.replaceAll(`$${key}`, env[key]);
}
replacedArgs.push(arg);
}
let child = childProcess.spawn(cmd, replacedArgs);
child.stdout.pipe(process.stdout);
child.stderr.pipe(process.stderr);

@ -0,0 +1,23 @@
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 infomation 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`.
*/
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;

@ -20,7 +20,7 @@ if (sslKey && sslCert) {
// Dual-stack support for (::) // Dual-stack support for (::)
let hostname = process.env.UPTIME_KUMA_HOST; let hostname = process.env.UPTIME_KUMA_HOST;
// Also read HOST if not FreeBSD, as HOST is a system environment variable in FreeBSD // Also read HOST if not *BSD, as HOST is a system environment variable in FreeBSD
if (!hostname && !FBSD) { if (!hostname && !FBSD) {
hostname = process.env.HOST; hostname = process.env.HOST;
} }

@ -189,7 +189,7 @@ if (type == "local") {
bash("check=$(pm2 --version)"); bash("check=$(pm2 --version)");
if (check == "") { if (check == "") {
println("Installing PM2"); println("Installing PM2");
bash("npm install pm2 -g"); bash("npm install pm2 -g && pm2 install pm2-logrotate");
bash("pm2 startup"); bash("pm2 startup");
} }

@ -0,0 +1,6 @@
console.log("Git Push and Publish the release note on github, then press any key to continue");
process.stdin.setRawMode(true);
process.stdin.resume();
process.stdin.on("data", process.exit.bind(process, 0));

@ -1,7 +1,5 @@
console.log("== Uptime Kuma Reset Password Tool =="); console.log("== Uptime Kuma Reset Password Tool ==");
console.log("Loading the database");
const Database = require("../server/database"); const Database = require("../server/database");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
const readline = require("readline"); const readline = require("readline");
@ -13,8 +11,9 @@ const rl = readline.createInterface({
}); });
const main = async () => { const main = async () => {
console.log("Connecting the database");
Database.init(args); Database.init(args);
await Database.connect(); await Database.connect(false, false, true);
try { try {
// No need to actually reset the password for testing, just make sure no connection problem. It is ok for now. // No need to actually reset the password for testing, just make sure no connection problem. It is ok for now.

@ -3,6 +3,7 @@
import fs from "fs"; import fs from "fs";
import path from "path"; import path from "path";
import util from "util"; import util from "util";
import rmSync from "../fs-rmSync.js";
// https://stackoverflow.com/questions/13786160/copy-folder-recursively-in-node-js // https://stackoverflow.com/questions/13786160/copy-folder-recursively-in-node-js
/** /**
@ -30,7 +31,7 @@ console.log("Arguments:", process.argv);
const baseLangCode = process.argv[2] || "en"; const baseLangCode = process.argv[2] || "en";
console.log("Base Lang: " + baseLangCode); console.log("Base Lang: " + baseLangCode);
if (fs.existsSync("./languages")) { if (fs.existsSync("./languages")) {
fs.rmdirSync("./languages", { recursive: true }); rmSync("./languages", { recursive: true });
} }
copyRecursiveSync("../../src/languages", "./languages"); copyRecursiveSync("../../src/languages", "./languages");
@ -40,7 +41,7 @@ const files = fs.readdirSync("./languages");
console.log("Files:", files); console.log("Files:", files);
for (const file of files) { for (const file of files) {
if (!file.endsWith(".js")) { if (! file.endsWith(".js")) {
console.log("Skipping " + file); console.log("Skipping " + file);
continue; continue;
} }
@ -82,5 +83,5 @@ for (const file of files) {
fs.writeFileSync(`../../src/languages/${file}`, code); fs.writeFileSync(`../../src/languages/${file}`, code);
} }
fs.rmdirSync("./languages", { recursive: true }); rmSync("./languages", { recursive: true });
console.log("Done. Fixing formatting by ESLint..."); console.log("Done. Fixing formatting by ESLint...");

@ -1,14 +1,13 @@
const pkg = require("../package.json"); const pkg = require("../package.json");
const fs = require("fs"); const fs = require("fs");
const rmSync = require("./fs-rmSync.js");
const child_process = require("child_process"); const child_process = require("child_process");
const util = require("../src/util"); const util = require("../src/util");
util.polyfill(); util.polyfill();
const oldVersion = pkg.version; const newVersion = process.env.VERSION;
const newVersion = process.argv[2];
console.log("Old Version: " + oldVersion);
console.log("New Version: " + newVersion); console.log("New Version: " + newVersion);
if (! newVersion) { if (! newVersion) {
@ -22,23 +21,20 @@ if (! exists) {
// Process package.json // Process package.json
pkg.version = newVersion; pkg.version = newVersion;
pkg.scripts.setup = pkg.scripts.setup.replaceAll(oldVersion, newVersion);
pkg.scripts["build-docker"] = pkg.scripts["build-docker"].replaceAll(oldVersion, newVersion); // Replace the version: https://regex101.com/r/hmj2Bc/1
pkg.scripts["build-docker-alpine"] = pkg.scripts["build-docker-alpine"].replaceAll(oldVersion, newVersion); pkg.scripts.setup = pkg.scripts.setup.replace(/(git checkout )([^\s]+)/, `$1${newVersion}`);
pkg.scripts["build-docker-debian"] = pkg.scripts["build-docker-debian"].replaceAll(oldVersion, newVersion);
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n"); fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n");
commit(newVersion); commit(newVersion);
tag(newVersion); tag(newVersion);
updateWiki(oldVersion, newVersion);
} else { } else {
console.log("version exists"); console.log("version exists");
} }
function commit(version) { function commit(version) {
let msg = "update to " + version; let msg = "Update to " + version;
let res = child_process.spawnSync("git", ["commit", "-m", msg, "-a"]); let res = child_process.spawnSync("git", ["commit", "-m", msg, "-a"]);
let stdout = res.stdout.toString().trim(); let stdout = res.stdout.toString().trim();
@ -63,38 +59,3 @@ function tagExists(version) {
return res.stdout.toString().trim() === version; return res.stdout.toString().trim() === version;
} }
function updateWiki(oldVersion, newVersion) {
const wikiDir = "./tmp/wiki";
const howToUpdateFilename = "./tmp/wiki/🆙-How-to-Update.md";
safeDelete(wikiDir);
child_process.spawnSync("git", ["clone", "https://github.com/louislam/uptime-kuma.wiki.git", wikiDir]);
let content = fs.readFileSync(howToUpdateFilename).toString();
content = content.replaceAll(`git checkout ${oldVersion}`, `git checkout ${newVersion}`);
fs.writeFileSync(howToUpdateFilename, content);
child_process.spawnSync("git", ["add", "-A"], {
cwd: wikiDir,
});
child_process.spawnSync("git", ["commit", "-m", `Update to ${newVersion} from ${oldVersion}`], {
cwd: wikiDir,
});
console.log("Pushing to Github");
child_process.spawnSync("git", ["push"], {
cwd: wikiDir,
});
safeDelete(wikiDir);
}
function safeDelete(dir) {
if (fs.existsSync(dir)) {
fs.rmdirSync(dir, {
recursive: true,
});
}
}

@ -0,0 +1,48 @@
const child_process = require("child_process");
const fs = require("fs");
const newVersion = process.env.VERSION;
if (!newVersion) {
console.log("Missing version");
process.exit(1);
}
updateWiki(newVersion);
function updateWiki(newVersion) {
const wikiDir = "./tmp/wiki";
const howToUpdateFilename = "./tmp/wiki/🆙-How-to-Update.md";
safeDelete(wikiDir);
child_process.spawnSync("git", ["clone", "https://github.com/louislam/uptime-kuma.wiki.git", wikiDir]);
let content = fs.readFileSync(howToUpdateFilename).toString();
// Replace the version: https://regex101.com/r/hmj2Bc/1
content = content.replace(/(git checkout )([^\s]+)/, `$1${newVersion}`);
fs.writeFileSync(howToUpdateFilename, content);
child_process.spawnSync("git", ["add", "-A"], {
cwd: wikiDir,
});
child_process.spawnSync("git", ["commit", "-m", `Update to ${newVersion}`], {
cwd: wikiDir,
});
console.log("Pushing to Github");
child_process.spawnSync("git", ["push"], {
cwd: wikiDir,
});
safeDelete(wikiDir);
}
function safeDelete(dir) {
if (fs.existsSync(dir)) {
fs.rmdirSync(dir, {
recursive: true,
});
}
}

@ -159,7 +159,7 @@ fi
check=$(pm2 --version) check=$(pm2 --version)
if [ "$check" == "" ]; then if [ "$check" == "" ]; then
"echo" "-e" "Installing PM2" "echo" "-e" "Installing PM2"
npm install pm2 -g npm install pm2 -g && pm2 install pm2-logrotate
pm2 startup pm2 startup
fi fi
mkdir -p $installPath mkdir -p $installPath

12596
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
{ {
"name": "uptime-kuma", "name": "uptime-kuma",
"version": "1.11.3", "version": "1.14.0",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
@ -30,15 +30,14 @@
"build-docker": "npm run build && npm run build-docker-debian && npm run build-docker-alpine", "build-docker": "npm run build && npm run build-docker-debian && npm run build-docker-alpine",
"build-docker-alpine-base": "docker buildx build -f docker/alpine-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base-alpine . --push", "build-docker-alpine-base": "docker buildx build -f docker/alpine-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base-alpine . --push",
"build-docker-debian-base": "docker buildx build -f docker/debian-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base-debian . --push", "build-docker-debian-base": "docker buildx build -f docker/debian-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base-debian . --push",
"build-docker-alpine": "docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:1.11.3-alpine --target release . --push", "build-docker-alpine": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:$VERSION-alpine --target release . --push",
"build-docker-debian": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.11.3 -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:1.11.3-debian --target release . --push", "build-docker-debian": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:$VERSION -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:$VERSION-debian --target release . --push",
"build-docker-nightly": "npm run build && docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push", "build-docker-nightly": "npm run build && docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push",
"build-docker-nightly-alpine": "docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly-alpine --target nightly . --push", "build-docker-nightly-alpine": "docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly-alpine --target nightly . --push",
"build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain", "build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --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", "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.11.3 && npm ci --production && npm run download-dist", "setup": "git checkout 1.14.0 && npm ci --production && npm run download-dist",
"download-dist": "node extra/download-dist.js", "download-dist": "node extra/download-dist.js",
"update-version": "node extra/update-version.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",
"remove-2fa": "node extra/remove-2fa.js", "remove-2fa": "node extra/remove-2fa.js",
@ -51,7 +50,10 @@
"simple-dns-server": "node extra/simple-dns-server.js", "simple-dns-server": "node extra/simple-dns-server.js",
"update-language-files-with-base-lang": "cd extra/update-language-files && node index.js %npm_config_base_lang% && eslint ../../src/languages/**.js --fix", "update-language-files-with-base-lang": "cd extra/update-language-files && node index.js %npm_config_base_lang% && eslint ../../src/languages/**.js --fix",
"update-language-files": "cd extra/update-language-files && node index.js && eslint ../../src/languages/**.js --fix", "update-language-files": "cd extra/update-language-files && node index.js && eslint ../../src/languages/**.js --fix",
"ncu-patch": "ncu -u -t patch" "ncu-patch": "npm-check-updates -u -t patch",
"release-final": "node extra/update-version.js && npm run build-docker && node ./extra/press-any-key.js && npm run upload-artifacts && node ./extra/update-wiki-version.js",
"release-beta": "node extra/beta/update-version.js && npm run build && node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:$VERSION -t louislam/uptime-kuma:beta . --target release --push && node ./extra/press-any-key.js && npm run upload-artifacts",
"git-remove-tag": "git tag -d"
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "~1.2.36", "@fortawesome/fontawesome-svg-core": "~1.2.36",
@ -61,61 +63,66 @@
"@louislam/sqlite3": "~6.0.1", "@louislam/sqlite3": "~6.0.1",
"@popperjs/core": "~2.10.2", "@popperjs/core": "~2.10.2",
"args-parser": "~1.3.0", "args-parser": "~1.3.0",
"axios": "~0.21.4", "axios": "~0.26.1",
"bcryptjs": "~2.4.3", "bcryptjs": "~2.4.3",
"bootstrap": "5.1.3", "bootstrap": "5.1.3",
"bree": "~7.1.0", "bree": "~7.1.5",
"chardet": "^1.3.0", "chardet": "^1.3.0",
"chart.js": "~3.6.0", "chart.js": "~3.6.2",
"chartjs-adapter-dayjs": "~1.0.0", "chartjs-adapter-dayjs": "~1.0.0",
"check-password-strength": "^2.0.3", "check-password-strength": "^2.0.5",
"command-exists": "~1.2.9", "command-exists": "~1.2.9",
"compare-versions": "~3.6.0", "compare-versions": "~3.6.0",
"dayjs": "~1.10.7", "dayjs": "~1.10.8",
"express": "~4.17.1", "express": "~4.17.3",
"express-basic-auth": "~1.2.0", "express-basic-auth": "~1.2.1",
"favico.js": "^0.3.10",
"form-data": "~4.0.0", "form-data": "~4.0.0",
"http-graceful-shutdown": "~3.1.5", "http-graceful-shutdown": "~3.1.7",
"http-proxy-agent": "^5.0.0",
"https-proxy-agent": "^5.0.0",
"iconv-lite": "^0.6.3", "iconv-lite": "^0.6.3",
"jsonwebtoken": "~8.5.1", "jsonwebtoken": "~8.5.1",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"limiter": "^2.1.0", "limiter": "^2.1.0",
"node-cloudflared-tunnel": "~1.0.9",
"nodemailer": "~6.6.5", "nodemailer": "~6.6.5",
"notp": "~2.0.3", "notp": "~2.0.3",
"password-hash": "~1.2.2", "password-hash": "~1.2.2",
"postcss-rtlcss": "~3.4.1", "postcss-rtlcss": "~3.4.1",
"postcss-scss": "~4.0.2", "postcss-scss": "~4.0.3",
"prom-client": "~13.2.0", "prom-client": "~13.2.0",
"prometheus-api-metrics": "~3.2.0", "prometheus-api-metrics": "~3.2.1",
"qrcode": "~1.5.0", "qrcode": "~1.5.0",
"redbean-node": "0.1.3", "redbean-node": "0.1.3",
"socket.io": "~4.2.0", "socket.io": "~4.4.1",
"socket.io-client": "~4.2.0", "socket.io-client": "~4.4.1",
"socks-proxy-agent": "^6.1.1",
"tar": "^6.1.11", "tar": "^6.1.11",
"tcp-ping": "~0.1.1", "tcp-ping": "~0.1.1",
"thirty-two": "~1.0.2", "thirty-two": "~1.0.2",
"timezones-list": "~3.0.1", "timezones-list": "~3.0.1",
"v-pagination-3": "~0.1.7", "v-pagination-3": "~0.1.7",
"vue": "next", "vue": "next",
"vue-chart-3": "~0.5.11", "vue-chart-3": "3.0.9",
"vue-confirm-dialog": "~1.0.2", "vue-confirm-dialog": "~1.0.2",
"vue-contenteditable": "~3.0.4", "vue-contenteditable": "~3.0.4",
"vue-i18n": "~9.1.9", "vue-i18n": "~9.1.9",
"vue-image-crop-upload": "~3.0.3", "vue-image-crop-upload": "~3.0.3",
"vue-multiselect": "~3.0.0-alpha.2", "vue-multiselect": "~3.0.0-alpha.2",
"vue-qrcode": "~1.0.0", "vue-qrcode": "~1.0.0",
"vue-router": "~4.0.12", "vue-router": "~4.0.14",
"vue-toastification": "~2.0.0-rc.5", "vue-toastification": "~2.0.0-rc.5",
"vuedraggable": "~4.1.0" "vuedraggable": "~4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@actions/github": "~5.0.0", "@actions/github": "~5.0.1",
"@babel/eslint-parser": "~7.15.8", "@babel/eslint-parser": "~7.15.8",
"@babel/preset-env": "^7.15.8", "@babel/preset-env": "^7.15.8",
"@types/bootstrap": "~5.1.6", "@types/bootstrap": "~5.1.9",
"@vitejs/plugin-legacy": "~1.6.3", "@vitejs/plugin-legacy": "~1.6.4",
"@vitejs/plugin-vue": "~1.9.4", "@vitejs/plugin-vue": "~1.9.4",
"@vue/compiler-sfc": "~3.2.22", "@vue/compiler-sfc": "~3.2.31",
"babel-plugin-rewire": "~1.2.0", "babel-plugin-rewire": "~1.2.0",
"core-js": "~3.18.3", "core-js": "~3.18.3",
"cross-env": "~7.0.3", "cross-env": "~7.0.3",
@ -123,11 +130,12 @@
"eslint": "~7.32.0", "eslint": "~7.32.0",
"eslint-plugin-vue": "~7.18.0", "eslint-plugin-vue": "~7.18.0",
"jest": "~27.2.5", "jest": "~27.2.5",
"jest-puppeteer": "~6.0.0", "jest-puppeteer": "~6.0.3",
"puppeteer": "~10.4.0", "npm-check-updates": "^12.5.5",
"puppeteer": "~13.1.3",
"sass": "~1.42.1", "sass": "~1.42.1",
"stylelint": "~13.13.1", "stylelint": "~14.2.0",
"stylelint-config-standard": "~22.0.0", "stylelint-config-standard": "~24.0.0",
"typescript": "~4.4.4", "typescript": "~4.4.4",
"vite": "~2.6.14" "vite": "~2.6.14"
} }

@ -12,6 +12,10 @@ const { loginRateLimiter } = require("./rate-limiter");
* @returns {Promise<Bean|null>} * @returns {Promise<Bean|null>}
*/ */
exports.login = async function (username, password) { exports.login = async function (username, password) {
if (typeof username !== "string" || typeof password !== "string") {
return null;
}
let user = await R.findOne("user", " username = ? AND active = 1 ", [ let user = await R.findOne("user", " username = ? AND active = 1 ", [
username, username,
]); ]);
@ -31,31 +35,34 @@ exports.login = async function (username, password) {
}; };
function myAuthorizer(username, password, callback) { function myAuthorizer(username, password, callback) {
setting("disableAuth").then((result) => { // Login Rate Limit
if (result) { loginRateLimiter.pass(null, 0).then((pass) => {
callback(null, true); if (pass) {
} else { exports.login(username, password).then((user) => {
// Login Rate Limit callback(null, user != null);
loginRateLimiter.pass(null, 0).then((pass) => {
if (pass) { if (user == null) {
exports.login(username, password).then((user) => { loginRateLimiter.removeTokens(1);
callback(null, user != null);
if (user == null) {
loginRateLimiter.removeTokens(1);
}
});
} else {
callback(null, false);
} }
}); });
} else {
callback(null, false);
} }
}); });
} }
exports.basicAuth = basicAuth({ exports.basicAuth = async function (req, res, next) {
authorizer: myAuthorizer, const middleware = basicAuth({
authorizeAsync: true, authorizer: myAuthorizer,
challenge: true, authorizeAsync: true,
}); challenge: true,
});
const disabledAuth = await setting("disableAuth");
if (!disabledAuth) {
middleware(req, res, next);
} else {
next();
}
};

@ -1,5 +1,6 @@
const { setSetting } = require("./util-server"); const { setSetting, setting } = require("./util-server");
const axios = require("axios"); const axios = require("axios");
const compareVersions = require("compare-versions");
exports.version = require("../package.json").version; exports.version = require("../package.json").version;
exports.latestVersion = null; exports.latestVersion = null;
@ -16,6 +17,19 @@ exports.startInterval = () => {
res.data.slow = "1000.0.0"; res.data.slow = "1000.0.0";
} }
if (await setting("checkUpdate") === false) {
return;
}
let checkBeta = await setting("checkBeta");
if (checkBeta && res.data.beta) {
if (compareVersions.compare(res.data.beta, res.data.beta, ">")) {
exports.latestVersion = res.data.beta;
return;
}
}
if (res.data.slow) { if (res.data.slow) {
exports.latestVersion = res.data.slow; exports.latestVersion = res.data.slow;
} }

@ -83,6 +83,23 @@ async function sendImportantHeartbeatList(socket, monitorID, toUser = false, ove
} }
/**
* Delivers proxy list
*
* @param socket
* @return {Promise<Bean[]>}
*/
async function sendProxyList(socket) {
const timeLogger = new TimeLogger();
const list = await R.find("proxy", " user_id = ? ", [socket.userID]);
io.to(socket.userID).emit("proxyList", list.map(bean => bean.export()));
timeLogger.print("Send Proxy List");
return list;
}
async function sendInfo(socket) { async function sendInfo(socket) {
socket.emit("info", { socket.emit("info", {
version: checkVersion.version, version: checkVersion.version,
@ -95,6 +112,6 @@ module.exports = {
sendNotificationList, sendNotificationList,
sendImportantHeartbeatList, sendImportantHeartbeatList,
sendHeartbeatList, sendHeartbeatList,
sendInfo sendProxyList,
sendInfo,
}; };

@ -53,6 +53,9 @@ class Database {
"patch-2fa-invalidate-used-token.sql": true, "patch-2fa-invalidate-used-token.sql": true,
"patch-notification_sent_history.sql": true, "patch-notification_sent_history.sql": true,
"patch-monitor-basic-auth.sql": true, "patch-monitor-basic-auth.sql": true,
"patch-status-page.sql": true,
"patch-proxy.sql": true,
"patch-monitor-expiry-notification.sql": true,
} }
/** /**
@ -80,7 +83,7 @@ class Database {
console.log(`Data Dir: ${Database.dataDir}`); console.log(`Data Dir: ${Database.dataDir}`);
} }
static async connect(testMode = false) { static async connect(testMode = false, autoloadModels = true, noLog = false) {
const acquireConnectionTimeout = 120 * 1000; const acquireConnectionTimeout = 120 * 1000;
const Dialect = require("knex/lib/dialects/sqlite3/index.js"); const Dialect = require("knex/lib/dialects/sqlite3/index.js");
@ -110,7 +113,10 @@ class Database {
// Auto map the model to a bean object // Auto map the model to a bean object
R.freeze(true); R.freeze(true);
await R.autoloadModels("./server/model");
if (autoloadModels) {
await R.autoloadModels("./server/model");
}
await R.exec("PRAGMA foreign_keys = ON"); await R.exec("PRAGMA foreign_keys = ON");
if (testMode) { if (testMode) {
@ -123,10 +129,17 @@ class Database {
await R.exec("PRAGMA cache_size = -12000"); await R.exec("PRAGMA cache_size = -12000");
await R.exec("PRAGMA auto_vacuum = FULL"); await R.exec("PRAGMA auto_vacuum = FULL");
console.log("SQLite config:"); // This ensures that an operating system crash or power failure will not corrupt the database.
console.log(await R.getAll("PRAGMA journal_mode")); // FULL synchronous is very safe, but it is also slower.
console.log(await R.getAll("PRAGMA cache_size")); // Read more: https://sqlite.org/pragma.html#pragma_synchronous
console.log("SQLite Version: " + await R.getCell("SELECT sqlite_version()")); await R.exec("PRAGMA synchronous = FULL");
if (!noLog) {
console.log("SQLite config:");
console.log(await R.getAll("PRAGMA journal_mode"));
console.log(await R.getAll("PRAGMA cache_size"));
console.log("SQLite Version: " + await R.getCell("SELECT sqlite_version()"));
}
} }
static async patch() { static async patch() {
@ -170,6 +183,7 @@ class Database {
} }
await this.patch2(); await this.patch2();
await this.migrateNewStatusPage();
} }
/** /**
@ -211,6 +225,74 @@ class Database {
await setSetting("databasePatchedFiles", databasePatchedFiles); await setSetting("databasePatchedFiles", databasePatchedFiles);
} }
/**
* Migrate status page value in setting to "status_page" table
* @returns {Promise<void>}
*/
static async migrateNewStatusPage() {
// Fix 1.13.0 empty slug bug
await R.exec("UPDATE status_page SET slug = 'empty-slug-recover' WHERE TRIM(slug) = ''");
let title = await setting("title");
if (title) {
console.log("Migrating Status Page");
let statusPageCheck = await R.findOne("status_page", " slug = 'default' ");
if (statusPageCheck !== null) {
console.log("Migrating Status Page - Skip, default slug record is already existing");
return;
}
let statusPage = R.dispense("status_page");
statusPage.slug = "default";
statusPage.title = title;
statusPage.description = await setting("description");
statusPage.icon = await setting("icon");
statusPage.theme = await setting("statusPageTheme");
statusPage.published = !!await setting("statusPagePublished");
statusPage.search_engine_index = !!await setting("searchEngineIndex");
statusPage.show_tags = !!await setting("statusPageTags");
statusPage.password = null;
if (!statusPage.title) {
statusPage.title = "My Status Page";
}
if (!statusPage.icon) {
statusPage.icon = "";
}
if (!statusPage.theme) {
statusPage.theme = "light";
}
let id = await R.store(statusPage);
await R.exec("UPDATE incident SET status_page_id = ? WHERE status_page_id IS NULL", [
id
]);
await R.exec("UPDATE [group] SET status_page_id = ? WHERE status_page_id IS NULL", [
id
]);
await R.exec("DELETE FROM setting WHERE type = 'statusPage'");
// Migrate Entry Page if it is status page
let entryPage = await setting("entryPage");
if (entryPage === "statusPage") {
await setSetting("entryPage", "statusPage-default", "general");
}
console.log("Migrating Status Page - Done");
}
}
/** /**
* Used it patch2() only * Used it patch2() only
* @param sqlFilename * @param sqlFilename

@ -1,7 +1,7 @@
const path = require("path"); const path = require("path");
const Bree = require("bree"); const Bree = require("bree");
const { SHARE_ENV } = require("worker_threads"); const { SHARE_ENV } = require("worker_threads");
let bree;
const jobs = [ const jobs = [
{ {
name: "clear-old-data", name: "clear-old-data",
@ -10,7 +10,7 @@ const jobs = [
]; ];
const initBackgroundJobs = function (args) { const initBackgroundJobs = function (args) {
const bree = new Bree({ bree = new Bree({
root: path.resolve("server", "jobs"), root: path.resolve("server", "jobs"),
jobs, jobs,
worker: { worker: {
@ -26,6 +26,13 @@ const initBackgroundJobs = function (args) {
return bree; return bree;
}; };
const stopBackgroundJobs = function () {
if (bree) {
bree.stop();
}
};
module.exports = { module.exports = {
initBackgroundJobs initBackgroundJobs,
stopBackgroundJobs
}; };

@ -3,12 +3,12 @@ const { R } = require("redbean-node");
class Group extends BeanModel { class Group extends BeanModel {
async toPublicJSON() { async toPublicJSON(showTags = false) {
let monitorBeanList = await this.getMonitorList(); let monitorBeanList = await this.getMonitorList();
let monitorList = []; let monitorList = [];
for (let bean of monitorBeanList) { for (let bean of monitorBeanList) {
monitorList.push(await bean.toPublicJSON()); monitorList.push(await bean.toPublicJSON(showTags));
} }
return { return {

@ -11,6 +11,7 @@ const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalCli
const { R } = require("redbean-node"); const { R } = require("redbean-node");
const { BeanModel } = require("redbean-node/dist/bean-model"); const { BeanModel } = require("redbean-node/dist/bean-model");
const { Notification } = require("../notification"); const { Notification } = require("../notification");
const { Proxy } = require("../proxy");
const { demoMode } = require("../config"); const { demoMode } = require("../config");
const version = require("../../package.json").version; const version = require("../../package.json").version;
const apicache = require("../modules/apicache"); const apicache = require("../modules/apicache");
@ -24,18 +25,22 @@ const apicache = require("../modules/apicache");
class Monitor extends BeanModel { class Monitor extends BeanModel {
/** /**
* Return a object that ready to parse to JSON for public * Return an object that ready to parse to JSON for public
* Only show necessary data to public * Only show necessary data to public
*/ */
async toPublicJSON() { async toPublicJSON(showTags = false) {
return { let obj = {
id: this.id, id: this.id,
name: this.name, name: this.name,
}; };
if (showTags) {
obj.tags = await this.getTags();
}
return obj;
} }
/** /**
* Return a object that ready to parse to JSON * Return an object that ready to parse to JSON
*/ */
async toJSON() { async toJSON() {
@ -49,7 +54,7 @@ class Monitor extends BeanModel {
notificationIDList[bean.notification_id] = true; notificationIDList[bean.notification_id] = true;
} }
const tags = await R.getAll("SELECT mt.*, tag.name, tag.color FROM monitor_tag mt JOIN tag ON mt.tag_id = tag.id WHERE mt.monitor_id = ?", [this.id]); const tags = await this.getTags();
return { return {
id: this.id, id: this.id,
@ -69,6 +74,7 @@ class Monitor extends BeanModel {
interval: this.interval, interval: this.interval,
retryInterval: this.retryInterval, retryInterval: this.retryInterval,
keyword: this.keyword, keyword: this.keyword,
expiryNotification: this.isEnabledExpiryNotification(),
ignoreTls: this.getIgnoreTls(), ignoreTls: this.getIgnoreTls(),
upsideDown: this.isUpsideDown(), upsideDown: this.isUpsideDown(),
maxredirects: this.maxredirects, maxredirects: this.maxredirects,
@ -77,11 +83,16 @@ class Monitor extends BeanModel {
dns_resolve_server: this.dns_resolve_server, dns_resolve_server: this.dns_resolve_server,
dns_last_result: this.dns_last_result, dns_last_result: this.dns_last_result,
pushToken: this.pushToken, pushToken: this.pushToken,
proxyId: this.proxy_id,
notificationIDList, notificationIDList,
tags: tags, tags: tags,
}; };
} }
async getTags() {
return await R.getAll("SELECT mt.*, tag.name, tag.color FROM monitor_tag mt JOIN tag ON mt.tag_id = tag.id WHERE mt.monitor_id = ?", [this.id]);
}
/** /**
* Encode user and password to Base64 encoding * Encode user and password to Base64 encoding
* for HTTP "basic" auth, as per RFC-7617 * for HTTP "basic" auth, as per RFC-7617
@ -91,6 +102,10 @@ class Monitor extends BeanModel {
return Buffer.from(user + ":" + pass).toString("base64"); return Buffer.from(user + ":" + pass).toString("base64");
} }
isEnabledExpiryNotification() {
return Boolean(this.expiryNotification);
}
/** /**
* Parse to boolean * Parse to boolean
* @returns {boolean} * @returns {boolean}
@ -119,6 +134,19 @@ class Monitor extends BeanModel {
const beat = async () => { const beat = async () => {
let beatInterval = this.interval;
if (! beatInterval) {
beatInterval = 1;
}
if (demoMode) {
if (beatInterval < 20) {
console.log("beat interval too low, reset to 20s");
beatInterval = 20;
}
}
// Expose here for prometheus update // Expose here for prometheus update
// undefined if not https // undefined if not https
let tlsInfo = undefined; let tlsInfo = undefined;
@ -160,6 +188,11 @@ class Monitor extends BeanModel {
}; };
} }
const httpsAgentOptions = {
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
rejectUnauthorized: !this.getIgnoreTls(),
};
debug(`[${this.name}] Prepare Options for axios`); debug(`[${this.name}] Prepare Options for axios`);
const options = { const options = {
@ -173,17 +206,33 @@ class Monitor extends BeanModel {
...(this.headers ? JSON.parse(this.headers) : {}), ...(this.headers ? JSON.parse(this.headers) : {}),
...(basicAuthHeader), ...(basicAuthHeader),
}, },
httpsAgent: new https.Agent({
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
rejectUnauthorized: ! this.getIgnoreTls(),
}),
maxRedirects: this.maxredirects, maxRedirects: this.maxredirects,
validateStatus: (status) => { validateStatus: (status) => {
return checkStatusCode(status, this.getAcceptedStatuscodes()); return checkStatusCode(status, this.getAcceptedStatuscodes());
}, },
}; };
if (this.proxy_id) {
const proxy = await R.load("proxy", this.proxy_id);
if (proxy && proxy.active) {
const { httpAgent, httpsAgent } = Proxy.createAgents(proxy, {
httpsAgentOptions: httpsAgentOptions,
});
options.proxy = false;
options.httpAgent = httpAgent;
options.httpsAgent = httpsAgent;
}
}
if (!options.httpsAgent) {
options.httpsAgent = new https.Agent(httpsAgentOptions);
}
debug(`[${this.name}] Axios Options: ${JSON.stringify(options)}`);
debug(`[${this.name}] Axios Request`); debug(`[${this.name}] Axios Request`);
let res = await axios.request(options); let res = await axios.request(options);
bean.msg = `${res.status} - ${res.statusText}`; bean.msg = `${res.status} - ${res.statusText}`;
bean.ping = dayjs().valueOf() - startTime; bean.ping = dayjs().valueOf() - startTime;
@ -196,7 +245,7 @@ class Monitor extends BeanModel {
let tlsInfoObject = checkCertificate(res); let tlsInfoObject = checkCertificate(res);
tlsInfo = await this.updateTlsInfo(tlsInfoObject); tlsInfo = await this.updateTlsInfo(tlsInfoObject);
if (!this.getIgnoreTls()) { if (!this.getIgnoreTls() && this.isEnabledExpiryNotification()) {
debug(`[${this.name}] call sendCertNotification`); debug(`[${this.name}] call sendCertNotification`);
await this.sendCertNotification(tlsInfoObject); await this.sendCertNotification(tlsInfoObject);
} }
@ -303,7 +352,7 @@ class Monitor extends BeanModel {
} else { } else {
// No need to insert successful heartbeat for push type, so end here // No need to insert successful heartbeat for push type, so end here
retries = 0; retries = 0;
this.heartbeatInterval = setTimeout(beat, this.interval * 1000); this.heartbeatInterval = setTimeout(beat, beatInterval * 1000);
return; return;
} }
@ -377,8 +426,6 @@ class Monitor extends BeanModel {
} }
} }
let beatInterval = this.interval;
debug(`[${this.name}] Check isImportant`); debug(`[${this.name}] Check isImportant`);
let isImportant = Monitor.isImportantBeat(isFirstBeat, previousBeat?.status, bean.status); let isImportant = Monitor.isImportantBeat(isFirstBeat, previousBeat?.status, bean.status);
@ -422,14 +469,6 @@ class Monitor extends BeanModel {
previousBeat = bean; previousBeat = bean;
if (! this.isStop) { if (! this.isStop) {
if (demoMode) {
if (beatInterval < 20) {
console.log("beat interval too low, reset to 20s");
beatInterval = 20;
}
}
debug(`[${this.name}] SetTimeout for next check.`); debug(`[${this.name}] SetTimeout for next check.`);
this.heartbeatInterval = setTimeout(safeBeat, beatInterval * 1000); this.heartbeatInterval = setTimeout(safeBeat, beatInterval * 1000);
} else { } else {
@ -466,6 +505,12 @@ class Monitor extends BeanModel {
stop() { stop() {
clearTimeout(this.heartbeatInterval); clearTimeout(this.heartbeatInterval);
this.isStop = true; this.isStop = true;
this.prometheus().remove();
}
prometheus() {
return new Prometheus(this);
} }
/** /**

@ -0,0 +1,21 @@
const { BeanModel } = require("redbean-node/dist/bean-model");
class Proxy extends BeanModel {
toJSON() {
return {
id: this._id,
userId: this._user_id,
protocol: this._protocol,
host: this._host,
port: this._port,
auth: !!this._auth,
username: this._username,
password: this._password,
active: !!this._active,
default: !!this._default,
createdDate: this._created_date,
};
}
}
module.exports = Proxy;

@ -0,0 +1,126 @@
const { BeanModel } = require("redbean-node/dist/bean-model");
const { R } = require("redbean-node");
class StatusPage extends BeanModel {
static domainMappingList = { };
/**
* Return object like this: { "test-uptime.kuma.pet": "default" }
* @returns {Promise<void>}
*/
static async loadDomainMappingList() {
StatusPage.domainMappingList = await R.getAssoc(`
SELECT domain, slug
FROM status_page, status_page_cname
WHERE status_page.id = status_page_cname.status_page_id
`);
}
static async sendStatusPageList(io, socket) {
let result = {};
let list = await R.findAll("status_page", " ORDER BY title ");
for (let item of list) {
result[item.id] = await item.toJSON();
}
io.to(socket.userID).emit("statusPageList", result);
return list;
}
async updateDomainNameList(domainNameList) {
if (!Array.isArray(domainNameList)) {
throw new Error("Invalid array");
}
let trx = await R.begin();
await trx.exec("DELETE FROM status_page_cname WHERE status_page_id = ?", [
this.id,
]);
try {
for (let domain of domainNameList) {
if (typeof domain !== "string") {
throw new Error("Invalid domain");
}
if (domain.trim() === "") {
continue;
}
// If the domain name is used in another status page, delete it
await trx.exec("DELETE FROM status_page_cname WHERE domain = ?", [
domain,
]);
let mapping = trx.dispense("status_page_cname");
mapping.status_page_id = this.id;
mapping.domain = domain;
await trx.store(mapping);
}
await trx.commit();
} catch (error) {
await trx.rollback();
throw error;
}
}
getDomainNameList() {
let domainList = [];
for (let domain in StatusPage.domainMappingList) {
let s = StatusPage.domainMappingList[domain];
if (this.slug === s) {
domainList.push(domain);
}
}
return domainList;
}
async toJSON() {
return {
id: this.id,
slug: this.slug,
title: this.title,
description: this.description,
icon: this.getIcon(),
theme: this.theme,
published: !!this.published,
showTags: !!this.show_tags,
domainNameList: this.getDomainNameList(),
};
}
async toPublicJSON() {
return {
slug: this.slug,
title: this.title,
description: this.description,
icon: this.getIcon(),
theme: this.theme,
published: !!this.published,
showTags: !!this.show_tags,
};
}
static async slugToID(slug) {
return await R.getCell("SELECT id FROM status_page WHERE slug = ? ", [
slug
]);
}
getIcon() {
if (!this.icon) {
return "/icon.svg";
} else {
return this.icon;
}
}
}
module.exports = StatusPage;

@ -0,0 +1,67 @@
const NotificationProvider = require("./notification-provider");
const { DOWN, UP } = require("../../src/util");
const axios = require("axios");
class Alerta extends NotificationProvider {
name = "alerta";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully.";
try {
let alertaUrl = `${notification.alertaApiEndpoint}`;
let config = {
headers: {
"Content-Type": "application/json;charset=UTF-8",
"Authorization": "Key " + notification.alertaApiKey,
}
};
let data = {
environment: notification.alertaEnvironment,
severity: "critical",
correlate: [],
service: [ "UptimeKuma" ],
value: "Timeout",
tags: [ "uptimekuma" ],
attributes: {},
origin: "uptimekuma",
type: "exceptionAlert",
};
if (heartbeatJSON == null) {
let postData = Object.assign({
event: "msg",
text: msg,
group: "uptimekuma-msg",
resource: "Message",
}, data);
await axios.post(alertaUrl, postData, config);
} else {
let datadup = Object.assign( {
correlate: ["service_up", "service_down"],
event: monitorJSON["type"],
group: "uptimekuma-" + monitorJSON["type"],
resource: monitorJSON["name"],
}, data );
if (heartbeatJSON["status"] == DOWN) {
datadup.severity = notification.alertaAlertState; // critical
datadup.text = "Service " + monitorJSON["type"] + " is down.";
await axios.post(alertaUrl, datadup, config);
} else if (heartbeatJSON["status"] == UP) {
datadup.severity = notification.alertaRecoverState; // cleaned
datadup.text = "Service " + monitorJSON["type"] + " is up.";
await axios.post(alertaUrl, datadup, config);
}
}
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
}
module.exports = Alerta;

@ -0,0 +1,42 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
class Gorush extends NotificationProvider {
name = "gorush";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully.";
let platformMapping = {
"ios": 1,
"android": 2,
"huawei": 3,
};
try {
let data = {
"notifications": [
{
"tokens": [notification.gorushDeviceToken],
"platform": platformMapping[notification.gorushPlatform],
"message": msg,
// Optional
"title": notification.gorushTitle,
"priority": notification.gorushPriority,
"retry": parseInt(notification.gorushRetry) || 0,
"topic": notification.gorushTopic,
}
]
};
let config = {};
await axios.post(`${notification.gorushServerURL}/api/push`, data, config);
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
}
module.exports = Gorush;

@ -15,12 +15,17 @@ class Mattermost extends NotificationProvider {
let mattermostTestData = { let mattermostTestData = {
username: mattermostUserName, username: mattermostUserName,
text: msg, text: msg,
} };
await axios.post(notification.mattermostWebhookUrl, mattermostTestData) await axios.post(notification.mattermostWebhookUrl, mattermostTestData);
return okMsg; return okMsg;
} }
const mattermostChannel = notification.mattermostchannel; let mattermostChannel;
if (typeof notification.mattermostchannel === "string") {
mattermostChannel = notification.mattermostchannel.toLowerCase();
}
const mattermostIconEmoji = notification.mattermosticonemo; const mattermostIconEmoji = notification.mattermosticonemo;
const mattermostIconUrl = notification.mattermosticonurl; const mattermostIconUrl = notification.mattermosticonurl;

@ -9,36 +9,31 @@ class Pushover extends NotificationProvider {
let okMsg = "Sent Successfully."; let okMsg = "Sent Successfully.";
let pushoverlink = "https://api.pushover.net/1/messages.json"; let pushoverlink = "https://api.pushover.net/1/messages.json";
let data = {
"message": "<b>Uptime Kuma Alert</b>\n\n<b>Message</b>:" + msg,
"user": notification.pushoveruserkey,
"token": notification.pushoverapptoken,
"sound": notification.pushoversounds,
"priority": notification.pushoverpriority,
"title": notification.pushovertitle,
"retry": "30",
"expire": "3600",
"html": 1,
};
if (notification.pushoverdevice) {
data.device = notification.pushoverdevice;
}
try { try {
if (heartbeatJSON == null) { if (heartbeatJSON == null) {
let data = { await axios.post(pushoverlink, data);
"message": msg, return okMsg;
"user": notification.pushoveruserkey, } else {
"token": notification.pushoverapptoken, data.message += "\n<b>Time (UTC)</b>:" + heartbeatJSON["time"];
"sound": notification.pushoversounds,
"priority": notification.pushoverpriority,
"title": notification.pushovertitle,
"retry": "30",
"expire": "3600",
"html": 1,
};
await axios.post(pushoverlink, data); await axios.post(pushoverlink, data);
return okMsg; return okMsg;
} }
let data = {
"message": "<b>Uptime Kuma Alert</b>\n\n<b>Message</b>:" + msg + "\n<b>Time (UTC)</b>:" + heartbeatJSON["time"],
"user": notification.pushoveruserkey,
"token": notification.pushoverapptoken,
"sound": notification.pushoversounds,
"priority": notification.pushoverpriority,
"title": notification.pushovertitle,
"retry": "30",
"expire": "3600",
"html": 1,
};
await axios.post(pushoverlink, data);
return okMsg;
} catch (error) { } catch (error) {
this.throwGeneralAxiosError(error); this.throwGeneralAxiosError(error);
} }

@ -0,0 +1,23 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
class TechulusPush extends NotificationProvider {
name = "PushByTechulus";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully.";
try {
await axios.post(`https://push.techulus.com/api/v1/notify/${notification.pushAPIKey}`, {
"title": "Uptime-Kuma",
"body": msg,
})
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error)
}
}
}
module.exports = TechulusPush;

@ -12,6 +12,7 @@ const ClickSendSMS = require("./notification-providers/clicksendsms");
const Pushbullet = require("./notification-providers/pushbullet"); const Pushbullet = require("./notification-providers/pushbullet");
const Pushover = require("./notification-providers/pushover"); const Pushover = require("./notification-providers/pushover");
const Pushy = require("./notification-providers/pushy"); const Pushy = require("./notification-providers/pushy");
const TechulusPush = require("./notification-providers/techulus-push");
const RocketChat = require("./notification-providers/rocket-chat"); const RocketChat = require("./notification-providers/rocket-chat");
const Signal = require("./notification-providers/signal"); const Signal = require("./notification-providers/signal");
const Slack = require("./notification-providers/slack"); const Slack = require("./notification-providers/slack");
@ -27,6 +28,8 @@ const SerwerSMS = require("./notification-providers/serwersms");
const Stackfield = require("./notification-providers/stackfield"); const Stackfield = require("./notification-providers/stackfield");
const WeCom = require("./notification-providers/wecom"); const WeCom = require("./notification-providers/wecom");
const GoogleChat = require("./notification-providers/google-chat"); const GoogleChat = require("./notification-providers/google-chat");
const Gorush = require("./notification-providers/gorush");
const Alerta = require("./notification-providers/alerta");
class Notification { class Notification {
@ -55,6 +58,7 @@ class Notification {
new Pushbullet(), new Pushbullet(),
new Pushover(), new Pushover(),
new Pushy(), new Pushy(),
new TechulusPush(),
new RocketChat(), new RocketChat(),
new Signal(), new Signal(),
new Slack(), new Slack(),
@ -65,7 +69,9 @@ class Notification {
new SerwerSMS(), new SerwerSMS(),
new Stackfield(), new Stackfield(),
new WeCom(), new WeCom(),
new GoogleChat() new GoogleChat(),
new Gorush(),
new Alerta(),
]; ];
for (let item of list) { for (let item of list) {

@ -48,7 +48,7 @@ function Ping(host, options) {
this._args = (options.args) ? options.args : [ "-n", "-t", timeout, "-c", "1", host ]; this._args = (options.args) ? options.args : [ "-n", "-t", timeout, "-c", "1", host ];
this._regmatch = /=([0-9.]+?) ms/; this._regmatch = /=([0-9.]+?) ms/;
} else if (util.FBSD) { } else if (util.BSD) {
this._bin = "/sbin/ping"; this._bin = "/sbin/ping";
const defaultArgs = [ "-n", "-t", timeout, "-c", "1", host ]; const defaultArgs = [ "-n", "-t", timeout, "-c", "1", host ];

@ -60,7 +60,9 @@ class Prometheus {
} }
try { try {
monitor_cert_days_remaining.set(this.monitorLabelValues, tlsInfo.certInfo.daysRemaining); if (tlsInfo.certInfo != null) {
monitor_cert_days_remaining.set(this.monitorLabelValues, tlsInfo.certInfo.daysRemaining);
}
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
@ -84,6 +86,16 @@ class Prometheus {
} }
} }
remove() {
try {
monitor_cert_days_remaining.remove(this.monitorLabelValues);
monitor_cert_is_valid.remove(this.monitorLabelValues);
monitor_response_time.remove(this.monitorLabelValues);
monitor_status.remove(this.monitorLabelValues);
} catch (e) {
console.error(e);
}
}
} }
module.exports = { module.exports = {

@ -0,0 +1,187 @@
const { R } = require("redbean-node");
const HttpProxyAgent = require("http-proxy-agent");
const HttpsProxyAgent = require("https-proxy-agent");
const SocksProxyAgent = require("socks-proxy-agent");
const { debug } = require("../src/util");
const server = require("./server");
class Proxy {
static SUPPORTED_PROXY_PROTOCOLS = ["http", "https", "socks", "socks5", "socks4"]
/**
* Saves and updates given proxy entity
*
* @param proxy
* @param proxyID
* @param userID
* @return {Promise<Bean>}
*/
static async save(proxy, proxyID, userID) {
let bean;
if (proxyID) {
bean = await R.findOne("proxy", " id = ? AND user_id = ? ", [proxyID, userID]);
if (!bean) {
throw new Error("proxy not found");
}
} else {
bean = R.dispense("proxy");
}
// Make sure given proxy protocol is supported
if (!this.SUPPORTED_PROXY_PROTOCOLS.includes(proxy.protocol)) {
throw new Error(`
Unsupported proxy protocol "${proxy.protocol}.
Supported protocols are ${this.SUPPORTED_PROXY_PROTOCOLS.join(", ")}."`
);
}
// When proxy is default update deactivate old default proxy
if (proxy.default) {
await R.exec("UPDATE proxy SET `default` = 0 WHERE `default` = 1");
}
bean.user_id = userID;
bean.protocol = proxy.protocol;
bean.host = proxy.host;
bean.port = proxy.port;
bean.auth = proxy.auth;
bean.username = proxy.username;
bean.password = proxy.password;
bean.active = proxy.active || true;
bean.default = proxy.default || false;
await R.store(bean);
if (proxy.applyExisting) {
await applyProxyEveryMonitor(bean.id, userID);
}
return bean;
}
/**
* Deletes proxy with given id and removes it from monitors
*
* @param proxyID
* @param userID
* @return {Promise<void>}
*/
static async delete(proxyID, userID) {
const bean = await R.findOne("proxy", " id = ? AND user_id = ? ", [proxyID, userID]);
if (!bean) {
throw new Error("proxy not found");
}
// Delete removed proxy from monitors if exists
await R.exec("UPDATE monitor SET proxy_id = null WHERE proxy_id = ?", [proxyID]);
// Delete proxy from list
await R.trash(bean);
}
/**
* Create HTTP and HTTPS agents related with given proxy bean object
*
* @param proxy proxy bean object
* @param options http and https agent options
* @return {{httpAgent: Agent, httpsAgent: Agent}}
*/
static createAgents(proxy, options) {
const { httpAgentOptions, httpsAgentOptions } = options || {};
let agent;
let httpAgent;
let httpsAgent;
const proxyOptions = {
protocol: proxy.protocol,
host: proxy.host,
port: proxy.port,
};
if (proxy.auth) {
proxyOptions.auth = `${proxy.username}:${proxy.password}`;
}
debug(`Proxy Options: ${JSON.stringify(proxyOptions)}`);
debug(`HTTP Agent Options: ${JSON.stringify(httpAgentOptions)}`);
debug(`HTTPS Agent Options: ${JSON.stringify(httpsAgentOptions)}`);
switch (proxy.protocol) {
case "http":
case "https":
httpAgent = new HttpProxyAgent({
...httpAgentOptions || {},
...proxyOptions
});
httpsAgent = new HttpsProxyAgent({
...httpsAgentOptions || {},
...proxyOptions,
});
break;
case "socks":
case "socks5":
case "socks4":
agent = new SocksProxyAgent({
...httpAgentOptions,
...httpsAgentOptions,
...proxyOptions,
});
httpAgent = agent;
httpsAgent = agent;
break;
default: throw new Error(`Unsupported proxy protocol provided. ${proxy.protocol}`);
}
return {
httpAgent,
httpsAgent
};
}
/**
* Reload proxy settings for current monitors
* @returns {Promise<void>}
*/
static async reloadProxy() {
let updatedList = await R.getAssoc("SELECT id, proxy_id FROM monitor");
for (let monitorID in server.monitorList) {
let monitor = server.monitorList[monitorID];
if (updatedList[monitorID]) {
monitor.proxy_id = updatedList[monitorID].proxy_id;
}
}
}
}
/**
* Applies given proxy id to monitors
*
* @param proxyID
* @param userID
* @return {Promise<void>}
*/
async function applyProxyEveryMonitor(proxyID, userID) {
// Find all monitors with id and proxy id
const monitors = await R.getAll("SELECT id, proxy_id FROM monitor WHERE user_id = ?", [userID]);
// Update proxy id not match with given proxy id
for (const monitor of monitors) {
if (monitor.proxy_id !== proxyID) {
await R.exec("UPDATE monitor SET proxy_id = ? WHERE id = ?", [proxyID, monitor.id]);
}
}
}
module.exports = {
Proxy,
};

@ -34,6 +34,14 @@ const loginRateLimiter = new KumaRateLimiter({
errorMessage: "Too frequently, try again later." errorMessage: "Too frequently, try again later."
}); });
const twoFaRateLimiter = new KumaRateLimiter({
tokensPerInterval: 30,
interval: "minute",
fireImmediately: true,
errorMessage: "Too frequently, try again later."
});
module.exports = { module.exports = {
loginRateLimiter loginRateLimiter,
twoFaRateLimiter,
}; };

@ -6,14 +6,25 @@ const apicache = require("../modules/apicache");
const Monitor = require("../model/monitor"); const Monitor = require("../model/monitor");
const dayjs = require("dayjs"); const dayjs = require("dayjs");
const { UP, flipStatus, debug } = require("../../src/util"); const { UP, flipStatus, debug } = require("../../src/util");
const StatusPage = require("../model/status_page");
let router = express.Router(); let router = express.Router();
let cache = apicache.middleware; let cache = apicache.middleware;
let io = server.io; let io = server.io;
router.get("/api/entry-page", async (_, response) => { router.get("/api/entry-page", async (request, response) => {
allowDevAllOrigin(response); allowDevAllOrigin(response);
response.json(server.entryPage);
let result = { };
if (request.hostname in StatusPage.domainMappingList) {
result.type = "statusPageMatchedDomain";
result.statusPageSlug = StatusPage.domainMappingList[request.hostname];
} else {
result.type = "entryPage";
result.entryPage = server.entryPage;
}
response.json(result);
}); });
router.get("/api/push/:pushToken", async (request, response) => { router.get("/api/push/:pushToken", async (request, response) => {
@ -82,110 +93,80 @@ router.get("/api/push/:pushToken", async (request, response) => {
} }
}); });
// Status Page Config // Status page config, incident, monitor list
router.get("/api/status-page/config", async (_request, response) => { router.get("/api/status-page/:slug", cache("5 minutes"), async (request, response) => {
allowDevAllOrigin(response); allowDevAllOrigin(response);
let slug = request.params.slug;
let config = await getSettings("statusPage"); // Get Status Page
let statusPage = await R.findOne("status_page", " slug = ? ", [
if (! config.statusPageTheme) { slug
config.statusPageTheme = "light"; ]);
}
if (! config.statusPagePublished) {
config.statusPagePublished = true;
}
if (! config.statusPageTags) {
config.statusPageTags = false;
}
if (! config.title) { if (!statusPage) {
config.title = "Uptime Kuma"; response.statusCode = 404;
response.json({
msg: "Not Found"
});
return;
} }
response.json(config);
});
// Status Page - Get the current Incident
// Can fetch only if published
router.get("/api/status-page/incident", async (_, response) => {
allowDevAllOrigin(response);
try { try {
await checkPublished(); // Incident
let incident = await R.findOne("incident", " pin = 1 AND active = 1 AND status_page_id = ? ", [
let incident = await R.findOne("incident", " pin = 1 AND active = 1"); statusPage.id,
]);
if (incident) { if (incident) {
incident = incident.toPublicJSON(); incident = incident.toPublicJSON();
} }
response.json({ // Public Group List
ok: true,
incident,
});
} catch (error) {
send403(response, error.message);
}
});
// Status Page - Monitor List
// Can fetch only if published
router.get("/api/status-page/monitor-list", cache("5 minutes"), async (_request, response) => {
allowDevAllOrigin(response);
try {
await checkPublished();
const publicGroupList = []; const publicGroupList = [];
const tagsVisible = (await getSettings("statusPage")).statusPageTags; const showTags = !!statusPage.show_tags;
const list = await R.find("group", " public = 1 ORDER BY weight "); debug("Show Tags???" + showTags);
for (let groupBean of list) { const list = await R.find("group", " public = 1 AND status_page_id = ? ORDER BY weight ", [
let monitorGroup = await groupBean.toPublicJSON(); statusPage.id
if (tagsVisible) { ]);
monitorGroup.monitorList = await Promise.all(monitorGroup.monitorList.map(async (monitor) => {
// Includes tags as an array in response, allows for tags to be displayed on public status page
const tags = await R.getAll(
`SELECT monitor_tag.monitor_id, monitor_tag.value, tag.name, tag.color
FROM monitor_tag
JOIN tag
ON monitor_tag.tag_id = tag.id
WHERE monitor_tag.monitor_id = ?`, [monitor.id]
);
return {
...monitor,
tags: tags
};
}));
}
for (let groupBean of list) {
let monitorGroup = await groupBean.toPublicJSON(showTags);
publicGroupList.push(monitorGroup); publicGroupList.push(monitorGroup);
} }
response.json(publicGroupList); // Response
response.json({
config: await statusPage.toPublicJSON(),
incident,
publicGroupList
});
} catch (error) { } catch (error) {
send403(response, error.message); send403(response, error.message);
} }
}); });
// Status Page Polling Data // Status Page Polling Data
// Can fetch only if published // Can fetch only if published
router.get("/api/status-page/heartbeat", cache("5 minutes"), async (_request, response) => { router.get("/api/status-page/heartbeat/:slug", cache("1 minutes"), async (request, response) => {
allowDevAllOrigin(response); allowDevAllOrigin(response);
try { try {
await checkPublished();
let heartbeatList = {}; let heartbeatList = {};
let uptimeList = {}; let uptimeList = {};
let slug = request.params.slug;
let statusPageID = await StatusPage.slugToID(slug);
let monitorIDList = await R.getCol(` let monitorIDList = await R.getCol(`
SELECT monitor_group.monitor_id FROM monitor_group, \`group\` SELECT monitor_group.monitor_id FROM monitor_group, \`group\`
WHERE monitor_group.group_id = \`group\`.id WHERE monitor_group.group_id = \`group\`.id
AND public = 1 AND public = 1
`); AND \`group\`.status_page_id = ?
`, [
statusPageID
]);
for (let monitorID of monitorIDList) { for (let monitorID of monitorIDList) {
let list = await R.getAll(` let list = await R.getAll(`
@ -214,22 +195,12 @@ router.get("/api/status-page/heartbeat", cache("5 minutes"), async (_request, re
} }
}); });
async function checkPublished() {
if (! await isPublished()) {
throw new Error("The status page is not published");
}
}
/** /**
* Default is published * Default is published
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
async function isPublished() { async function isPublished() {
const value = await setting("statusPagePublished"); return true;
if (value === null) {
return true;
}
return value;
} }
function send403(res, msg = "") { function send403(res, msg = "") {

@ -1,4 +1,15 @@
console.log("Welcome to Uptime Kuma"); console.log("Welcome to Uptime Kuma");
// Check Node.js Version
const nodeVersion = parseInt(process.versions.node.split(".")[0]);
const requiredVersion = 14;
console.log(`Your Node.js version: ${nodeVersion}`);
if (nodeVersion < requiredVersion) {
console.error(`Error: Your Node.js version is not supported, please upgrade to Node.js >= ${requiredVersion}.`);
process.exit(-1);
}
const args = require("args-parser")(process.argv); const args = require("args-parser")(process.argv);
const { sleep, debug, getRandomInt, genSecret } = require("../src/util"); const { sleep, debug, getRandomInt, genSecret } = require("../src/util");
const config = require("./config"); const config = require("./config");
@ -37,22 +48,46 @@ debug("Importing 2FA Modules");
const notp = require("notp"); const notp = require("notp");
const base32 = require("thirty-two"); const base32 = require("thirty-two");
/**
* `module.exports` (alias: `server`) should be inside this class, in order to avoid circular dependency issue.
* @type {UptimeKumaServer}
*/
class UptimeKumaServer {
/**
* Main monitor list
* @type {{}}
*/
monitorList = {};
entryPage = "dashboard";
async sendMonitorList(socket) {
let list = await getMonitorJSONList(socket.userID);
io.to(socket.userID).emit("monitorList", list);
return list;
}
}
const server = module.exports = new UptimeKumaServer();
console.log("Importing this project modules"); console.log("Importing this project modules");
debug("Importing Monitor"); debug("Importing Monitor");
const Monitor = require("./model/monitor"); const Monitor = require("./model/monitor");
debug("Importing Settings"); debug("Importing Settings");
const { getSettings, setSettings, setting, initJWTSecret, checkLogin, startUnitTest, FBSD, errorLog } = require("./util-server"); const { getSettings, setSettings, setting, initJWTSecret, checkLogin, startUnitTest, FBSD, errorLog, doubleCheckPassword } = require("./util-server");
debug("Importing Notification"); debug("Importing Notification");
const { Notification } = require("./notification"); const { Notification } = require("./notification");
Notification.init(); Notification.init();
debug("Importing Proxy");
const { Proxy } = require("./proxy");
debug("Importing Database"); debug("Importing Database");
const Database = require("./database"); const Database = require("./database");
debug("Importing Background Jobs"); debug("Importing Background Jobs");
const { initBackgroundJobs } = require("./jobs"); const { initBackgroundJobs, stopBackgroundJobs } = require("./jobs");
const { loginRateLimiter } = require("./rate-limiter"); const { loginRateLimiter, twoFaRateLimiter } = require("./rate-limiter");
const { basicAuth } = require("./auth"); const { basicAuth } = require("./auth");
const { login } = require("./auth"); const { login } = require("./auth");
@ -79,6 +114,7 @@ const port = [args.port, process.env.UPTIME_KUMA_PORT, process.env.PORT, 3001]
const sslKey = args["ssl-key"] || process.env.UPTIME_KUMA_SSL_KEY || process.env.SSL_KEY || undefined; const sslKey = args["ssl-key"] || process.env.UPTIME_KUMA_SSL_KEY || process.env.SSL_KEY || undefined;
const sslCert = args["ssl-cert"] || process.env.UPTIME_KUMA_SSL_CERT || process.env.SSL_CERT || undefined; const sslCert = args["ssl-cert"] || process.env.UPTIME_KUMA_SSL_CERT || process.env.SSL_CERT || undefined;
const disableFrameSameOrigin = args["disable-frame-sameorigin"] || !!process.env.UPTIME_KUMA_DISABLE_FRAME_SAMEORIGIN || false; const disableFrameSameOrigin = args["disable-frame-sameorigin"] || !!process.env.UPTIME_KUMA_DISABLE_FRAME_SAMEORIGIN || false;
const cloudflaredToken = args["cloudflared-token"] || process.env.UPTIME_KUMA_CLOUDFLARED_TOKEN || undefined;
// 2FA / notp verification defaults // 2FA / notp verification defaults
const twofa_verification_opts = { const twofa_verification_opts = {
@ -99,27 +135,30 @@ if (config.demoMode) {
console.log("Creating express and socket.io instance"); console.log("Creating express and socket.io instance");
const app = express(); const app = express();
let server; let httpServer;
if (sslKey && sslCert) { if (sslKey && sslCert) {
console.log("Server Type: HTTPS"); console.log("Server Type: HTTPS");
server = https.createServer({ httpServer = https.createServer({
key: fs.readFileSync(sslKey), key: fs.readFileSync(sslKey),
cert: fs.readFileSync(sslCert) cert: fs.readFileSync(sslCert)
}, app); }, app);
} else { } else {
console.log("Server Type: HTTP"); console.log("Server Type: HTTP");
server = http.createServer(app); httpServer = http.createServer(app);
} }
const io = new Server(server); const io = new Server(httpServer);
module.exports.io = io; module.exports.io = io;
// Must be after io instantiation // Must be after io instantiation
const { sendNotificationList, sendHeartbeatList, sendImportantHeartbeatList, sendInfo } = require("./client"); const { sendNotificationList, sendHeartbeatList, sendImportantHeartbeatList, sendInfo, sendProxyList } = require("./client");
const { statusPageSocketHandler } = require("./socket-handlers/status-page-socket-handler"); const { statusPageSocketHandler } = require("./socket-handlers/status-page-socket-handler");
const databaseSocketHandler = require("./socket-handlers/database-socket-handler"); const databaseSocketHandler = require("./socket-handlers/database-socket-handler");
const TwoFA = require("./2fa"); const TwoFA = require("./2fa");
const StatusPage = require("./model/status_page");
const { cloudflaredSocketHandler, autoStart: cloudflaredAutoStart, stop: cloudflaredStop } = require("./socket-handlers/cloudflared-socket-handler");
const { proxySocketHandler } = require("./socket-handlers/proxy-socket-handler");
app.use(express.json()); app.use(express.json());
@ -144,12 +183,6 @@ let totalClient = 0;
*/ */
let jwtSecret = null; let jwtSecret = null;
/**
* Main monitor list
* @type {{}}
*/
let monitorList = {};
/** /**
* Show Setup Page * Show Setup Page
* @type {boolean} * @type {boolean}
@ -172,13 +205,12 @@ try {
} }
} }
exports.entryPage = "dashboard";
(async () => { (async () => {
Database.init(args); Database.init(args);
await initDatabase(testMode); await initDatabase(testMode);
exports.entryPage = await setting("entryPage"); exports.entryPage = await setting("entryPage");
await StatusPage.loadDomainMappingList();
console.log("Adding route"); console.log("Adding route");
@ -187,9 +219,14 @@ exports.entryPage = "dashboard";
// *************************** // ***************************
// Entry Page // Entry Page
app.get("/", async (_request, response) => { app.get("/", async (request, response) => {
if (exports.entryPage === "statusPage") { debug(`Request Domain: ${request.hostname}`);
response.redirect("/status");
if (request.hostname in StatusPage.domainMappingList) {
debug("This is a status page domain");
response.send(indexHTML);
} else if (exports.entryPage && exports.entryPage.startsWith("statusPage-")) {
response.redirect("/status/" + exports.entryPage.replace("statusPage-", ""));
} else { } else {
response.redirect("/dashboard"); response.redirect("/dashboard");
} }
@ -292,6 +329,15 @@ exports.entryPage = "dashboard";
socket.on("login", async (data, callback) => { socket.on("login", async (data, callback) => {
console.log("Login"); console.log("Login");
// Checking
if (typeof callback !== "function") {
return;
}
if (!data) {
return;
}
// Login Rate Limit // Login Rate Limit
if (! await loginRateLimiter.pass(callback)) { if (! await loginRateLimiter.pass(callback)) {
return; return;
@ -350,14 +396,27 @@ exports.entryPage = "dashboard";
}); });
socket.on("logout", async (callback) => { socket.on("logout", async (callback) => {
// Rate Limit
if (! await loginRateLimiter.pass(callback)) {
return;
}
socket.leave(socket.userID); socket.leave(socket.userID);
socket.userID = null; socket.userID = null;
callback();
if (typeof callback === "function") {
callback();
}
}); });
socket.on("prepare2FA", async (callback) => { socket.on("prepare2FA", async (currentPassword, callback) => {
try { try {
if (! await twoFaRateLimiter.pass(callback)) {
return;
}
checkLogin(socket); checkLogin(socket);
await doubleCheckPassword(socket, currentPassword);
let user = await R.findOne("user", " id = ? AND active = 1 ", [ let user = await R.findOne("user", " id = ? AND active = 1 ", [
socket.userID, socket.userID,
@ -392,14 +451,19 @@ exports.entryPage = "dashboard";
} catch (error) { } catch (error) {
callback({ callback({
ok: false, ok: false,
msg: "Error while trying to prepare 2FA.", msg: error.message,
}); });
} }
}); });
socket.on("save2FA", async (callback) => { socket.on("save2FA", async (currentPassword, callback) => {
try { try {
if (! await twoFaRateLimiter.pass(callback)) {
return;
}
checkLogin(socket); checkLogin(socket);
await doubleCheckPassword(socket, currentPassword);
await R.exec("UPDATE `user` SET twofa_status = 1 WHERE id = ? ", [ await R.exec("UPDATE `user` SET twofa_status = 1 WHERE id = ? ", [
socket.userID, socket.userID,
@ -412,14 +476,19 @@ exports.entryPage = "dashboard";
} catch (error) { } catch (error) {
callback({ callback({
ok: false, ok: false,
msg: "Error while trying to change 2FA.", msg: error.message,
}); });
} }
}); });
socket.on("disable2FA", async (callback) => { socket.on("disable2FA", async (currentPassword, callback) => {
try { try {
if (! await twoFaRateLimiter.pass(callback)) {
return;
}
checkLogin(socket); checkLogin(socket);
await doubleCheckPassword(socket, currentPassword);
await TwoFA.disable2FA(socket.userID); await TwoFA.disable2FA(socket.userID);
callback({ callback({
@ -429,36 +498,47 @@ exports.entryPage = "dashboard";
} catch (error) { } catch (error) {
callback({ callback({
ok: false, ok: false,
msg: "Error while trying to change 2FA.", msg: error.message,
}); });
} }
}); });
socket.on("verifyToken", async (token, callback) => { socket.on("verifyToken", async (token, currentPassword, callback) => {
let user = await R.findOne("user", " id = ? AND active = 1 ", [ try {
socket.userID, checkLogin(socket);
]); await doubleCheckPassword(socket, currentPassword);
let verify = notp.totp.verify(token, user.twofa_secret, twofa_verification_opts); let user = await R.findOne("user", " id = ? AND active = 1 ", [
socket.userID,
]);
if (user.twofa_last_token !== token && verify) { let verify = notp.totp.verify(token, user.twofa_secret, twofa_verification_opts);
callback({
ok: true, if (user.twofa_last_token !== token && verify) {
valid: true, callback({
}); ok: true,
} else { valid: true,
});
} else {
callback({
ok: false,
msg: "Invalid Token.",
valid: false,
});
}
} catch (error) {
callback({ callback({
ok: false, ok: false,
msg: "Invalid Token.", msg: error.message,
valid: false,
}); });
} }
}); });
socket.on("twoFAStatus", async (callback) => { socket.on("twoFAStatus", async (callback) => {
checkLogin(socket);
try { try {
checkLogin(socket);
let user = await R.findOne("user", " id = ? AND active = 1 ", [ let user = await R.findOne("user", " id = ? AND active = 1 ", [
socket.userID, socket.userID,
]); ]);
@ -475,9 +555,10 @@ exports.entryPage = "dashboard";
}); });
} }
} catch (error) { } catch (error) {
console.log(error);
callback({ callback({
ok: false, ok: false,
msg: "Error while trying to get 2FA status.", msg: error.message,
}); });
} }
}); });
@ -538,7 +619,7 @@ exports.entryPage = "dashboard";
await updateMonitorNotification(bean.id, notificationIDList); await updateMonitorNotification(bean.id, notificationIDList);
await sendMonitorList(socket); await server.sendMonitorList(socket);
await startMonitor(socket.userID, bean.id); await startMonitor(socket.userID, bean.id);
callback({ callback({
@ -566,6 +647,9 @@ exports.entryPage = "dashboard";
throw new Error("Permission denied."); throw new Error("Permission denied.");
} }
// Reset Prometheus labels
server.monitorList[monitor.id]?.prometheus()?.remove();
bean.name = monitor.name; bean.name = monitor.name;
bean.type = monitor.type; bean.type = monitor.type;
bean.url = monitor.url; bean.url = monitor.url;
@ -581,12 +665,14 @@ exports.entryPage = "dashboard";
bean.port = monitor.port; bean.port = monitor.port;
bean.keyword = monitor.keyword; bean.keyword = monitor.keyword;
bean.ignoreTls = monitor.ignoreTls; bean.ignoreTls = monitor.ignoreTls;
bean.expiryNotification = monitor.expiryNotification;
bean.upsideDown = monitor.upsideDown; bean.upsideDown = monitor.upsideDown;
bean.maxredirects = monitor.maxredirects; bean.maxredirects = monitor.maxredirects;
bean.accepted_statuscodes_json = JSON.stringify(monitor.accepted_statuscodes); bean.accepted_statuscodes_json = JSON.stringify(monitor.accepted_statuscodes);
bean.dns_resolve_type = monitor.dns_resolve_type; bean.dns_resolve_type = monitor.dns_resolve_type;
bean.dns_resolve_server = monitor.dns_resolve_server; bean.dns_resolve_server = monitor.dns_resolve_server;
bean.pushToken = monitor.pushToken; bean.pushToken = monitor.pushToken;
bean.proxyId = Number.isInteger(monitor.proxyId) ? monitor.proxyId : null;
await R.store(bean); await R.store(bean);
@ -596,7 +682,7 @@ exports.entryPage = "dashboard";
await restartMonitor(socket.userID, bean.id); await restartMonitor(socket.userID, bean.id);
} }
await sendMonitorList(socket); await server.sendMonitorList(socket);
callback({ callback({
ok: true, ok: true,
@ -616,7 +702,7 @@ exports.entryPage = "dashboard";
socket.on("getMonitorList", async (callback) => { socket.on("getMonitorList", async (callback) => {
try { try {
checkLogin(socket); checkLogin(socket);
await sendMonitorList(socket); await server.sendMonitorList(socket);
callback({ callback({
ok: true, ok: true,
}); });
@ -690,7 +776,7 @@ exports.entryPage = "dashboard";
try { try {
checkLogin(socket); checkLogin(socket);
await startMonitor(socket.userID, monitorID); await startMonitor(socket.userID, monitorID);
await sendMonitorList(socket); await server.sendMonitorList(socket);
callback({ callback({
ok: true, ok: true,
@ -709,7 +795,7 @@ exports.entryPage = "dashboard";
try { try {
checkLogin(socket); checkLogin(socket);
await pauseMonitor(socket.userID, monitorID); await pauseMonitor(socket.userID, monitorID);
await sendMonitorList(socket); await server.sendMonitorList(socket);
callback({ callback({
ok: true, ok: true,
@ -730,9 +816,9 @@ exports.entryPage = "dashboard";
console.log(`Delete Monitor: ${monitorID} User ID: ${socket.userID}`); console.log(`Delete Monitor: ${monitorID} User ID: ${socket.userID}`);
if (monitorID in monitorList) { if (monitorID in server.monitorList) {
monitorList[monitorID].stop(); server.monitorList[monitorID].stop();
delete monitorList[monitorID]; delete server.monitorList[monitorID];
} }
await R.exec("DELETE FROM monitor WHERE id = ? AND user_id = ? ", [ await R.exec("DELETE FROM monitor WHERE id = ? AND user_id = ? ", [
@ -745,7 +831,7 @@ exports.entryPage = "dashboard";
msg: "Deleted Successfully.", msg: "Deleted Successfully.",
}); });
await sendMonitorList(socket); await server.sendMonitorList(socket);
// Clear heartbeat list on client // Clear heartbeat list on client
await sendImportantHeartbeatList(socket, monitorID, true, true); await sendImportantHeartbeatList(socket, monitorID, true, true);
@ -923,21 +1009,13 @@ exports.entryPage = "dashboard";
throw new Error("Password is too weak. It should contain alphabetic and numeric characters. It must be at least 6 characters in length."); throw new Error("Password is too weak. It should contain alphabetic and numeric characters. It must be at least 6 characters in length.");
} }
let user = await R.findOne("user", " id = ? AND active = 1 ", [ let user = await doubleCheckPassword(socket, password.currentPassword);
socket.userID, await user.resetPassword(password.newPassword);
]);
if (user && passwordHash.verify(password.currentPassword, user.password)) {
user.resetPassword(password.newPassword); callback({
ok: true,
callback({ msg: "Password has been updated successfully.",
ok: true, });
msg: "Password has been updated successfully.",
});
} else {
throw new Error("Incorrect current password");
}
} catch (e) { } catch (e) {
callback({ callback({
@ -964,10 +1042,14 @@ exports.entryPage = "dashboard";
} }
}); });
socket.on("setSettings", async (data, callback) => { socket.on("setSettings", async (data, currentPassword, callback) => {
try { try {
checkLogin(socket); checkLogin(socket);
if (data.disableAuth) {
await doubleCheckPassword(socket, currentPassword);
}
await setSettings("general", data); await setSettings("general", data);
exports.entryPage = data.entryPage; exports.entryPage = data.entryPage;
@ -1067,6 +1149,7 @@ exports.entryPage = "dashboard";
console.log(`Importing Backup, User ID: ${socket.userID}, Version: ${backupData.version}`); console.log(`Importing Backup, User ID: ${socket.userID}, Version: ${backupData.version}`);
let notificationListData = backupData.notificationList; let notificationListData = backupData.notificationList;
let proxyListData = backupData.proxyList;
let monitorListData = backupData.monitorList; let monitorListData = backupData.monitorList;
let version17x = compareVersions.compare(backupData.version, "1.7.0", ">="); let version17x = compareVersions.compare(backupData.version, "1.7.0", ">=");
@ -1074,8 +1157,8 @@ exports.entryPage = "dashboard";
// If the import option is "overwrite" it'll clear most of the tables, except "settings" and "user" // If the import option is "overwrite" it'll clear most of the tables, except "settings" and "user"
if (importHandle == "overwrite") { if (importHandle == "overwrite") {
// Stops every monitor first, so it doesn't execute any heartbeat while importing // Stops every monitor first, so it doesn't execute any heartbeat while importing
for (let id in monitorList) { for (let id in server.monitorList) {
let monitor = monitorList[id]; let monitor = server.monitorList[id];
await monitor.stop(); await monitor.stop();
} }
await R.exec("DELETE FROM heartbeat"); await R.exec("DELETE FROM heartbeat");
@ -1085,6 +1168,7 @@ exports.entryPage = "dashboard";
await R.exec("DELETE FROM monitor_tag"); await R.exec("DELETE FROM monitor_tag");
await R.exec("DELETE FROM tag"); await R.exec("DELETE FROM tag");
await R.exec("DELETE FROM monitor"); await R.exec("DELETE FROM monitor");
await R.exec("DELETE FROM proxy");
} }
// Only starts importing if the backup file contains at least one notification // Only starts importing if the backup file contains at least one notification
@ -1104,6 +1188,24 @@ exports.entryPage = "dashboard";
} }
} }
// Only starts importing if the backup file contains at least one proxy
if (proxyListData.length >= 1) {
const proxies = await R.findAll("proxy");
// Loop over proxy list and save proxies
for (const proxy of proxyListData) {
const exists = proxies.find(item => item.id === proxy.id);
// Do not process when proxy already exists in import handle is skip and keep
if (["skip", "keep"].includes(importHandle) && !exists) {
return;
}
// Save proxy as new entry if exists update exists one
await Proxy.save(proxy, exists ? proxy.id : undefined, proxy.userId);
}
}
// Only starts importing if the backup file contains at least one monitor // Only starts importing if the backup file contains at least one monitor
if (monitorListData.length >= 1) { if (monitorListData.length >= 1) {
// Get every existing monitor name and puts them in one simple string // Get every existing monitor name and puts them in one simple string
@ -1153,6 +1255,7 @@ exports.entryPage = "dashboard";
dns_resolve_type: monitorListData[i].dns_resolve_type, dns_resolve_type: monitorListData[i].dns_resolve_type,
dns_resolve_server: monitorListData[i].dns_resolve_server, dns_resolve_server: monitorListData[i].dns_resolve_server,
notificationIDList: {}, notificationIDList: {},
proxy_id: monitorListData[i].proxy_id || null,
}; };
if (monitorListData[i].pushToken) { if (monitorListData[i].pushToken) {
@ -1218,7 +1321,7 @@ exports.entryPage = "dashboard";
} }
await sendNotificationList(socket); await sendNotificationList(socket);
await sendMonitorList(socket); await server.sendMonitorList(socket);
} }
callback({ callback({
@ -1306,7 +1409,9 @@ exports.entryPage = "dashboard";
// Status Page Socket Handler for admin only // Status Page Socket Handler for admin only
statusPageSocketHandler(socket); statusPageSocketHandler(socket);
cloudflaredSocketHandler(socket);
databaseSocketHandler(socket); databaseSocketHandler(socket);
proxySocketHandler(socket);
debug("added all socket handlers"); debug("added all socket handlers");
@ -1327,12 +1432,12 @@ exports.entryPage = "dashboard";
console.log("Init the server"); console.log("Init the server");
server.once("error", async (err) => { httpServer.once("error", async (err) => {
console.error("Cannot listen: " + err.message); console.error("Cannot listen: " + err.message);
await Database.close(); await shutdownFunction();
}); });
server.listen(port, hostname, () => { httpServer.listen(port, hostname, () => {
if (hostname) { if (hostname) {
console.log(`Listening on ${hostname}:${port}`); console.log(`Listening on ${hostname}:${port}`);
} else { } else {
@ -1348,6 +1453,9 @@ exports.entryPage = "dashboard";
initBackgroundJobs(args); initBackgroundJobs(args);
// Start cloudflared at the end if configured
await cloudflaredAutoStart(cloudflaredToken);
})(); })();
async function updateMonitorNotification(monitorID, notificationIDList) { async function updateMonitorNotification(monitorID, notificationIDList) {
@ -1376,21 +1484,18 @@ async function checkOwner(userID, monitorID) {
} }
} }
async function sendMonitorList(socket) {
let list = await getMonitorJSONList(socket.userID);
io.to(socket.userID).emit("monitorList", list);
return list;
}
async function afterLogin(socket, user) { async function afterLogin(socket, user) {
socket.userID = user.id; socket.userID = user.id;
socket.join(user.id); socket.join(user.id);
let monitorList = await sendMonitorList(socket); let monitorList = await server.sendMonitorList(socket);
sendNotificationList(socket); sendNotificationList(socket);
sendProxyList(socket);
await sleep(500); await sleep(500);
await StatusPage.sendStatusPageList(io, socket);
for (let monitorID in monitorList) { for (let monitorID in monitorList) {
await sendHeartbeatList(socket, monitorID); await sendHeartbeatList(socket, monitorID);
} }
@ -1466,11 +1571,11 @@ async function startMonitor(userID, monitorID) {
monitorID, monitorID,
]); ]);
if (monitor.id in monitorList) { if (monitor.id in server.monitorList) {
monitorList[monitor.id].stop(); server.monitorList[monitor.id].stop();
} }
monitorList[monitor.id] = monitor; server.monitorList[monitor.id] = monitor;
monitor.start(io); monitor.start(io);
} }
@ -1488,8 +1593,8 @@ async function pauseMonitor(userID, monitorID) {
userID, userID,
]); ]);
if (monitorID in monitorList) { if (monitorID in server.monitorList) {
monitorList[monitorID].stop(); server.monitorList[monitorID].stop();
} }
} }
@ -1500,7 +1605,7 @@ async function startMonitors() {
let list = await R.find("monitor", " active = 1 "); let list = await R.find("monitor", " active = 1 ");
for (let monitor of list) { for (let monitor of list) {
monitorList[monitor.id] = monitor; server.monitorList[monitor.id] = monitor;
} }
for (let monitor of list) { for (let monitor of list) {
@ -1515,19 +1620,22 @@ async function shutdownFunction(signal) {
console.log("Called signal: " + signal); console.log("Called signal: " + signal);
console.log("Stopping all monitors"); console.log("Stopping all monitors");
for (let id in monitorList) { for (let id in server.monitorList) {
let monitor = monitorList[id]; let monitor = server.monitorList[id];
monitor.stop(); monitor.stop();
} }
await sleep(2000); await sleep(2000);
await Database.close(); await Database.close();
stopBackgroundJobs();
await cloudflaredStop();
} }
function finalFunction() { function finalFunction() {
console.log("Graceful shutdown successful!"); console.log("Graceful shutdown successful!");
} }
gracefulShutdown(server, { gracefulShutdown(httpServer, {
signals: "SIGINT SIGTERM", signals: "SIGINT SIGTERM",
timeout: 30000, // timeout: 30 secs timeout: 30000, // timeout: 30 secs
development: false, // not in dev mode development: false, // not in dev mode

@ -0,0 +1,90 @@
const { checkLogin, setSetting, setting, doubleCheckPassword } = require("../util-server");
const { CloudflaredTunnel } = require("node-cloudflared-tunnel");
const { io } = require("../server");
const prefix = "cloudflared_";
const cloudflared = new CloudflaredTunnel();
cloudflared.change = (running, message) => {
io.to("cloudflared").emit(prefix + "running", running);
io.to("cloudflared").emit(prefix + "message", message);
};
cloudflared.error = (errorMessage) => {
io.to("cloudflared").emit(prefix + "errorMessage", errorMessage);
};
module.exports.cloudflaredSocketHandler = (socket) => {
socket.on(prefix + "join", async () => {
try {
checkLogin(socket);
socket.join("cloudflared");
io.to(socket.userID).emit(prefix + "installed", cloudflared.checkInstalled());
io.to(socket.userID).emit(prefix + "running", cloudflared.running);
io.to(socket.userID).emit(prefix + "token", await setting("cloudflaredTunnelToken"));
} catch (error) { }
});
socket.on(prefix + "leave", async () => {
try {
checkLogin(socket);
socket.leave("cloudflared");
} catch (error) { }
});
socket.on(prefix + "start", async (token) => {
try {
checkLogin(socket);
if (token && typeof token === "string") {
await setSetting("cloudflaredTunnelToken", token);
cloudflared.token = token;
} else {
cloudflared.token = null;
}
cloudflared.start();
} catch (error) { }
});
socket.on(prefix + "stop", async (currentPassword, callback) => {
try {
checkLogin(socket);
await doubleCheckPassword(socket, currentPassword);
cloudflared.stop();
} catch (error) {
callback({
ok: false,
msg: error.message,
});
}
});
socket.on(prefix + "removeToken", async () => {
try {
checkLogin(socket);
await setSetting("cloudflaredTunnelToken", "");
} catch (error) { }
});
};
module.exports.autoStart = async (token) => {
if (!token) {
token = await setting("cloudflaredTunnelToken");
} else {
// Override the current token via args or env var
await setSetting("cloudflaredTunnelToken", token);
console.log("Use cloudflared token from args or env var");
}
if (token) {
console.log("Start cloudflared");
cloudflared.token = token;
cloudflared.start();
}
};
module.exports.stop = async () => {
console.log("Stop cloudflared");
cloudflared.stop();
};

@ -0,0 +1,53 @@
const { checkLogin } = require("../util-server");
const { Proxy } = require("../proxy");
const { sendProxyList } = require("../client");
const server = require("../server");
module.exports.proxySocketHandler = (socket) => {
socket.on("addProxy", async (proxy, proxyID, callback) => {
try {
checkLogin(socket);
const proxyBean = await Proxy.save(proxy, proxyID, socket.userID);
await sendProxyList(socket);
if (proxy.applyExisting) {
await Proxy.reloadProxy();
await server.sendMonitorList(socket);
}
callback({
ok: true,
msg: "Saved",
id: proxyBean.id,
});
} catch (e) {
callback({
ok: false,
msg: e.message,
});
}
});
socket.on("deleteProxy", async (proxyID, callback) => {
try {
checkLogin(socket);
await Proxy.delete(proxyID, socket.userID);
await sendProxyList(socket);
await Proxy.reloadProxy();
callback({
ok: true,
msg: "Deleted",
});
} catch (e) {
callback({
ok: false,
msg: e.message,
});
}
});
};

@ -1,25 +1,36 @@
const { R } = require("redbean-node"); const { R } = require("redbean-node");
const { checkLogin, setSettings } = require("../util-server"); const { checkLogin, setSettings, setSetting } = require("../util-server");
const dayjs = require("dayjs"); const dayjs = require("dayjs");
const { debug } = require("../../src/util"); const { debug } = require("../../src/util");
const ImageDataURI = require("../image-data-uri"); const ImageDataURI = require("../image-data-uri");
const Database = require("../database"); const Database = require("../database");
const apicache = require("../modules/apicache"); const apicache = require("../modules/apicache");
const StatusPage = require("../model/status_page");
const server = require("../server");
module.exports.statusPageSocketHandler = (socket) => { module.exports.statusPageSocketHandler = (socket) => {
// Post or edit incident // Post or edit incident
socket.on("postIncident", async (incident, callback) => { socket.on("postIncident", async (slug, incident, callback) => {
try { try {
checkLogin(socket); checkLogin(socket);
await R.exec("UPDATE incident SET pin = 0 "); let statusPageID = await StatusPage.slugToID(slug);
if (!statusPageID) {
throw new Error("slug is not found");
}
await R.exec("UPDATE incident SET pin = 0 WHERE status_page_id = ? ", [
statusPageID
]);
let incidentBean; let incidentBean;
if (incident.id) { if (incident.id) {
incidentBean = await R.findOne("incident", " id = ?", [ incidentBean = await R.findOne("incident", " id = ? AND status_page_id = ? ", [
incident.id incident.id,
statusPageID
]); ]);
} }
@ -31,6 +42,7 @@ module.exports.statusPageSocketHandler = (socket) => {
incidentBean.content = incident.content; incidentBean.content = incident.content;
incidentBean.style = incident.style; incidentBean.style = incident.style;
incidentBean.pin = true; incidentBean.pin = true;
incidentBean.status_page_id = statusPageID;
if (incident.id) { if (incident.id) {
incidentBean.lastUpdatedDate = R.isoDateTime(dayjs.utc()); incidentBean.lastUpdatedDate = R.isoDateTime(dayjs.utc());
@ -52,14 +64,42 @@ module.exports.statusPageSocketHandler = (socket) => {
} }
}); });
socket.on("unpinIncident", async (callback) => { socket.on("unpinIncident", async (slug, callback) => {
try {
checkLogin(socket);
let statusPageID = await StatusPage.slugToID(slug);
await R.exec("UPDATE incident SET pin = 0 WHERE pin = 1 AND status_page_id = ? ", [
statusPageID
]);
callback({
ok: true,
});
} catch (error) {
callback({
ok: false,
msg: error.message,
});
}
});
socket.on("getStatusPage", async (slug, callback) => {
try { try {
checkLogin(socket); checkLogin(socket);
await R.exec("UPDATE incident SET pin = 0 WHERE pin = 1"); let statusPage = await R.findOne("status_page", " slug = ? ", [
slug
]);
if (!statusPage) {
throw new Error("No slug?");
}
callback({ callback({
ok: true, ok: true,
config: await statusPage.toJSON(),
}); });
} catch (error) { } catch (error) {
callback({ callback({
@ -71,12 +111,20 @@ module.exports.statusPageSocketHandler = (socket) => {
// Save Status Page // Save Status Page
// imgDataUrl Only Accept PNG! // imgDataUrl Only Accept PNG!
socket.on("saveStatusPage", async (config, imgDataUrl, publicGroupList, callback) => { socket.on("saveStatusPage", async (slug, config, imgDataUrl, publicGroupList, callback) => {
try { try {
checkLogin(socket); checkLogin(socket);
apicache.clear(); // Save Config
let statusPage = await R.findOne("status_page", " slug = ? ", [
slug
]);
if (!statusPage) {
throw new Error("No slug?");
}
checkSlug(config.slug);
const header = "data:image/png;base64,"; const header = "data:image/png;base64,";
@ -88,16 +136,31 @@ module.exports.statusPageSocketHandler = (socket) => {
throw new Error("Only allowed PNG logo."); throw new Error("Only allowed PNG logo.");
} }
const filename = `logo${statusPage.id}.png`;
// Convert to file // Convert to file
await ImageDataURI.outputFile(imgDataUrl, Database.uploadDir + "logo.png"); await ImageDataURI.outputFile(imgDataUrl, Database.uploadDir + filename);
config.logo = "/upload/logo.png?t=" + Date.now(); config.logo = `/upload/${filename}?t=` + Date.now();
} else { } else {
config.icon = imgDataUrl; config.icon = imgDataUrl;
} }
// Save Config statusPage.slug = config.slug;
await setSettings("statusPage", config); statusPage.title = config.title;
statusPage.description = config.description;
statusPage.icon = config.logo;
statusPage.theme = config.theme;
//statusPage.published = ;
//statusPage.search_engine_index = ;
statusPage.show_tags = config.showTags;
//statusPage.password = null;
statusPage.modified_date = R.isoDateTime();
await R.store(statusPage);
await statusPage.updateDomainNameList(config.domainNameList);
await StatusPage.loadDomainMappingList();
// Save Public Group List // Save Public Group List
const groupIDList = []; const groupIDList = [];
@ -106,13 +169,15 @@ module.exports.statusPageSocketHandler = (socket) => {
for (let group of publicGroupList) { for (let group of publicGroupList) {
let groupBean; let groupBean;
if (group.id) { if (group.id) {
groupBean = await R.findOne("group", " id = ? AND public = 1 ", [ groupBean = await R.findOne("group", " id = ? AND public = 1 AND status_page_id = ? ", [
group.id group.id,
statusPage.id
]); ]);
} else { } else {
groupBean = R.dispense("group"); groupBean = R.dispense("group");
} }
groupBean.status_page_id = statusPage.id;
groupBean.name = group.name; groupBean.name = group.name;
groupBean.public = true; groupBean.public = true;
groupBean.weight = groupOrder++; groupBean.weight = groupOrder++;
@ -124,7 +189,6 @@ module.exports.statusPageSocketHandler = (socket) => {
]); ]);
let monitorOrder = 1; let monitorOrder = 1;
console.log(group.monitorList);
for (let monitor of group.monitorList) { for (let monitor of group.monitorList) {
let relationBean = R.dispense("monitor_group"); let relationBean = R.dispense("monitor_group");
@ -141,7 +205,20 @@ module.exports.statusPageSocketHandler = (socket) => {
// Delete groups that not in the list // Delete groups that not in the list
debug("Delete groups that not in the list"); debug("Delete groups that not in the list");
const slots = groupIDList.map(() => "?").join(","); const slots = groupIDList.map(() => "?").join(",");
await R.exec(`DELETE FROM \`group\` WHERE id NOT IN (${slots})`, groupIDList);
const data = [
...groupIDList,
statusPage.id
];
await R.exec(`DELETE FROM \`group\` WHERE id NOT IN (${slots}) AND status_page_id = ?`, data);
// 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) {
server.entryPage = "statusPage-" + statusPage.slug;
await setSetting("entryPage", server.entryPage, "general");
}
apicache.clear();
callback({ callback({
ok: true, ok: true,
@ -149,7 +226,7 @@ module.exports.statusPageSocketHandler = (socket) => {
}); });
} catch (error) { } catch (error) {
console.log(error); console.error(error);
callback({ callback({
ok: false, ok: false,
@ -158,4 +235,115 @@ module.exports.statusPageSocketHandler = (socket) => {
} }
}); });
// Add a new status page
socket.on("addStatusPage", async (title, slug, callback) => {
try {
checkLogin(socket);
title = title?.trim();
slug = slug?.trim();
// Check empty
if (!title || !slug) {
throw new Error("Please input all fields");
}
// Make sure slug is string
if (typeof slug !== "string") {
throw new Error("Slug -Accept string only");
}
// lower case only
slug = slug.toLowerCase();
checkSlug(slug);
let statusPage = R.dispense("status_page");
statusPage.slug = slug;
statusPage.title = title;
statusPage.theme = "light";
statusPage.icon = "";
await R.store(statusPage);
callback({
ok: true,
msg: "OK!"
});
} catch (error) {
console.error(error);
callback({
ok: false,
msg: error.message,
});
}
});
// Delete a status page
socket.on("deleteStatusPage", async (slug, callback) => {
try {
checkLogin(socket);
let statusPageID = await StatusPage.slugToID(slug);
if (statusPageID) {
// Reset entry page if it is the default one.
if (server.entryPage === "statusPage-" + slug) {
server.entryPage = "dashboard";
await setSetting("entryPage", server.entryPage, "general");
}
// No need to delete records from `status_page_cname`, because it has cascade foreign key.
// But for incident & group, it is hard to add cascade foreign key during migration, so they have to be deleted manually.
// Delete incident
await R.exec("DELETE FROM incident WHERE status_page_id = ? ", [
statusPageID
]);
// Delete group
await R.exec("DELETE FROM `group` WHERE status_page_id = ? ", [
statusPageID
]);
// Delete status_page
await R.exec("DELETE FROM status_page WHERE id = ? ", [
statusPageID
]);
} else {
throw new Error("Status Page is not found");
}
callback({
ok: true,
});
} catch (error) {
callback({
ok: false,
msg: error.message,
});
}
});
}; };
/**
* Check slug a-z, 0-9, - only
* Regex from: https://stackoverflow.com/questions/22454258/js-regex-string-validation-for-slug
*/
function checkSlug(slug) {
if (typeof slug !== "string") {
throw new Error("Slug must be string");
}
slug = slug.trim();
if (!slug) {
throw new Error("Slug cannot be empty");
}
if (!slug.match(/^[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*$/)) {
throw new Error("Invalid Slug");
}
}

@ -1,9 +1,8 @@
const tcpp = require("tcp-ping"); const tcpp = require("tcp-ping");
const Ping = require("./ping-lite"); const Ping = require("./ping-lite");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
const { debug } = require("../src/util"); const { debug, genSecret } = require("../src/util");
const passwordHash = require("./password-hash"); const passwordHash = require("./password-hash");
const dayjs = require("dayjs");
const { Resolver } = require("dns"); const { Resolver } = require("dns");
const child_process = require("child_process"); const child_process = require("child_process");
const iconv = require("iconv-lite"); const iconv = require("iconv-lite");
@ -16,6 +15,7 @@ exports.WIN = /^win/.test(process.platform);
exports.LIN = /^linux/.test(process.platform); exports.LIN = /^linux/.test(process.platform);
exports.MAC = /^darwin/.test(process.platform); exports.MAC = /^darwin/.test(process.platform);
exports.FBSD = /^freebsd/.test(process.platform); exports.FBSD = /^freebsd/.test(process.platform);
exports.BSD = /bsd$/.test(process.platform);
/** /**
* Init or reset JWT secret * Init or reset JWT secret
@ -31,7 +31,7 @@ exports.initJWTSecret = async () => {
jwtSecretBean.key = "jwtSecret"; jwtSecretBean.key = "jwtSecret";
} }
jwtSecretBean.value = passwordHash.generate(dayjs() + ""); jwtSecretBean.value = passwordHash.generate(genSecret());
await R.store(jwtSecretBean); await R.store(jwtSecretBean);
return jwtSecretBean; return jwtSecretBean;
}; };
@ -320,6 +320,28 @@ exports.checkLogin = (socket) => {
} }
}; };
/**
* For logged-in users, double-check the password
* @param socket
* @param currentPassword
* @returns {Promise<Bean>}
*/
exports.doubleCheckPassword = async (socket, currentPassword) => {
if (typeof currentPassword !== "string") {
throw new Error("Wrong data type?");
}
let user = await R.findOne("user", " id = ? AND active = 1 ", [
socket.userID,
]);
if (!user || !passwordHash.verify(currentPassword, user.password)) {
throw new Error("Incorrect current password");
}
return user;
};
exports.startUnitTest = async () => { exports.startUnitTest = async () => {
console.log("Starting unit test..."); console.log("Starting unit test...");
const npm = /^win/.test(process.platform) ? "npm.cmd" : "npm"; const npm = /^win/.test(process.platform) ? "npm.cmd" : "npm";

@ -22,6 +22,18 @@ textarea.form-control {
width: 10px; width: 10px;
} }
.list-group {
border-radius: 0.75rem;
.dark & {
.list-group-item {
background-color: $dark-bg;
color: $dark-font-color;
border-color: $dark-border-color;
}
}
}
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
background: #ccc; background: #ccc;
border-radius: 20px; border-radius: 20px;
@ -92,6 +104,10 @@ textarea.form-control {
} }
} }
.btn-dark {
background-color: #161B22;
}
@media (max-width: 550px) { @media (max-width: 550px) {
.table-shadow-box { .table-shadow-box {
padding: 10px !important; padding: 10px !important;
@ -144,6 +160,10 @@ textarea.form-control {
background-color: #090c10; background-color: #090c10;
color: $dark-font-color; color: $dark-font-color;
mark, .mark {
background-color: #b6ad86;
}
&::-webkit-scrollbar-thumb, ::-webkit-scrollbar-thumb { &::-webkit-scrollbar-thumb, ::-webkit-scrollbar-thumb {
background: $dark-border-color; background: $dark-border-color;
} }
@ -156,13 +176,24 @@ textarea.form-control {
.form-check-input { .form-check-input {
background-color: $dark-bg2; background-color: $dark-bg2;
border-color: $dark-border-color;
}
.input-group-text {
background-color: #282f39;
border-color: $dark-border-color;
color: $dark-font-color;
}
.form-check-input:checked {
border-color: $primary; // Re-apply bootstrap border
} }
.form-switch .form-check-input { .form-switch .form-check-input {
background-color: #232f3b; background-color: #232f3b;
} }
a, a:not(.btn),
.table, .table,
.nav-link { .nav-link {
color: $dark-font-color; color: $dark-font-color;
@ -329,11 +360,8 @@ textarea.form-control {
.monitor-list { .monitor-list {
&.scrollbar { &.scrollbar {
min-height: calc(100vh - 240px);
max-height: calc(100vh - 30px);
overflow-y: auto; overflow-y: auto;
position: sticky; height: calc(100% - 65px);
top: 10px;
} }
.item { .item {
@ -396,6 +424,10 @@ textarea.form-control {
background-color: rgba(239, 239, 239, 0.7); background-color: rgba(239, 239, 239, 0.7);
border-radius: 8px; border-radius: 8px;
&.no-bg {
background-color: transparent !important;
}
&:focus { &:focus {
outline: 0 solid #eee; outline: 0 solid #eee;
background-color: rgba(245, 245, 245, 0.9); background-color: rgba(245, 245, 245, 0.9);
@ -433,6 +465,10 @@ textarea.form-control {
border-radius: 10px !important; border-radius: 10px !important;
} }
.spinner {
color: $primary;
}
// Localization // Localization
@import "localization.scss"; @import "localization.scss";

@ -11,23 +11,23 @@
<table class="text-start"> <table class="text-start">
<tbody> <tbody>
<tr class="my-3"> <tr class="my-3">
<td class="px-3">Subject:</td> <td class="px-3">{{ $t("Subject:") }}</td>
<td>{{ formatSubject(cert.subject) }}</td> <td>{{ formatSubject(cert.subject) }}</td>
</tr> </tr>
<tr class="my-3"> <tr class="my-3">
<td class="px-3">Valid To:</td> <td class="px-3">{{ $t("Valid To:") }}</td>
<td><Datetime :value="cert.validTo" /></td> <td><Datetime :value="cert.validTo" /></td>
</tr> </tr>
<tr class="my-3"> <tr class="my-3">
<td class="px-3">Days Remaining:</td> <td class="px-3">{{ $t("Days Remaining:") }}</td>
<td>{{ cert.daysRemaining }}</td> <td>{{ cert.daysRemaining }}</td>
</tr> </tr>
<tr class="my-3"> <tr class="my-3">
<td class="px-3">Issuer:</td> <td class="px-3">{{ $t("Issuer:") }}</td>
<td>{{ formatSubject(cert.issuer) }}</td> <td>{{ formatSubject(cert.issuer) }}</td>
</tr> </tr>
<tr class="my-3"> <tr class="my-3">
<td class="px-3">Fingerprint:</td> <td class="px-3">{{ $t("Fingerprint:") }}</td>
<td>{{ cert.fingerprint }}</td> <td>{{ cert.fingerprint }}</td>
</tr> </tr>
</tbody> </tbody>

@ -16,8 +16,8 @@
<div v-if="tokenRequired"> <div v-if="tokenRequired">
<div class="form-floating mt-3"> <div class="form-floating mt-3">
<input id="floatingToken" v-model="token" type="text" maxlength="6" class="form-control" placeholder="123456"> <input id="otp" v-model="token" type="text" maxlength="6" class="form-control" placeholder="123456">
<label for="floatingToken">{{ $t("Token") }}</label> <label for="otp">{{ $t("Token") }}</label>
</div> </div>
</div> </div>

@ -1,5 +1,5 @@
<template> <template>
<div class="shadow-box mb-3"> <div class="shadow-box mb-3" :style="boxStyle">
<div class="list-header"> <div class="list-header">
<div class="placeholder"></div> <div class="placeholder"></div>
<div class="search-wrapper"> <div class="search-wrapper">
@ -9,7 +9,9 @@
<a v-if="searchText != ''" class="search-icon" @click="clearSearchText"> <a v-if="searchText != ''" class="search-icon" @click="clearSearchText">
<font-awesome-icon icon="times" /> <font-awesome-icon icon="times" />
</a> </a>
<input v-model="searchText" class="form-control search-input" :placeholder="$t('Search...')" /> <form>
<input v-model="searchText" class="form-control search-input" :placeholder="$t('Search...')" autocomplete="off" />
</form>
</div> </div>
</div> </div>
<div class="monitor-list" :class="{ scrollbar: scrollbar }"> <div class="monitor-list" :class="{ scrollbar: scrollbar }">
@ -34,7 +36,7 @@
</div> </div>
<div v-if="$root.userHeartbeatBar == 'bottom'" class="row"> <div v-if="$root.userHeartbeatBar == 'bottom'" class="row">
<div class="col-12"> <div class="col-12 bottom-style">
<HeartbeatBar size="small" :monitor-id="item.id" /> <HeartbeatBar size="small" :monitor-id="item.id" />
</div> </div>
</div> </div>
@ -63,9 +65,16 @@ export default {
data() { data() {
return { return {
searchText: "", searchText: "",
windowTop: 0,
}; };
}, },
computed: { computed: {
boxStyle() {
return {
height: `calc(100vh - 160px + ${this.windowTop}px)`,
};
},
sortedMonitorList() { sortedMonitorList() {
let result = Object.values(this.$root.monitorList); let result = Object.values(this.$root.monitorList);
@ -108,7 +117,20 @@ export default {
return result; return result;
}, },
}, },
mounted() {
window.addEventListener("scroll", this.onScroll);
},
beforeUnmount() {
window.removeEventListener("scroll", this.onScroll);
},
methods: { methods: {
onScroll() {
if (window.top.scrollY <= 133) {
this.windowTop = window.top.scrollY;
} else {
this.windowTop = 133;
}
},
monitorURL(id) { monitorURL(id) {
return getMonitorRelativeURL(id); return getMonitorRelativeURL(id);
}, },
@ -122,6 +144,12 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
@import "../assets/vars.scss"; @import "../assets/vars.scss";
.shadow-box {
height: calc(100vh - 150px);
position: sticky;
top: 10px;
}
.small-padding { .small-padding {
padding-left: 5px !important; padding-left: 5px !important;
padding-right: 5px !important; padding-right: 5px !important;
@ -142,6 +170,12 @@ export default {
} }
} }
.dark {
.footer {
// background-color: $dark-bg;
}
}
@media (max-width: 770px) { @media (max-width: 770px) {
.list-header { .list-header {
margin: -20px; margin: -20px;
@ -169,9 +203,16 @@ export default {
} }
.tags { .tags {
padding-left: 62px; margin-top: 4px;
padding-left: 67px;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 0; gap: 0;
} }
.bottom-style {
padding-left: 67px;
margin-top: 5px;
}
</style> </style>

@ -85,7 +85,9 @@ export default {
model: null, model: null,
processing: false, processing: false,
id: null, id: null,
notificationTypes: Object.keys(NotificationFormList), notificationTypes: Object.keys(NotificationFormList).sort((a, b) => {
return a.toLowerCase().localeCompare(b.toLowerCase());
}),
notification: { notification: {
name: "", name: "",
/** @type { null | keyof NotificationFormList } */ /** @type { null | keyof NotificationFormList } */
@ -143,12 +145,9 @@ export default {
this.id = null; this.id = null;
this.notification = { this.notification = {
name: "", name: "",
type: null, type: "telegram",
isDefault: false, isDefault: false,
}; };
// Set Default value here
this.notification.type = this.notificationTypes[0];
} }
this.modal.show(); this.modal.show();

@ -0,0 +1,206 @@
<template>
<form @submit.prevent="submit">
<div ref="modal" class="modal fade" tabindex="-1" data-bs-backdrop="static">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 id="exampleModalLabel" class="modal-title">
{{ $t("Setup Proxy") }}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" />
</div>
<div class="modal-body">
<div class="mb-3">
<label for="proxy-protocol" class="form-label">{{ $t("Proxy Protocol") }}</label>
<select id="proxy-protocol" v-model="proxy.protocol" class="form-select">
<option value="https">HTTPS</option>
<option value="http">HTTP</option>
<option value="socks">SOCKS</option>
<option value="socks5">SOCKS v5</option>
<option value="socks4">SOCKS v4</option>
</select>
</div>
<div class="mb-3">
<label for="proxy-host" class="form-label">{{ $t("Proxy Server") }}</label>
<div class="d-flex">
<input id="proxy-host" v-model="proxy.host" type="text" class="form-control" required :placeholder="$t('Server Address')">
<input v-model="proxy.port" type="number" class="form-control ms-2" style="width: 100px" required min="1" max="65535" :placeholder="$t('Port')">
</div>
</div>
<div class="mb-3">
<div class="form-check form-switch">
<input id="mark-auth" v-model="proxy.auth" class="form-check-input" type="checkbox">
<label for="mark-auth" class="form-check-label">{{ $t("Proxy server has authentication") }}</label>
</div>
</div>
<div v-if="proxy.auth" class="mb-3">
<label for="proxy-username" class="form-label">{{ $t("User") }}</label>
<input id="proxy-username" v-model="proxy.username" type="text" class="form-control" required>
</div>
<div v-if="proxy.auth" class="mb-3">
<label for="proxy-password" class="form-label">{{ $t("Password") }}</label>
<input id="proxy-password" v-model="proxy.password" type="password" class="form-control" required>
</div>
<div class="mb-3 mt-4">
<hr class="dropdown-divider mb-4">
<div class="form-check form-switch">
<input id="mark-active" v-model="proxy.active" class="form-check-input" type="checkbox">
<label for="mark-active" class="form-check-label">{{ $t("enabled") }}</label>
</div>
<div class="form-text">
{{ $t("enableProxyDescription") }}
</div>
<br />
<div class="form-check form-switch">
<input id="mark-default" v-model="proxy.default" class="form-check-input" type="checkbox">
<label for="mark-default" class="form-check-label">{{ $t("setAsDefault") }}</label>
</div>
<div class="form-text">
{{ $t("setAsDefaultProxyDescription") }}
</div>
<br />
<div class="form-check form-switch">
<input id="apply-existing" v-model="proxy.applyExisting" class="form-check-input" type="checkbox">
<label class="form-check-label" for="apply-existing">{{ $t("Apply on all existing monitors") }}</label>
</div>
</div>
</div>
<div class="modal-footer">
<button v-if="id" type="button" class="btn btn-danger" :disabled="processing" @click="deleteConfirm">
{{ $t("Delete") }}
</button>
<button type="submit" class="btn btn-primary" :disabled="processing">
<div v-if="processing" class="spinner-border spinner-border-sm me-1"></div>
{{ $t("Save") }}
</button>
</div>
</div>
</div>
</div>
</form>
<Confirm ref="confirmDelete" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="deleteProxy">
{{ $t("deleteProxyMsg") }}
</Confirm>
</template>
<script lang="ts">
import { Modal } from "bootstrap";
import Confirm from "./Confirm.vue";
export default {
components: {
Confirm,
},
props: {},
emits: ["added"],
data() {
return {
model: null,
processing: false,
id: null,
proxy: {
protocol: null,
host: null,
port: null,
auth: false,
username: null,
password: null,
active: false,
default: false,
applyExisting: false,
}
};
},
mounted() {
this.modal = new Modal(this.$refs.modal);
},
methods: {
deleteConfirm() {
this.modal.hide();
this.$refs.confirmDelete.show();
},
show(proxyID) {
if (proxyID) {
this.id = proxyID;
for (let proxy of this.$root.proxyList) {
if (proxy.id === proxyID) {
this.proxy = proxy;
break;
}
}
} else {
this.id = null;
this.proxy = {
protocol: "https",
host: null,
port: null,
auth: false,
username: null,
password: null,
active: true,
default: false,
applyExisting: false,
};
}
this.modal.show();
},
submit() {
this.processing = true;
this.$root.getSocket().emit("addProxy", this.proxy, this.id, (res) => {
this.$root.toastRes(res);
this.processing = false;
if (res.ok) {
this.modal.hide();
// Emit added event, doesn't emit edit.
if (! this.id) {
this.$emit("added", res.id);
}
}
});
},
deleteProxy() {
this.processing = true;
this.$root.getSocket().emit("deleteProxy", this.id, (res) => {
this.$root.toastRes(res);
this.processing = false;
if (res.ok) {
this.modal.hide();
}
});
},
},
};
</script>
<style lang="scss" scoped>
@import "../assets/vars.scss";
.dark {
.modal-dialog .form-text, .modal-dialog p {
color: $dark-font-color;
}
}
</style>

@ -41,7 +41,7 @@
<Uptime :monitor="monitor.element" type="24" :pill="true" /> <Uptime :monitor="monitor.element" type="24" :pill="true" />
{{ monitor.element.name }} {{ monitor.element.name }}
</div> </div>
<div class="tags"> <div v-if="showTags" class="tags">
<Tag v-for="tag in monitor.element.tags" :key="tag" :item="tag" :size="'sm'" /> <Tag v-for="tag in monitor.element.tags" :key="tag" :item="tag" :size="'sm'" />
</div> </div>
</div> </div>
@ -76,6 +76,9 @@ export default {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
showTags: {
type: Boolean,
}
}, },
data() { data() {
return { return {

@ -19,6 +19,19 @@
</div> </div>
<p v-if="showURI && twoFAStatus == false" class="text-break mt-2">{{ uri }}</p> <p v-if="showURI && twoFAStatus == false" class="text-break mt-2">{{ uri }}</p>
<div v-if="!(uri && twoFAStatus == false)" class="mb-3">
<label for="current-password" class="form-label">
{{ $t("Current Password") }}
</label>
<input
id="current-password"
v-model="currentPassword"
type="password"
class="form-control"
required
/>
</div>
<button v-if="uri == null && twoFAStatus == false" class="btn btn-primary" type="button" @click="prepare2FA()"> <button v-if="uri == null && twoFAStatus == false" class="btn btn-primary" type="button" @click="prepare2FA()">
{{ $t("Enable 2FA") }} {{ $t("Enable 2FA") }}
</button> </button>
@ -59,11 +72,11 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Modal } from "bootstrap" import { Modal } from "bootstrap";
import Confirm from "./Confirm.vue"; import Confirm from "./Confirm.vue";
import VueQrcode from "vue-qrcode" import VueQrcode from "vue-qrcode";
import { useToast } from "vue-toastification" import { useToast } from "vue-toastification";
const toast = useToast() const toast = useToast();
export default { export default {
components: { components: {
@ -73,35 +86,36 @@ export default {
props: {}, props: {},
data() { data() {
return { return {
currentPassword: "",
processing: false, processing: false,
uri: null, uri: null,
tokenValid: false, tokenValid: false,
twoFAStatus: null, twoFAStatus: null,
token: null, token: null,
showURI: false, showURI: false,
} };
}, },
mounted() { mounted() {
this.modal = new Modal(this.$refs.modal) this.modal = new Modal(this.$refs.modal);
this.getStatus(); this.getStatus();
}, },
methods: { methods: {
show() { show() {
this.modal.show() this.modal.show();
}, },
confirmEnableTwoFA() { confirmEnableTwoFA() {
this.$refs.confirmEnableTwoFA.show() this.$refs.confirmEnableTwoFA.show();
}, },
confirmDisableTwoFA() { confirmDisableTwoFA() {
this.$refs.confirmDisableTwoFA.show() this.$refs.confirmDisableTwoFA.show();
}, },
prepare2FA() { prepare2FA() {
this.processing = true; this.processing = true;
this.$root.getSocket().emit("prepare2FA", (res) => { this.$root.getSocket().emit("prepare2FA", this.currentPassword, (res) => {
this.processing = false; this.processing = false;
if (res.ok) { if (res.ok) {
@ -109,49 +123,51 @@ export default {
} else { } else {
toast.error(res.msg); toast.error(res.msg);
} }
}) });
}, },
save2FA() { save2FA() {
this.processing = true; this.processing = true;
this.$root.getSocket().emit("save2FA", (res) => { this.$root.getSocket().emit("save2FA", this.currentPassword, (res) => {
this.processing = false; this.processing = false;
if (res.ok) { if (res.ok) {
this.$root.toastRes(res) this.$root.toastRes(res);
this.getStatus(); this.getStatus();
this.currentPassword = "";
this.modal.hide(); this.modal.hide();
} else { } else {
toast.error(res.msg); toast.error(res.msg);
} }
}) });
}, },
disable2FA() { disable2FA() {
this.processing = true; this.processing = true;
this.$root.getSocket().emit("disable2FA", (res) => { this.$root.getSocket().emit("disable2FA", this.currentPassword, (res) => {
this.processing = false; this.processing = false;
if (res.ok) { if (res.ok) {
this.$root.toastRes(res) this.$root.toastRes(res);
this.getStatus(); this.getStatus();
this.currentPassword = "";
this.modal.hide(); this.modal.hide();
} else { } else {
toast.error(res.msg); toast.error(res.msg);
} }
}) });
}, },
verifyToken() { verifyToken() {
this.$root.getSocket().emit("verifyToken", this.token, (res) => { this.$root.getSocket().emit("verifyToken", this.token, this.currentPassword, (res) => {
if (res.ok) { if (res.ok) {
this.tokenValid = res.valid; this.tokenValid = res.valid;
} else { } else {
toast.error(res.msg); toast.error(res.msg);
} }
}) });
}, },
getStatus() { getStatus() {
@ -161,10 +177,10 @@ export default {
} else { } else {
toast.error(res.msg); toast.error(res.msg);
} }
}) });
}, },
}, },
} };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

@ -0,0 +1,14 @@
<template>
<div class="mb-3">
<label for="alerta-api-endpoint" class="form-label">{{ $t("alertaApiEndpoint") }}</label>
<input id="alerta-api-endpoint" v-model="$parent.notification.alertaApiEndpoint" type="text" class="form-control" required>
<label for="alerta-environment" class="form-label">{{ $t("alertaEnvironment") }}</label>
<input id="alerta-environment" v-model="$parent.notification.alertaEnvironment" type="text" class="form-control" required>
<label for="alerta-api-key" class="form-label">{{ $t("alertaApiKey") }}</label>
<input id="alerta-api-key" v-model="$parent.notification.alertaApiKey" type="text" class="form-control" required>
<label for="alerta-alert-state" class="form-label">{{ $t("alertaAlertState") }}</label>
<input id="alerta-alert-state" v-model="$parent.notification.alertaAlertState" type="text" class="form-control" placeholder="critical" required>
<label for="alerta-recover-state" class="form-label">{{ $t("alertaRecoverState") }}</label>
<input id="alerta-recover-state" v-model="$parent.notification.alertaRecoverState" type="text" class="form-control" placeholder="cleared" required>
</div>
</template>

@ -6,7 +6,7 @@
<label for="secretAccessKey" class="form-label">{{ $t("SecretAccessKey") }}<span style="color: red;"><sup>*</sup></span></label> <label for="secretAccessKey" class="form-label">{{ $t("SecretAccessKey") }}<span style="color: red;"><sup>*</sup></span></label>
<input id="secretAccessKey" v-model="$parent.notification.secretAccessKey" type="text" class="form-control" required> <input id="secretAccessKey" v-model="$parent.notification.secretAccessKey" type="text" class="form-control" required>
<label for="phonenumber" class="form-label">{{ $t("Phonenumber") }}<span style="color: red;"><sup>*</sup></span></label> <label for="phonenumber" class="form-label">{{ $t("PhoneNumbers") }}<span style="color: red;"><sup>*</sup></span></label>
<input id="phonenumber" v-model="$parent.notification.phonenumber" type="text" class="form-control" required> <input id="phonenumber" v-model="$parent.notification.phonenumber" type="text" class="form-control" required>
<label for="templateCode" class="form-label">{{ $t("TemplateCode") }}<span style="color: red;"><sup>*</sup></span></label> <label for="templateCode" class="form-label">{{ $t("TemplateCode") }}<span style="color: red;"><sup>*</sup></span></label>
@ -16,7 +16,7 @@
<input id="signName" v-model="$parent.notification.signName" type="text" class="form-control" required> <input id="signName" v-model="$parent.notification.signName" type="text" class="form-control" required>
<div class="form-text"> <div class="form-text">
<p>Sms template must contain parameters: <br> <code>${name} ${time} ${status} ${msg}</code></p> <p>{{ $t("Sms template must contain parameters: ") }}<br> <code>${name} ${time} ${status} ${msg}</code></p>
<i18n-t tag="p" keypath="Read more:"> <i18n-t tag="p" keypath="Read more:">
<a href="https://help.aliyun.com/document_detail/101414.html" target="_blank">https://help.aliyun.com/document_detail/101414.html</a> <a href="https://help.aliyun.com/document_detail/101414.html" target="_blank">https://help.aliyun.com/document_detail/101414.html</a>
</i18n-t> </i18n-t>

@ -7,9 +7,9 @@
<input id="secretKey" v-model="$parent.notification.secretKey" type="text" class="form-control" required> <input id="secretKey" v-model="$parent.notification.secretKey" type="text" class="form-control" required>
<div class="form-text"> <div class="form-text">
<p>For safety, must use secret key</p> <p>{{ $t("For safety, must use secret key") }}</p>
<i18n-t tag="p" keypath="Read more:"> <i18n-t tag="p" keypath="Read more:">
<a href="https://developers.dingtalk.com/document/robots/custom-robot-access" target="_blank">https://developers.dingtalk.com/document/robots/custom-robot-access</a> <a href="https://developers.dingtalk.com/document/robots/custom-robot-access" target="_blank">https://developers.dingtalk.com/document/robots/custom-robot-access</a> <a href="https://open.dingtalk.com/document/robots/customize-robot-security-settings#title-7fs-kgs-36x" target="_blank">https://open.dingtalk.com/document/robots/customize-robot-security-settings#title-7fs-kgs-36x</a>
</i18n-t> </i18n-t>
</div> </div>
</div> </div>

@ -0,0 +1,51 @@
<template>
<div class="mb-3">
<label for="gorush-device-token" class="form-label">{{ $t("Device Token") }}</label><span style="color: red;"><sup>*</sup></span>
<div class="input-group mb-3">
<input id="gorush-device-token" v-model="$parent.notification.gorushDeviceToken" type="text" class="form-control" required>
</div>
</div>
<div class="mb-3">
<label for="gorush-server-url" class="form-label">{{ $t("Server URL") }}</label><span style="color: red;"><sup>*</sup></span>
<div class="input-group mb-3">
<input id="gorush-server-url" v-model="$parent.notification.gorushServerURL" type="text" class="form-control" required>
</div>
</div>
<div class="mb-3">
<label for="gorush-platform" class="form-label">{{ $t("Platform") }}</label><span style="color: red;"><sup>*</sup></span>
<select id="gorush-platform" v-model="$parent.notification.gorushPlatform" class="form-select">
<option value="ios">{{ $t("iOS") }}</option>
<option value="android">{{ $t("Android") }}</option>
<option value="huawei">{{ $t("Huawei") }}</option>
</select>
</div>
<div class="mb-3">
<label for="gorush-title" class="form-label">{{ $t("Title") }}</label>
<input id="gorush-title" v-model="$parent.notification.gorushTitle" type="text" class="form-control">
</div>
<div class="mb-3">
<label for="gorush-priority" class="form-label">{{ $t("Priority") }}</label>
<select id="gorush-priority" v-model="$parent.notification.gorushPriority" class="form-select">
<option value="normal">{{ $t("Normal") }}</option>
<option value="high">{{ $t("High") }}</option>
</select>
</div>
<div class="mb-3">
<label for="gorush-retry" class="form-label">{{ $t("Retry") }}</label>
<input id="gorush-retry" v-model="$parent.notification.gorushRetry" type="number" class="form-control">
</div>
<div class="mb-3">
<label for="gorush-topic" class="form-label">{{ $t("Topic") }}</label>
<input id="gorush-topic" v-model="$parent.notification.gorushTopic" type="text" class="form-control">
</div>
<div class="form-text">
<span style="color: red;"><sup>*</sup></span>{{ $t("Required") }}
</div>
</template>

@ -0,0 +1,20 @@
<template>
<div class="mb-3">
<label for="push-api-key" class="form-label">API_KEY</label>
<HiddenInput id="push-api-key" v-model="$parent.notification.pushAPIKey" :required="true" autocomplete="one-time-code"></HiddenInput>
</div>
<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>
</i18n-t>
</template>
<script>
import HiddenInput from "../HiddenInput.vue";
export default {
components: {
HiddenInput,
},
};
</script>

@ -63,9 +63,9 @@ export default {
let update = res.data.result[res.data.result.length - 1]; let update = res.data.result[res.data.result.length - 1];
if (update.channel_post) { if (update.channel_post) {
this.notification.telegramChatID = update.channel_post.chat.id; this.$parent.notification.telegramChatID = update.channel_post.chat.id;
} else if (update.message) { } else if (update.message) {
this.notification.telegramChatID = update.message.chat.id; this.$parent.notification.telegramChatID = update.message.chat.id;
} else { } else {
throw new Error(this.$t("chatIDNotFound")); throw new Error(this.$t("chatIDNotFound"));
} }

@ -9,6 +9,7 @@ import RocketChat from "./RocketChat.vue";
import Teams from "./Teams.vue"; import Teams from "./Teams.vue";
import Pushover from "./Pushover.vue"; import Pushover from "./Pushover.vue";
import Pushy from "./Pushy.vue"; import Pushy from "./Pushy.vue";
import TechulusPush from "./TechulusPush.vue";
import Octopush from "./Octopush.vue"; import Octopush from "./Octopush.vue";
import PromoSMS from "./PromoSMS.vue"; import PromoSMS from "./PromoSMS.vue";
import ClickSendSMS from "./ClickSendSMS.vue"; import ClickSendSMS from "./ClickSendSMS.vue";
@ -26,6 +27,8 @@ import SerwerSMS from "./SerwerSMS.vue";
import Stackfield from './Stackfield.vue'; import Stackfield from './Stackfield.vue';
import WeCom from "./WeCom.vue"; import WeCom from "./WeCom.vue";
import GoogleChat from "./GoogleChat.vue"; import GoogleChat from "./GoogleChat.vue";
import Gorush from "./Gorush.vue";
import Alerta from "./Alerta.vue";
/** /**
* Manage all notification form. * Manage all notification form.
@ -44,6 +47,7 @@ const NotificationFormList = {
"rocket.chat": RocketChat, "rocket.chat": RocketChat,
"pushover": Pushover, "pushover": Pushover,
"pushy": Pushy, "pushy": Pushy,
"PushByTechulus": TechulusPush,
"octopush": Octopush, "octopush": Octopush,
"promosms": PromoSMS, "promosms": PromoSMS,
"clicksendsms": ClickSendSMS, "clicksendsms": ClickSendSMS,
@ -60,7 +64,9 @@ const NotificationFormList = {
"serwersms": SerwerSMS, "serwersms": SerwerSMS,
"stackfield": Stackfield, "stackfield": Stackfield,
"WeCom": WeCom, "WeCom": WeCom,
"GoogleChat": GoogleChat "GoogleChat": GoogleChat,
"gorush": Gorush,
"alerta": Alerta,
}; };
export default NotificationFormList; export default NotificationFormList;

@ -4,14 +4,39 @@
<object class="my-4" width="200" height="200" data="/icon.svg" /> <object class="my-4" width="200" height="200" data="/icon.svg" />
<div class="fs-4 fw-bold">Uptime Kuma</div> <div class="fs-4 fw-bold">Uptime Kuma</div>
<div>{{ $t("Version") }}: {{ $root.info.version }}</div> <div>{{ $t("Version") }}: {{ $root.info.version }}</div>
<div class="my-1 update-link"><a href="https://github.com/louislam/uptime-kuma/releases" target="_blank" rel="noopener">{{ $t("Check Update On GitHub") }}</a></div>
<div class="my-3 update-link"><a href="https://github.com/louislam/uptime-kuma/releases" target="_blank" rel="noopener">{{ $t("Check Update On GitHub") }}</a></div>
<div class="mt-1">
<div class="form-check">
<label><input v-model="settings.checkUpdate" type="checkbox" @change="saveSettings()" /> Show update if available</label>
</div>
<div class="form-check">
<label><input v-model="settings.checkBeta" type="checkbox" :disabled="!settings.checkUpdate" @change="saveSettings()" /> Also check beta release</label>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
computed: {
settings() {
return this.$parent.$parent.$parent.settings;
},
saveSettings() {
return this.$parent.$parent.$parent.saveSettings;
},
settingsLoaded() {
return this.$parent.$parent.$parent.settingsLoaded;
},
},
watch: {
}
}; };
</script> </script>

@ -62,31 +62,31 @@
<div class="form-check"> <div class="form-check">
<input <input
id="entryPageYes" id="entryPageDashboard"
v-model="settings.entryPage" v-model="settings.entryPage"
class="form-check-input" class="form-check-input"
type="radio" type="radio"
name="statusPage" name="entryPage"
value="dashboard" value="dashboard"
required required
/> />
<label class="form-check-label" for="entryPageYes"> <label class="form-check-label" for="entryPageDashboard">
{{ $t("Dashboard") }} {{ $t("Dashboard") }}
</label> </label>
</div> </div>
<div class="form-check"> <div v-for="statusPage in $root.statusPageList" :key="statusPage.id" class="form-check">
<input <input
id="entryPageNo" :id="'status-page-' + statusPage.id"
v-model="settings.entryPage" v-model="settings.entryPage"
class="form-check-input" class="form-check-input"
type="radio" type="radio"
name="statusPage" name="entryPage"
value="statusPage" :value="'statusPage-' + statusPage.slug"
required required
/> />
<label class="form-check-label" for="entryPageNo"> <label class="form-check-label" :for="'status-page-' + statusPage.id">
{{ $t("Status Page") }} {{ $t("Status Page") }} - {{ statusPage.title }}
</label> </label>
</div> </div>
</div> </div>

@ -0,0 +1,48 @@
<template>
<div>
<!-- Proxies -->
<div class="proxy-list my-4">
<p v-if="$root.proxyList.length === 0">
{{ $t("Not available, please setup.") }}
</p>
<p v-else>
{{ $t("proxyDescription") }}
</p>
<ul class="list-group mb-3" style="border-radius: 1rem;">
<li v-for="(proxy, index) in $root.proxyList" :key="index" class="list-group-item">
{{ proxy.host }}:{{ proxy.port }} ({{ proxy.protocol }})
<span v-if="proxy.default === true" class="badge bg-primary ms-2">{{ $t("Default") }}</span><br>
<a href="#" @click="$refs.proxyDialog.show(proxy.id)">{{ $t("Edit") }}</a>
</li>
</ul>
<button class="btn btn-primary me-2" type="button" @click="$refs.proxyDialog.show()">
{{ $t("Setup Proxy") }}
</button>
</div>
<ProxyDialog ref="proxyDialog" />
</div>
</template>
<script>
import ProxyDialog from "../../components/ProxyDialog.vue";
export default {
components: {
ProxyDialog
},
};
</script>
<style lang="scss" scoped>
@import "../../assets/vars.scss";
.dark {
.list-group-item {
background-color: $dark-bg2;
color: $dark-font-color;
}
}
</style>

@ -0,0 +1,144 @@
<template>
<div>
<h4 class="mt-4">Cloudflare Tunnel</h4>
<div class="my-3">
<div>
cloudflared:
<span v-if="installed === true" class="text-primary">{{ $t("Installed") }}</span>
<span v-else-if="installed === false" class="text-danger">{{ $t("Not installed") }}</span>
</div>
<div>
{{ $t("Status") }}:
<span v-if="running" class="text-primary">{{ $t("Running") }}</span>
<span v-else-if="!running" class="text-danger">{{ $t("Not running") }}</span>
</div>
<div v-if="false">
{{ message }}
</div>
<div v-if="errorMessage" class="mt-3">
{{ $t("Message:") }}
<textarea v-model="errorMessage" class="form-control" readonly></textarea>
</div>
<i18n-t v-if="installed === false" tag="p" keypath="wayToGetCloudflaredURL">
<a
href="https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation/"
target="_blank"
>{{ $t("cloudflareWebsite") }}</a>
</i18n-t>
</div>
<!-- If installed show token input -->
<div v-if="installed" class="mb-2">
<div class="mb-4">
<label class="form-label" for="cloudflareTunnelToken">
Cloudflare Tunnel {{ $t("Token") }}
</label>
<HiddenInput
id="cloudflareTunnelToken"
v-model="cloudflareTunnelToken"
autocomplete="one-time-code"
:readonly="running"
/>
<div class="form-text">
<div v-if="cloudflareTunnelToken" class="mb-3">
<span v-if="!running" class="remove-token" @click="removeToken">{{ $t("Remove Token") }}</span>
</div>
{{ $t("Don't know how to get the token? Please read the guide:") }}<br />
<a href="https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy-with-Cloudflare-Tunnel" target="_blank">
https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy-with-Cloudflare-Tunnel
</a>
</div>
</div>
<div>
<button v-if="!running" class="btn btn-primary" type="submit" @click="start">
{{ $t("Start") }} cloudflared
</button>
<button v-if="running" class="btn btn-danger" type="submit" @click="$refs.confirmStop.show();">
{{ $t("Stop") }} cloudflared
</button>
<Confirm ref="confirmStop" btn-style="btn-danger" :yes-text="$t('Stop') + ' cloudflared'" :no-text="$t('Cancel')" @yes="stop">
{{ $t("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.") }}
<div class="mt-3">
<label for="current-password2" class="form-label">
{{ $t("Current Password") }}
</label>
<input
id="current-password2"
v-model="currentPassword"
type="password"
class="form-control"
required
/>
</div>
</Confirm>
</div>
</div>
<h4 class="mt-4">{{ $t("Other Software") }}</h4>
<div>
{{ $t("For example: nginx, Apache and Traefik.") }} <br />
{{ $t("Please read") }} <a href="https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy" target="_blank">https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy</a>.
</div>
</div>
</template>
<script>
import HiddenInput from "../../components/HiddenInput.vue";
import Confirm from "../Confirm.vue";
const prefix = "cloudflared_";
export default {
components: {
HiddenInput,
Confirm
},
data() {
// See /src/mixins/socket.js
return this.$root.cloudflared;
},
computed: {
},
watch: {
},
created() {
this.$root.getSocket().emit(prefix + "join");
},
unmounted() {
this.$root.getSocket().emit(prefix + "leave");
},
methods: {
start() {
this.$root.getSocket().emit(prefix + "start", this.cloudflareTunnelToken);
},
stop() {
this.$root.getSocket().emit(prefix + "stop", this.currentPassword, (res) => {
this.$root.toastRes(res);
});
},
removeToken() {
this.$root.getSocket().emit(prefix + "removeToken");
this.cloudflareTunnelToken = "";
}
}
};
</script>
<style lang="scss" scoped>
.remove-token {
text-decoration: underline;
cursor: pointer;
}
</style>

@ -175,9 +175,9 @@
</template> </template>
<template v-else-if="$i18n.locale === 'it-IT' "> <template v-else-if="$i18n.locale === 'it-IT' ">
<p>Si è certi di voler <strong>disabilitare l'autenticazione</strong>?</p> <p><strong>Disabilitare l'autenticazione?</strong></p>
<p>È per <strong>chi ha l'autenticazione gestita da terze parti</strong> messa davanti ad Uptime Kuma, ad esempio Cloudflare Access.</p> <p><strong>Questa opzione è per chi un sistema di autenticazione gestito da terze parti</strong> messo davanti ad Uptime Kuma, ad esempio Cloudflare Access.</p>
<p>Utilizzare con attenzione.</p> <p>Utilizzare con attenzione!</p>
</template> </template>
<template v-else-if="$i18n.locale === 'id-ID' "> <template v-else-if="$i18n.locale === 'id-ID' ">
@ -192,6 +192,12 @@
<p>Пожалуйста, используйте с осторожностью.</p> <p>Пожалуйста, используйте с осторожностью.</p>
</template> </template>
<template v-else-if="$i18n.locale === 'uk-UA' ">
<p>Ви впевнені, що бажаєте <strong>вимкнути авторизацію</strong>?</p>
<p>Це підходить для <strong>тих, у кого встановлена інша авторизація</strong> пееред відкриттям Uptime Kuma, наприклад Cloudflare Access.</p>
<p>Будь ласка, використовуйте з обережністю.</p>
</template>
<template v-else-if="$i18n.locale === 'fa' "> <template v-else-if="$i18n.locale === 'fa' ">
<p>آیا مطمئن هستید که میخواهید <strong>احراز هویت را غیر فعال کنید</strong>?</p> <p>آیا مطمئن هستید که میخواهید <strong>احراز هویت را غیر فعال کنید</strong>?</p>
<p>این ویژگی برای کسانی است که <strong> لایه امنیتی شخص ثالث دیگر بر روی این آدرس فعال کردهاند</strong>، مانند Cloudflare Access.</p> <p>این ویژگی برای کسانی است که <strong> لایه امنیتی شخص ثالث دیگر بر روی این آدرس فعال کردهاند</strong>، مانند Cloudflare Access.</p>
@ -216,12 +222,37 @@
<p>Vennligst vær forsiktig.</p> <p>Vennligst vær forsiktig.</p>
</template> </template>
<template v-else-if="$i18n.locale === 'cs-CZ' ">
<p>Opravdu chcete <strong>deaktivovat autentifikaci</strong>?</p>
<p>Tato možnost je určena pro případy, kdy <strong>máte autentifikaci zajištěnou třetí stranou</strong> ještě před přístupem do Uptime Kuma, například prostřednictvím Cloudflare Access.</p>
<p>Používejte ji prosím s rozmyslem.</p>
</template>
<template v-else-if="$i18n.locale === 'vi-VN' ">
<p>Bạn muốn <strong>TẮT XÁC THỰC</strong> không?</p>
<p>Điều này rất nguy hiểm<strong>BẤT KỲ AI</strong> cũng thể truy cập cướp quyền điều khiển.</p>
<p>Vui lòng <strong>cẩn thận</strong>.</p>
</template>
<!-- English (en) --> <!-- English (en) -->
<template v-else> <template v-else>
<p>Are you sure want to <strong>disable auth</strong>?</p> <p>Are you sure want to <strong>disable authentication</strong>?</p>
<p>It is for <strong>someone who have 3rd-party auth</strong> in front of Uptime Kuma such as Cloudflare Access.</p> <p>It is designed for scenarios <strong>where you intend to implement third-party authentication</strong> in front of Uptime Kuma such as Cloudflare Access, Authelia or other authentication mechanisms.</p>
<p>Please use it carefully.</p> <p>Please use this option carefully!</p>
</template> </template>
<div class="mb-3">
<label for="current-password2" class="form-label">
{{ $t("Current Password") }}
</label>
<input
id="current-password2"
v-model="password.currentPassword"
type="password"
class="form-control"
required
/>
</div>
</Confirm> </Confirm>
</div> </div>
</template> </template>
@ -298,7 +329,12 @@ export default {
disableAuth() { disableAuth() {
this.settings.disableAuth = true; this.settings.disableAuth = true;
this.saveSettings();
// Need current password to disable auth
// Set it to empty if done
this.saveSettings(() => {
this.password.currentPassword = "";
}, this.password.currentPassword);
}, },
enableAuth() { enableAuth() {

@ -2,6 +2,7 @@ import { createI18n } from "vue-i18n/index";
import en from "./languages/en"; import en from "./languages/en";
const languageList = { const languageList = {
"cs-CZ": "Čeština",
"zh-HK": "繁體中文 (香港)", "zh-HK": "繁體中文 (香港)",
"bg-BG": "Български", "bg-BG": "Български",
"de-DE": "Deutsch (Deutschland)", "de-DE": "Deutsch (Deutschland)",
@ -27,8 +28,9 @@ const languageList = {
"zh-CN": "简体中文", "zh-CN": "简体中文",
"pl": "Polski", "pl": "Polski",
"et-EE": "eesti", "et-EE": "eesti",
"vi": "Vietnamese", "vi-VN": "Tiếng Việt",
"zh-TW": "繁體中文 (台灣)" "zh-TW": "繁體中文 (台灣)",
"uk-UA": "Український",
}; };
let messages = { let messages = {

@ -34,6 +34,11 @@ import {
faAward, faAward,
faLink, faLink,
faChevronDown, faChevronDown,
faPen,
faExternalLinkSquareAlt,
faSpinner,
faUndo,
faPlusCircle,
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
library.add( library.add(
@ -67,6 +72,11 @@ library.add(
faAward, faAward,
faLink, faLink,
faChevronDown, faChevronDown,
faPen,
faExternalLinkSquareAlt,
faSpinner,
faUndo,
faPlusCircle,
); );
export { FontAwesomeIcon }; export { FontAwesomeIcon };

@ -13,7 +13,7 @@ export default {
pauseDashboardHome: "Пауза", pauseDashboardHome: "Пауза",
deleteMonitorMsg: "Наистина ли желаете да изтриете този монитор?", deleteMonitorMsg: "Наистина ли желаете да изтриете този монитор?",
deleteNotificationMsg: "Наистина ли желаете да изтриете това известяване за всички монитори?", deleteNotificationMsg: "Наистина ли желаете да изтриете това известяване за всички монитори?",
resoverserverDescription: "Cloudflare е сървърът по подразбиране, но можете да го промените по всяко време.", resolverserverDescription: "Cloudflare е сървърът по подразбиране, но можете да го промените по всяко време.",
rrtypeDescription: "Изберете ресурсния запис, който желаете да наблюдавате", rrtypeDescription: "Изберете ресурсния запис, който желаете да наблюдавате",
pauseMonitorMsg: "Наистина ли желаете да поставите в режим пауза?", pauseMonitorMsg: "Наистина ли желаете да поставите в режим пауза?",
enableDefaultNotificationDescription: "За всеки нов монитор това известяване ще бъде активирано по подразбиране. Можете да го изключите за всеки отделен монитор.", enableDefaultNotificationDescription: "За всеки нов монитор това известяване ще бъде активирано по подразбиране. Можете да го изключите за всеки отделен монитор.",
@ -197,6 +197,7 @@ export default {
line: "Line Messenger", line: "Line Messenger",
mattermost: "Mattermost", mattermost: "Mattermost",
"Status Page": "Статус страница", "Status Page": "Статус страница",
"Status Pages": "Статус страница",
"Primary Base URL": "Основен базов URL адрес", "Primary Base URL": "Основен базов URL адрес",
"Push URL": "Генериран Push URL адрес", "Push URL": "Генериран Push URL адрес",
needPushEvery: "Необходимо е да извършвате заявка към този URL адрес на всеки {0} секунди", needPushEvery: "Необходимо е да извършвате заявка към този URL адрес на всеки {0} секунди",
@ -360,4 +361,14 @@ export default {
smtpDkimHashAlgo: "Хеш алгоритъм (по желание)", smtpDkimHashAlgo: "Хеш алгоритъм (по желание)",
smtpDkimheaderFieldNames: "Хедър ключове за подписване (по желание)", smtpDkimheaderFieldNames: "Хедър ключове за подписване (по желание)",
smtpDkimskipFields: "Хедър ключове, които да не се подписват (по желание)", smtpDkimskipFields: "Хедър ключове, които да не се подписват (по желание)",
PushByTechulus: "Push от Techulus",
GoogleChat: "Google Chat (Само за работното пространство на Google)",
gorush: "Gorush",
alerta: "Alerta",
alertaApiEndpoint: "Крайна точка на API",
alertaEnvironment: "Среда",
alertaApiKey: "API Ключ",
alertaAlertState: "Състояние на тревога",
alertaRecoverState: "Състояние на възстановяване",
deleteStatusPageMsg: "Сигурни ли сте, че желаете да изтриете тази статус страница?",
}; };

@ -0,0 +1,365 @@
export default {
languageName: "Czech",
checkEverySecond: "Kontrolovat každých {0} sekund",
retryCheckEverySecond: "Opakovat každých {0} sekund",
retriesDescription: "Maximální počet pokusů před označením služby jako nedostupné a odesláním oznámení",
ignoreTLSError: "Ignorovat TLS/SSL chyby na HTTPS stránkách",
upsideDownModeDescription: "Pomocí této možnosti změníte způsob vyhodnocování stavu. Pokud je služba dosažitelná, je NEDOSTUPNÁ.",
maxRedirectDescription: "Maximální počet přesměrování, která se mají následovat. Nastavením hodnoty 0 zakážete přesměrování.",
acceptedStatusCodesDescription: "Vyberte stavové kódy, které jsou považovány za úspěšnou odpověď.",
passwordNotMatchMsg: "Hesla se neshodují",
notificationDescription: "Pro zajištění funkčnosti oznámení je nutné je přiřadit dohledu.",
keywordDescription: "Vyhledat klíčové slovo v prosté odpovědi HTML nebo JSON. Při hledání se rozlišuje velikost písmen.",
pauseDashboardHome: "Pozastavit",
deleteMonitorMsg: "Opravdu chcete odstranit tento dohled?",
deleteNotificationMsg: "Opravdu chcete odstranit toto oznámení pro všechny dohledy?",
resolverserverDescription: "Cloudflare je výchozí server. Resolver server můžete kdykoli změnit.",
rrtypeDescription: "Vyberte typ záznamu o prostředku, který chcete monitorovat",
pauseMonitorMsg: "Opravdu chcete dohled pozastavit?",
enableDefaultNotificationDescription: "Toto oznámení bude standardně aktivní pro nové dohledy. V případě potřeby můžete oznámení stále zakázat na úrovni jednotlivých dohledů.",
clearEventsMsg: "Opravdu chcete odstranit všechny události pro tento dohled?",
clearHeartbeatsMsg: "Opravdu chcete odstranit všechny heartbeaty pro tento dohled?",
confirmClearStatisticsMsg: "Opravdu chcete smazat VŠECHNY statistiky?",
importHandleDescription: "Možnost 'Přeskočit existující' vyberte v případě, že chcete přeskočit všechny dohledy nebo oznámení se stejným názvem. Vybráním možnosti 'Přepsat' dojde k odstranění všech existujících dohledů a oznámení.",
confirmImportMsg: "Opravdu chcete importovat zálohu? Prosím ověřte, zda jste vybrali správnou možnost importu.",
twoFAVerifyLabel: "Prosím, zadejte svůj token pro ověření 2FA:",
tokenValidSettingsMsg: "Token je platný! Nyní můžete uložit nastavení 2FA.",
confirmEnableTwoFAMsg: "Opravdu chcete zapnout 2FA?",
confirmDisableTwoFAMsg: "Opravdu chcete deaktivovat 2FA?",
Settings: "Nastavení",
Dashboard: "Nástěnka",
"New Update": "Nová aktualizace",
Language: "Jazyk",
Appearance: "Vzhled",
Theme: "Motiv",
General: "Obecné",
"Primary Base URL": "Primární URL adresa",
Version: "Verze",
"Check Update On GitHub": "Zkontrolovat aktualizace na GitHubu",
List: "Seznam",
Add: "Přidat",
"Add New Monitor": "Přidat nový dohled",
"Quick Stats": "Rychlé statistiky",
Up: "Běží",
Down: "Nedostupný",
Pending: "Čekám",
Unknown: "Neznámý",
Pause: "Pozastavit",
Name: "Název",
Status: "Stav",
DateTime: "DateTime",
Message: "Zpráva",
"No important events": "Žádné důležité události",
Resume: "Pokračovat",
Edit: "Změnit",
Delete: "Vymazat",
Current: "Aktuální",
Uptime: "Doba provozu",
"Cert Exp.": "Platnost certifikátu",
days: "dny/í",
day: "den",
"-day": "-dní",
hour: "hodina",
"-hour": "-hodin",
Response: "Odpověď",
Ping: "Ping",
"Monitor Type": "Typ dohledu",
Keyword: "Klíčové slovo",
"Friendly Name": "Obecný název",
URL: "URL",
Hostname: "Adresa serveru",
Port: "Port",
"Heartbeat Interval": "Heartbeat interval",
Retries: "Počet pokusů",
"Heartbeat Retry Interval": "Interval opakování prezenčního signálu",
Advanced: "Rozšířené",
"Upside Down Mode": "Inverzní režim",
"Max. Redirects": "Max. Přesměrování",
"Accepted Status Codes": "Akceptované stavové kódy",
"Push URL": "Push URL",
needPushEvery: "Tuto URL adresu byste měli volat každých {0} sekund.",
pushOptionalParams: "Volitelné parametry: {0}",
Save: "Uložit",
Notifications: "Oznámení",
"Not available, please setup.": "Není k dispozici, prosím nastavte.",
"Setup Notification": "Nastavení oznámení",
Light: "Světlý",
Dark: "Tmavý",
Auto: "Automaticky",
"Theme - Heartbeat Bar": "Motiv Heartbeat panel",
Normal: "Normální",
Bottom: "Dole",
None: "Žádné",
Timezone: "Časové pásmo",
"Search Engine Visibility": "Viditelnost pro vyhledávače",
"Allow indexing": "Povolit indexování",
"Discourage search engines from indexing site": "Zabránit vyhledávačům v indexování stránky",
"Change Password": "Změnit heslo",
"Current Password": "Aktuální heslo",
"New Password": "Nové heslo",
"Repeat New Password": "Znovu zadat nové heslo",
"Update Password": "Aktualizovat heslo",
"Disable Auth": "Deaktivovat ověřování",
"Enable Auth": "Povolit ověřování",
Logout: "Odhlášení",
Leave: "Odejít",
"I understand, please disable": "Rozumím, chci ji deaktivovat",
Confirm: "Potvrzení",
Yes: "Ano",
No: "Ne",
Username: "Uživatelské jméno",
Password: "Heslo",
"Remember me": "Zapamatovat si mě",
Login: "Přihlášení",
"No Monitors, please": "Žádné dohledy, prosím",
"add one": "přidat jeden",
"Notification Type": "Typ oznámení",
Email: "E-mail",
Test: "Test",
"Certificate Info": "Informace o certifikátu",
"Resolver Server": "Resolver Server",
"Resource Record Type": "Typ záznamu o prostředku",
"Last Result": "Poslední výsledek",
"Create your admin account": "Vytvořit účet administrátora",
"Repeat Password": "Znovu zadat heslo",
"Import Backup": "Importovat zálohu",
"Export Backup": "Exportovat zálohu",
Export: "Exportovat",
Import: "Importovat",
respTime: "Odezva Čas (ms)",
notAvailableShort: "N/A",
"Default enabled": "Standardně povoleno",
"Apply on all existing monitors": "Použít pro všechny existující dohledy",
Create: "Vytvořit",
"Clear Data": "Vymazat data",
Events: "Události",
Heartbeats: "Heartbeaty",
"Auto Get": "Získat automaticky",
backupDescription: "Všechny dohledy a oznámení můžete zálohovat do souboru ve formátu JSON.",
backupDescription2: "Poznámka: Nezahrnuje historii a data událostí.",
backupDescription3: "Součástí exportovaného souboru jsou citlivá data jako tokeny oznámení; export si prosím bezpečně uložte.",
alertNoFile: "Vyberte soubor, který chcete importovat.",
alertWrongFileType: "Vyberte soubor ve formátu JSON.",
"Clear all statistics": "Vymazat všechny statistiky",
"Skip existing": "Přeskočit existující",
Overwrite: "Přepsat",
Options: "Možnosti",
"Keep both": "Ponechat obojí",
"Verify Token": "Ověřit token",
"Setup 2FA": "Nastavení 2FA",
"Enable 2FA": "Povolit 2FA",
"Disable 2FA": "Deaktivovat 2FA",
"2FA Settings": "Nastavení 2FA",
"Two Factor Authentication": "Dvoufaktorová autentifikace",
Active: "Zapnuto",
Inactive: "Neaktivní",
Token: "Token",
"Show URI": "Zobrazit URI",
Tags: "Štítky",
"Add New below or Select...": "Níže přidejte nový nebo vyberte existující…",
"Tag with this name already exist.": "Štítek s tímto názvem již existuje.",
"Tag with this value already exist.": "Štítek touto hodnotou již existuje.",
color: "barva",
"value (optional)": "hodnota (volitelné)",
Gray: "Šedá",
Red: "Červená",
Orange: "Oranžová",
Green: "Zelená",
Blue: "Modrá",
Indigo: "Indigo",
Purple: "Purpurová",
Pink: "Růžová",
"Search...": "Hledat…",
"Avg. Ping": "Průměr Ping",
"Avg. Response": "Průměr Odpověď",
"Entry Page": "Vstupní stránka",
statusPageNothing: "Nic tady není, přidejte prosím skupinu nebo dohled.",
"No Services": "Žádné služby",
"All Systems Operational": "Všechny systémy běží",
"Partially Degraded Service": "Částečně zhoršená služba",
"Degraded Service": "Zhoršená služba",
"Add Group": "Přidat skupinu",
"Add a monitor": "Přidání dohledu",
"Edit Status Page": "Upravit stavovou stránku",
"Go to Dashboard": "Přejít na nástěnku",
"Status Page": "Stavová stránka",
"Status Pages": "Stavová stránka",
defaultNotificationName: "Moje {notification} upozornění ({číslo})",
here: "sem",
Required: "Vyžadováno",
telegram: "Telegram",
"Bot Token": "Token robota",
wayToGetTelegramToken: "Token můžete získat od {0}.",
"Chat ID": "ID chatu",
supportTelegramChatID: "Podpora přímého chatu / skupiny / ID chatu kanálu",
wayToGetTelegramChatID: "ID chatu můžete získat tak, že robotovi zašlete zprávu a přejdete na tuto adresu URL, kde zobrazíte chat_id:",
"YOUR BOT TOKEN HERE": "YOUR BOT TOKEN HERE",
chatIDNotFound: "ID chatu nebylo nalezeno; nejprve tomuto robotovi zašlete zprávu",
webhook: "Webhook",
"Post URL": "URL adresa příspěvku",
"Content Type": "Typ obsahu",
webhookJsonDesc: "{0} je vhodný pro všechny moderní servery HTTP, jako je Express.js",
webhookFormDataDesc: "{multipart} je vhodné pro PHP. JSON bude nutné analyzovat prostřednictvím {decodeFunction}",
smtp: "E-mail (SMTP)",
secureOptionNone: "Žádné / STARTTLS (25, 587)",
secureOptionTLS: "TLS (465)",
"Ignore TLS Error": "Ignorovat chybu TLS",
"From Email": "Odesílatel",
emailCustomSubject: "Vlastní předmět",
"To Email": "Příjemce",
smtpCC: "Kopie",
smtpBCC: "Skrytá kopie",
discord: "Discord",
"Discord Webhook URL": "Discord Webhook URL",
wayToGetDiscordURL: "Získáte tak, že přejdete do Nastavení serveru - > Integrace - > Vytvořit Webhook",
"Bot Display Name": "Zobrazované jméno robota",
"Prefix Custom Message": "Předpona vlastní zprávy",
"Hello @everyone is...": "Dobrý den, {'@'}všichni jsou…",
teams: "Microsoft Teams",
"Webhook URL": "URL adresa webhooku",
wayToGetTeamsURL: "Informace o tom, jak vytvořit URL adresu webhooku naleznete {0}.",
signal: "Signal",
Number: "Číslo",
Recipients: "Příjemci",
needSignalAPI: "Musíte mít Signal klienta s REST API.",
wayToCheckSignalURL: "Pro zobrazení instrukcí, jak službu nastavit, přejděte na následující adresu:",
signalImportant: "Důležité V seznamu příjemců není možné současně použít skupiny a čísla!",
gotify: "Gotify",
"Application Token": "Token aplikace",
"Server URL": "URL adresa serveru",
Priority: "Priorita",
slack: "Slack",
"Icon Emoji": "Ikona smajlíka",
"Channel Name": "Název kanálu",
"Uptime Kuma URL": "Uptime Kuma URL",
aboutWebhooks: "Více informací o Webhoocích naleznete na adrese: {0}",
aboutChannelName: "Pro vynechání Webhook kanálu zadejte jeho název do pole Název kanálu {0}. Příklad: #jiny-kanal",
aboutKumaURL: "Pokud ponecháte pole URL adresa Uptime Kuma prázdné, použije se domovská stránka GitHub projektu.",
emojiCheatSheet: "Tahák smajlíků: {0}",
"rocket.chat": "Rocket.Chat",
pushover: "Pushover",
pushy: "Pushy",
octopush: "Octopush",
promosms: "PromoSMS",
clicksendsms: "ClickSend SMS",
lunasea: "LunaSea",
apprise: "Apprise (podpora více než 50 oznamovacích služeb)",
GoogleChat: "Google Chat (pouze Google Workspace)",
pushbullet: "Pushbullet",
line: "Line Messenger",
mattermost: "Mattermost",
"User Key": "Klíč uživatele",
Device: "Zařízení",
"Message Title": "Nadpis zprávy",
"Notification Sound": "Zvuk oznámení",
"More info on:": "Více informací naleznete na adrese: {0}",
pushoverDesc1: "Výchozí časový limit pro emergency prioritu (2) je 30 sekund mezi opakovanými pokusy a vyprší po 1 hodině.",
pushoverDesc2: "Pokud chcete odesílat oznámení do různých zařízení, vyplňte pole Zařízení.",
"SMS Type": "Typ SMS",
octopushTypePremium: "Premium (rychlé doporučeno pro upozornění)",
octopushTypeLowCost: "Nízké náklady (pomalé někdy blokované operátorem)",
checkPrice: "Ceny {0} zjistíte na adrese:",
apiCredentials: "API přihlašovací údaje",
octopushLegacyHint: "Používáte starší verzi Octopush (2011-2020) nebo novou verzi?",
"Check octopush prices": "Ceny octopush naleznete na adrese {0}.",
octopushPhoneNumber: "Telefonní číslo (v mezinárodním formátu, např: +42012345678) ",
octopushSMSSender: "Odesílatel SMS: 3-11 alfanumerických znaků a mezera (a-zA-Z0-9)",
"LunaSea Device ID": "ID zařízení LunaSea",
"Apprise URL": "Apprise URL",
"Example:": "Příklad: {0}",
"Read more:": "Více informací: {0}",
"Status:": "Stav: {0}",
"Read more": "Více informací",
appriseInstalled: "Apprise je nainstalován.",
appriseNotInstalled: "Apprise není nainstalován. {0}",
"Access Token": "Přístupový token",
"Channel access token": "Přístupový token ke kanálu",
"Line Developers Console": "Konzole Line Developers",
lineDevConsoleTo: "Konzole Line Developers - {0}",
"Basic Settings": "Obecné nastavení",
"User ID": "ID uživatele",
"Messaging API": "Messaging API",
wayToGetLineChannelToken: "Nejprve otevřete {0}, vytvořte poskytovatele a kanál (Messaging API). Poté můžete získat přístupový token ke kanálu a ID uživatele, v sekci uvedené výše.",
"Icon URL": "URL adresa ikony",
aboutIconURL: "Pro přepsání výchozího profilového obrázku můžete do pole \"URL adresa ikony\" zadat odkaz na obrázek. Nebude použito, pokud je nastavena ikona smajlíka.",
aboutMattermostChannelName: "Výchozí kanál, do kterého jsou zasílány Webhook příspěvky, můžete přepsat zadáním názvu kanálu do pole \"Název kanálu\". Tato možnost musí být povolena v nastavení Mattermost Webhooku. Příklad: #jiny-kanal",
matrix: "Matrix",
promosmsTypeEco: "SMS ECO levné, ale pomalé a často přetížené. Omezeno pouze na polské příjemce.",
promosmsTypeFlash: "SMS FLASH zpráva se automaticky zobrazí na zařízení příjemce. Omezeno pouze na polské příjemce.",
promosmsTypeFull: "SMS FULL prémiová úroveň SMS. Můžete definovat odesílatele (vyžadována registrace jména). Spolehlivý pro výstrahy.",
promosmsTypeSpeed: "SMS SPEED nejvyšší priorita v systému. Velmi rychlé a spolehlivé, ale nákladné (přibližně dvojnásobek ceny SMS FULL).",
promosmsPhoneNumber: "Telefonní číslo (polští příjemci mohou vynechat telefonní předvolbu)",
promosmsSMSSender: "Odesílatel SMS: Předem zaregistrovaný název nebo jeden z výchozích: InfoSMS, SMS Info, MaxSMS, INFO, SMS",
"Feishu WebHookUrl": "Feishu WebHookURL",
matrixHomeserverURL: "URL adresa domácího serveru (s http(s):// a volitelně portem)",
"Internal Room Id": "ID interní místnosti",
matrixDesc1: "ID interní místnosti naleznete v Matrix klientovi v rozšířeném nastavení místnosti. Mělo by být ve tvaru !QMdRCpUIfLwsfjxye6:home.server.",
matrixDesc2: "Důrazně doporučujeme vytvořit nového uživatele a nepoužívat váš vlastní přístupový token uživatele Matrix. Pomocí něj je možné získat přístup k vašemu účtu a všem místnostem, ke kterým jste se připojili. Místo toho vytvořte nového uživatele a pozvěte jej pouze do místnosti, do které chcete oznámení dostávat. Přístupový token můžete získat spuštěním {0}",
Method: "Metoda",
Body: "Tělo",
Headers: "Hlavičky",
PushUrl: "Push URL",
HeadersInvalidFormat: "The request headers are not valid JSON: ",
BodyInvalidFormat: "The request body is not valid JSON: ",
"Monitor History": "Historie dohledu",
clearDataOlderThan: "Historie dohledu bude uchovávána po dobu {0} dní.",
PasswordsDoNotMatch: "Hesla se neshodují.",
records: "záznamů",
"One record": "Jeden záznam",
steamApiKeyDescription: "For monitoring a Steam Game Server you need a Steam Web-API key. You can register your API key here: ",
"Current User": "Aktuálně přihlášený uživatel",
recent: "Poslední",
Done: "Hotovo",
Info: "Informace",
Security: "Bezpečnost",
"Steam API Key": "API klíč služby Steam",
"Shrink Database": "Zmenšit databázi",
"Pick a RR-Type...": "Vyberte typ záznamu o prostředku…",
"Pick Accepted Status Codes...": "Vyberte stavové kódy, které chcete akceptovat…",
Default: "Standardní",
"HTTP Options": "Možnosti protokolu HTTP",
"Create Incident": "Vytvořit incident",
Title: "Předmět",
Content: "Obsah",
Style: "Styl",
info: "informace",
warning: "upozornění",
danger: "riziko",
primary: "primární",
light: "světlý",
dark: "tmavý",
Post: "Publikovat",
"Please input title and content": "Zadejte prosím název a obsah",
Created: "Vytvořen",
"Last Updated": "Poslední aktualizace",
Unpin: "Odepnout",
"Switch to Light Theme": "Přepnout na světlý motiv",
"Switch to Dark Theme": "Přepnutí na tmavý motiv",
"Show Tags": "Zobrazit štítky",
"Hide Tags": "Skrýt štítky",
Description: "Popis",
"No monitors available.": "Není dostupný žádný dohled.",
"Add one": "Přidat jeden",
"No Monitors": "Žádný dohled",
"Untitled Group": "Skupina bez názvu",
Services: "Služby",
Discard: "Zahodit",
Cancel: "Zrušit",
"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",
serwersmsAPIUser: "API uživatelské jméno (včetně předpony webapi_)",
serwersmsAPIPassword: "API heslo",
serwersmsPhoneNumber: "Telefonní číslo",
serwersmsSenderName: "Odesílatel SMS (registrováno prostřednictvím zákaznického portálu)",
"stackfield": "Stackfield",
smtpDkimSettings: "Nastavení DKIM",
smtpDkimDesc: "Informace o použití naleznete v {0} Nodemailer DKIM.",
documentation: "dokumentaci",
smtpDkimDomain: "Název domény",
smtpDkimKeySelector: "Selector klíče",
smtpDkimPrivateKey: "Privátní klíč",
smtpDkimHashAlgo: "Hashovací algoritmus (volitelné)",
smtpDkimheaderFieldNames: "Podepisovat tyto hlavičky (volitelné)",
smtpDkimskipFields: "Nepodepisovat tyto hlavičky (volitelné)",
};

@ -98,7 +98,7 @@ export default {
keywordDescription: "Søg efter et søgeord i almindelig HTML- eller JSON -output. Bemærk, at der skelnes mellem store og små bogstaver.", keywordDescription: "Søg efter et søgeord i almindelig HTML- eller JSON -output. Bemærk, at der skelnes mellem store og små bogstaver.",
deleteMonitorMsg: "Er du sikker på, at du vil slette overvågeren?", deleteMonitorMsg: "Er du sikker på, at du vil slette overvågeren?",
deleteNotificationMsg: "Er du sikker på, at du vil slette denne underretning for alle overvågere? ", deleteNotificationMsg: "Er du sikker på, at du vil slette denne underretning for alle overvågere? ",
resoverserverDescription: "Cloudflare er standardserveren, den kan til enhver tid ændres.", resolverserverDescription: "Cloudflare er standardserveren, den kan til enhver tid ændres.",
"Resolver Server": "Navne-server", "Resolver Server": "Navne-server",
rrtypeDescription: "Vælg den type RR, du vil overvåge.", rrtypeDescription: "Vælg den type RR, du vil overvåge.",
"Last Result": "Seneste resultat", "Last Result": "Seneste resultat",
@ -180,6 +180,7 @@ export default {
"Edit Status Page": "Rediger Statusside", "Edit Status Page": "Rediger Statusside",
"Go to Dashboard": "Gå til Betjeningspanel", "Go to Dashboard": "Gå til Betjeningspanel",
"Status Page": "Statusside", "Status Page": "Statusside",
"Status Pages": "Statusside",
telegram: "Telegram", telegram: "Telegram",
webhook: "Webhook", webhook: "Webhook",
smtp: "Email (SMTP)", smtp: "Email (SMTP)",

@ -4,14 +4,14 @@ export default {
Dashboard: "Dashboard", Dashboard: "Dashboard",
"New Update": "Update verfügbar", "New Update": "Update verfügbar",
Language: "Sprache", Language: "Sprache",
Appearance: "Erscheinung", Appearance: "Erscheinungsbild",
Theme: "Thema", Theme: "Erscheinungsbild",
General: "Allgemein", General: "Allgemein",
Version: "Version", Version: "Version",
"Check Update On GitHub": "Auf GitHub nach Updates suchen", "Check Update On GitHub": "Auf GitHub nach Updates suchen",
List: "Liste", List: "Liste",
Add: "Hinzufügen", Add: "Hinzufügen",
"Add New Monitor": "Neuer Monitor", "Add New Monitor": "Neuen Monitor hinzufügen",
"Quick Stats": "Übersicht", "Quick Stats": "Übersicht",
Up: "Aktiv", Up: "Aktiv",
Down: "Inaktiv", Down: "Inaktiv",
@ -49,20 +49,20 @@ export default {
retriesDescription: "Maximale Anzahl von Wiederholungen, bevor der Dienst als inaktiv markiert und eine Benachrichtigung gesendet wird.", retriesDescription: "Maximale Anzahl von Wiederholungen, bevor der Dienst als inaktiv markiert und eine Benachrichtigung gesendet wird.",
Advanced: "Erweitert", Advanced: "Erweitert",
ignoreTLSError: "Ignoriere TLS-/SSL-Fehler von Webseiten", ignoreTLSError: "Ignoriere TLS-/SSL-Fehler von Webseiten",
"Upside Down Mode": "Invertierter Modus", "Upside Down Mode": "Umgekehrter Modus",
upsideDownModeDescription: "Im invertierten Modus wird der Dienst als inaktiv angezeigt, wenn er erreichbar ist.", upsideDownModeDescription: "Im umgekehrten Modus wird der Dienst als inaktiv angezeigt, wenn er erreichbar ist.",
"Max. Redirects": "Max. Weiterleitungen", "Max. Redirects": "Max. Weiterleitungen",
maxRedirectDescription: "Maximale Anzahl von Weiterleitungen, denen gefolgt werden soll. Auf 0 setzen, um Weiterleitungen zu deaktivieren.", maxRedirectDescription: "Maximale Anzahl von Weiterleitungen, denen gefolgt werden soll. Auf 0 setzen, um Weiterleitungen zu deaktivieren.",
"Accepted Status Codes": "Erlaubte HTTP-Statuscodes", "Accepted Status Codes": "Erlaubte HTTP-Statuscodes",
acceptedStatusCodesDescription: "Wähle die Statuscodes aus, welche trotzdem als erfolgreich gewertet werden sollen.", acceptedStatusCodesDescription: "Statuscodes auswählen, die als erfolgreiche Verbindung gelten sollen.",
Save: "Speichern", Save: "Speichern",
Notifications: "Benachrichtigungen", Notifications: "Benachrichtigungen",
"Not available, please setup.": "Keine verfügbar, bitte einrichten.", "Not available, please setup.": "Nicht verfügbar, bitte einrichten.",
"Setup Notification": "Benachrichtigung einrichten", "Setup Notification": "Benachrichtigung einrichten",
Light: "Hell", Light: "Hell",
Dark: "Dunkel", Dark: "Dunkel",
Auto: "Auto", Auto: "Auto",
"Theme - Heartbeat Bar": "Thema - Zeitleiste", "Theme - Heartbeat Bar": "Erscheinungsbild - Zeitleiste",
Normal: "Normal", Normal: "Normal",
Bottom: "Unten", Bottom: "Unten",
None: "Keine", None: "Keine",
@ -71,15 +71,15 @@ export default {
"Allow indexing": "Indizierung zulassen", "Allow indexing": "Indizierung zulassen",
"Discourage search engines from indexing site": "Halte Suchmaschinen von der Indexierung der Seite ab", "Discourage search engines from indexing site": "Halte Suchmaschinen von der Indexierung der Seite ab",
"Change Password": "Passwort ändern", "Change Password": "Passwort ändern",
"Current Password": "Derzeitiges Passwort", "Current Password": "Aktuelles Passwort",
"New Password": "Neues Passwort", "New Password": "Neues Passwort",
"Repeat New Password": "Neues Passwort wiederholen", "Repeat New Password": "Neues Passwort wiederholen",
passwordNotMatchMsg: "Passwörter stimmen nicht überein. ", passwordNotMatchMsg: "Passwörter stimmen nicht überein.",
"Update Password": "Passwort aktualisieren", "Update Password": "Passwort aktualisieren",
"Disable Auth": "Authentifizierung deaktivieren", "Disable Auth": "Authentifizierung deaktivieren",
"Enable Auth": "Authentifizierung aktivieren", "Enable Auth": "Authentifizierung aktivieren",
Logout: "Ausloggen", Logout: "Ausloggen",
notificationDescription: "Weise den Monitor(en) eine Benachrichtigung zu, damit diese Funktion greift.", notificationDescription: "Benachrichtigungen müssen einem Monitor zugewiesen werden, damit diese funktionieren.",
Leave: "Verlassen", Leave: "Verlassen",
"I understand, please disable": "Ich verstehe, bitte deaktivieren", "I understand, please disable": "Ich verstehe, bitte deaktivieren",
Confirm: "Bestätigen", Confirm: "Bestätigen",
@ -87,7 +87,7 @@ export default {
No: "Nein", No: "Nein",
Username: "Benutzername", Username: "Benutzername",
Password: "Passwort", Password: "Passwort",
"Remember me": "Passwort merken", "Remember me": "Angemeldet bleiben",
Login: "Einloggen", Login: "Einloggen",
"No Monitors, please": "Keine Monitore, bitte", "No Monitors, please": "Keine Monitore, bitte",
"add one": "hinzufügen", "add one": "hinzufügen",
@ -98,7 +98,7 @@ export default {
keywordDescription: "Ein Suchwort in der HTML- oder JSON-Ausgabe finden. Bitte beachte: es wird zwischen Groß-/Kleinschreibung unterschieden.", keywordDescription: "Ein Suchwort in der HTML- oder JSON-Ausgabe finden. Bitte beachte: es wird zwischen Groß-/Kleinschreibung unterschieden.",
deleteMonitorMsg: "Bist du sicher, dass du den Monitor löschen möchtest?", deleteMonitorMsg: "Bist du sicher, dass du den Monitor löschen möchtest?",
deleteNotificationMsg: "Möchtest du diese Benachrichtigung wirklich für alle Monitore löschen?", deleteNotificationMsg: "Möchtest du diese Benachrichtigung wirklich für alle Monitore löschen?",
resoverserverDescription: "Cloudflare ist als der Standardserver festgelegt, dieser kann jederzeit geändern werden.", resolverserverDescription: "Cloudflare ist als der Standardserver festgelegt. Dieser kann jederzeit geändert werden.",
"Resolver Server": "Auflösungsserver", "Resolver Server": "Auflösungsserver",
rrtypeDescription: "Wähle den RR-Typ aus, welchen du überwachen möchtest.", rrtypeDescription: "Wähle den RR-Typ aus, welchen du überwachen möchtest.",
"Last Result": "Letztes Ergebnis", "Last Result": "Letztes Ergebnis",
@ -110,8 +110,8 @@ export default {
Heartbeats: "Statistiken", Heartbeats: "Statistiken",
confirmClearStatisticsMsg: "Bist du dir sicher, dass du ALLE Statistiken löschen möchtest?", confirmClearStatisticsMsg: "Bist du dir sicher, dass du ALLE Statistiken löschen möchtest?",
"Create your admin account": "Erstelle dein Admin-Konto", "Create your admin account": "Erstelle dein Admin-Konto",
"Repeat Password": "Wiederhole das Passwort", "Repeat Password": "Passwort erneut eingeben",
"Resource Record Type": "Resource Record Type", "Resource Record Type": "Ressourcen Record Typ",
Export: "Export", Export: "Export",
Import: "Import", Import: "Import",
respTime: "Antw.-Zeit (ms)", respTime: "Antw.-Zeit (ms)",
@ -148,7 +148,7 @@ export default {
Token: "Token", Token: "Token",
"Show URI": "URI anzeigen", "Show URI": "URI anzeigen",
Tags: "Tags", Tags: "Tags",
"Add New below or Select...": "Bestehenden Tag auswählen oder neuen hinzufügen...", "Add New below or Select...": "Einen bestehenden Tag auswählen oder neuen hinzufügen...",
"Tag with this name already exist.": "Ein Tag mit diesem Namen existiert bereits.", "Tag with this name already exist.": "Ein Tag mit diesem Namen existiert bereits.",
"Tag with this value already exist.": "Ein Tag mit diesem Wert existiert bereits.", "Tag with this value already exist.": "Ein Tag mit diesem Wert existiert bereits.",
color: "Farbe", color: "Farbe",
@ -162,8 +162,8 @@ export default {
Purple: "Lila", Purple: "Lila",
Pink: "Pink", Pink: "Pink",
"Search...": "Suchen...", "Search...": "Suchen...",
"Heartbeat Retry Interval": "Heartbeat-Wiederholungsintervall", "Heartbeat Retry Interval": "Überprüfungsintervall",
retryCheckEverySecond: "Versuche alle {0} Sekunden", retryCheckEverySecond: "Alle {0} Sekunden neu versuchen",
"Import Backup": "Backup importieren", "Import Backup": "Backup importieren",
"Export Backup": "Backup exportieren", "Export Backup": "Backup exportieren",
"Avg. Ping": "Durchschn. Ping", "Avg. Ping": "Durchschn. Ping",
@ -179,6 +179,7 @@ export default {
"Edit Status Page": "Bearbeite Status-Seite", "Edit Status Page": "Bearbeite Status-Seite",
"Go to Dashboard": "Gehe zum Dashboard", "Go to Dashboard": "Gehe zum Dashboard",
"Status Page": "Status-Seite", "Status Page": "Status-Seite",
"Status Pages": "Status-Seite",
telegram: "Telegram", telegram: "Telegram",
webhook: "Webhook", webhook: "Webhook",
smtp: "E-Mail (SMTP)", smtp: "E-Mail (SMTP)",
@ -202,7 +203,7 @@ export default {
"Push URL": "Push URL", "Push URL": "Push URL",
needPushEvery: "Du solltest diese URL alle {0} Sekunden aufrufen", needPushEvery: "Du solltest diese URL alle {0} Sekunden aufrufen",
pushOptionalParams: "Optionale Parameter: {0}", pushOptionalParams: "Optionale Parameter: {0}",
defaultNotificationName: "Meine {notification} Alarm ({number})", defaultNotificationName: "Mein {notification} Alarm ({number})",
here: "hier", here: "hier",
Required: "Erforderlich", Required: "Erforderlich",
"Bot Token": "Bot Token", "Bot Token": "Bot Token",
@ -214,23 +215,23 @@ export default {
chatIDNotFound: "Chat-ID wurde nicht gefunden: bitte sende zuerst eine Nachricht an diesen Bot", chatIDNotFound: "Chat-ID wurde nicht gefunden: bitte sende zuerst eine Nachricht an diesen Bot",
"Post URL": "Post URL", "Post URL": "Post URL",
"Content Type": "Content Type", "Content Type": "Content Type",
webhookJsonDesc: "{0} ist gut für alle modernen HTTP-Server sowie Express.js", webhookJsonDesc: "{0} ist gut für alle modernen HTTP-Server, wie z.B. Express.js, geeignet",
webhookFormDataDesc: "{multipart} ist gut für PHP. Die JSON muss mit {decodeFunction} geparst werden", webhookFormDataDesc: "{multipart} ist gut für PHP. Das JSON muss mit {decodeFunction} verarbeitet werden",
secureOptionNone: "Keine / STARTTLS (25, 587)", secureOptionNone: "Keine / STARTTLS (25, 587)",
secureOptionTLS: "TLS (465)", secureOptionTLS: "TLS (465)",
"Ignore TLS Error": "TLS-Fehler ignorieren", "Ignore TLS Error": "TLS-Fehler ignorieren",
"From Email": "Von Email", "From Email": "Absender E-Mail",
emailCustomSubject: "Benutzerdefinierter Betreff", emailCustomSubject: "Benutzerdefinierter Betreff",
"To Email": "Zu Email", "To Email": "Empfänger E-Mail",
smtpCC: "CC", smtpCC: "CC",
smtpBCC: "BCC", smtpBCC: "BCC",
"Discord Webhook URL": "Discord Webhook URL", "Discord Webhook URL": "Discord Webhook URL",
wayToGetDiscordURL: "Du kannst diesen erhalten, indem du zu den Servereinstellungen gehst -> Integrationen -> Neuer Webhook", wayToGetDiscordURL: "Du kannst diese erhalten, indem du zu den Servereinstellungen gehst -> Integrationen -> Neuer Webhook",
"Bot Display Name": "Bot-Anzeigename", "Bot Display Name": "Bot-Anzeigename",
"Prefix Custom Message": "Benutzerdefinierter Nachrichten Präfix", "Prefix Custom Message": "Benutzerdefinierter Nachrichten Präfix",
"Hello @everyone is...": "Hallo {'@'}everyone ist...", "Hello @everyone is...": "Hallo {'@'}everyone ist...",
"Webhook URL": "Webhook URL", "Webhook URL": "Webhook URL",
wayToGetTeamsURL: "Hier erfährst du, wie eine Webhook-URL erstellt werden kann {0}.", wayToGetTeamsURL: "Wie eine Webhook-URL erstellt werden kann, erfährst du {0}.",
Number: "Nummer", Number: "Nummer",
Recipients: "Empfänger", Recipients: "Empfänger",
needSignalAPI: "Es wird ein Signal Client mit REST-API benötigt.", needSignalAPI: "Es wird ein Signal Client mit REST-API benötigt.",
@ -243,22 +244,22 @@ export default {
"Channel Name": "Kanalname", "Channel Name": "Kanalname",
"Uptime Kuma URL": "Uptime Kuma URL", "Uptime Kuma URL": "Uptime Kuma URL",
aboutWebhooks: "Weitere Informationen zu Webhooks auf: {0}", aboutWebhooks: "Weitere Informationen zu Webhooks auf: {0}",
aboutChannelName: "Gebe den Kanalnamen ein auf {0} Feld Kanalname, wenn du den Webhook-Kanal umgehen möchtest. Ex: #other-channel", aboutChannelName: "Gebe den Kanalnamen ein in {0} Feld Kanalname, falls du den Webhook-Kanal umgehen möchtest. Ex: #other-channel",
aboutKumaURL: "Wenn das Feld für die Uptime Kuma URL leer gelassen wird, wird es standardmäßig die GitHub Projekt Seite verwenden.", aboutKumaURL: "Wenn das Feld für die Uptime Kuma URL leer gelassen wird, wird standardmäßig die GitHub Projekt Seite verwendet.",
emojiCheatSheet: "Emoji Cheat Sheet: {0}", emojiCheatSheet: "Emoji Cheat Sheet: {0}",
"User Key": "Benutzerschlüssel", "User Key": "Benutzerschlüssel",
Device: "Gerät", Device: "Gerät",
"Message Title": "Nachrichtentitel", "Message Title": "Nachrichtentitel",
"Notification Sound": "Benachrichtigungston", "Notification Sound": "Benachrichtigungston",
"More info on:": "Mehr Infos auf: {0}", "More info on:": "Mehr Infos auf: {0}",
pushoverDesc1: "Notfallpriorität (2) hat Standardmäßig 30 Sekunden Auszeit, zwischen den Versuchen und läuft nach 1 Stunde ab.", pushoverDesc1: "Notfallpriorität (2) hat standardmäßig 30 Sekunden Auszeit zwischen den Versuchen und läuft nach 1 Stunde ab.",
pushoverDesc2: "Fülle das Geräte Feld aus, wenn du Benachrichtigungen an verschiedene Geräte senden möchtest.", pushoverDesc2: "Fülle das Geräte Feld aus, wenn du Benachrichtigungen an verschiedene Geräte senden möchtest.",
"SMS Type": "SMS Typ", "SMS Type": "SMS Typ",
octopushTypePremium: "Premium (Schnell - zur Benachrichtigung empfohlen)", octopushTypePremium: "Premium (Schnell - zur Benachrichtigung empfohlen)",
octopushTypeLowCost: "Kostengünstig (Langsam - manchmal vom Betreiber gesperrt)", octopushTypeLowCost: "Kostengünstig (Langsam - manchmal vom Betreiber gesperrt)",
checkPrice: "Prüfe {0} Preise:", checkPrice: "Prüfe {0} Preise:",
octopushLegacyHint: "Verwendest du die Legacy-Version von Octopush (2011-2020) oder die neue Version?", octopushLegacyHint: "Verwendest du die Legacy-Version von Octopush (2011-2020) oder die neue Version?",
"Check octopush prices": "Überprüfe die Oktopush Preise {0}.", "Check octopush prices": "Vergleiche die Oktopush Preise {0}.",
octopushPhoneNumber: "Telefonnummer (Internationales Format, z.B : +49612345678) ", octopushPhoneNumber: "Telefonnummer (Internationales Format, z.B : +49612345678) ",
octopushSMSSender: "Name des SMS-Absenders : 3-11 alphanumerische Zeichen und Leerzeichen (a-zA-Z0-9)", octopushSMSSender: "Name des SMS-Absenders : 3-11 alphanumerische Zeichen und Leerzeichen (a-zA-Z0-9)",
"LunaSea Device ID": "LunaSea Geräte ID", "LunaSea Device ID": "LunaSea Geräte ID",
@ -279,24 +280,24 @@ export default {
wayToGetLineChannelToken: "Rufe zuerst {0} auf, erstelle dann einen Provider und Channel (Messaging API). Als nächstes kannst du den Channel access token und die User ID aus den oben genannten Menüpunkten abrufen.", wayToGetLineChannelToken: "Rufe zuerst {0} auf, erstelle dann einen Provider und Channel (Messaging API). Als nächstes kannst du den Channel access token und die User ID aus den oben genannten Menüpunkten abrufen.",
"Icon URL": "Icon URL", "Icon URL": "Icon URL",
aboutIconURL: "Du kannst einen Link zu einem Bild in 'Icon URL' übergeben um das Standardprofilbild zu überschreiben. Wird nicht verwendet, wenn ein Icon Emoji gesetzt ist.", aboutIconURL: "Du kannst einen Link zu einem Bild in 'Icon URL' übergeben um das Standardprofilbild zu überschreiben. Wird nicht verwendet, wenn ein Icon Emoji gesetzt ist.",
aboutMattermostChannelName: "Du kannst den Standardkanal, auf dem der Webhook postet überschreiben, indem der Kanalnamen in das Feld 'Channel Name' eingeben wird. Dies muss in den Mattermost Webhook-Einstellungen aktiviert werden. Ex: #other-channel", aboutMattermostChannelName: "Du kannst den Standardkanal, auf dem der Webhook gesendet wird überschreiben, indem der Kanalnamen in das Feld 'Channel Name' eingeben wird. Dies muss in den Mattermost Webhook-Einstellungen aktiviert werden. Ex: #other-channel",
matrix: "Matrix", matrix: "Matrix",
promosmsTypeEco: "SMS ECO - billig, aber langsam und oft überladen. Nur auf polnische Empfänger beschränkt.", promosmsTypeEco: "SMS ECO - billig, aber langsam und oft überladen. Auf polnische Empfänger beschränkt.",
promosmsTypeFlash: "SMS FLASH - Die Nachricht wird automatisch auf dem Empfängergerät angezeigt. Nur auf polnische Empfänger beschränkt.", promosmsTypeFlash: "SMS FLASH - Die Nachricht wird automatisch auf dem Empfängergerät angezeigt. Auf polnische Empfänger beschränkt.",
promosmsTypeFull: "SMS FULL - Premium Stufe von SMS, es kann der Absendernamen verwendet werden (Der Name musst zuerst registriert werden). Zuverlässig für Warnungen.", promosmsTypeFull: "SMS FULL - Premium Stufe von SMS, es kann der Absendernamen verwendet werden (Der Name musst zuerst registriert werden). Zuverlässig für Warnungen.",
promosmsTypeSpeed: "SMS SPEED - Höchste Priorität im System. Sehr schnell und zuverlässig, aber teuer (Ungefähr das doppelte von SMS FULL).", promosmsTypeSpeed: "SMS SPEED - Höchste Priorität im System. Sehr schnell und zuverlässig, aber teuer (Ungefähr das doppelte von SMS FULL).",
promosmsPhoneNumber: "Phone number (Für polnische Empfänger können die Vorwahlen übersprungen werden)", promosmsPhoneNumber: "Telefonnummer (für polnische Empfänger können die Vorwahlen übersprungen werden)",
promosmsSMSSender: "Name des SMS-Absenders : vorregistrierter Name oder einer der Standardwerte: InfoSMS, SMS Info, MaxSMS, INFO, SMS", promosmsSMSSender: "Name des SMS-Absenders : vorregistrierter Name oder einer der Standardwerte: InfoSMS, SMS Info, MaxSMS, INFO, SMS",
"Feishu WebHookUrl": "Feishu Webhook URL", "Feishu WebHookUrl": "Feishu Webhook URL",
matrixHomeserverURL: "Heimserver URL (mit http(s):// und optionalen Ports)", matrixHomeserverURL: "Heimserver URL (mit http(s):// und optionalen Ports)",
"Internal Room Id": "Interne Raum-ID", "Internal Room Id": "Interne Raum-ID",
matrixDesc1: "Die interne Raum-ID findest du im erweiterten Bereich der Raumeinstellungen im Matrix-Client. Es sollte es aussehen wie z.B. !QMdRCpUIfLwsfjxye6:home.server.", matrixDesc1: "Die interne Raum-ID findest du im erweiterten Bereich der Raumeinstellungen im Matrix-Client. Es sollte aussehen wie z.B. !QMdRCpUIfLwsfjxye6:home.server.",
matrixDesc2: "Es wird dringend empfohlen, dass ein neuen Benutzer erstellt wird und nicht den Zugriffstoken deines eigenen Matrix-Benutzers verwendest. Anderfalls ermöglicht es vollen Zugriff auf dein Konto und alle Räume, denen du beigetreten bist. Erstelle stattdessen einen neuen Benutzer und lade ihn nur in den Raum ein, in dem du die Benachrichtigung erhalten möchtest. Du kannst den Zugriffstoken erhalten, indem du folgendes ausführst {0}", matrixDesc2: "Es wird dringend empfohlen einen neuen Benutzer anzulegen und nicht den Zugriffstoken deines eigenen Matrix-Benutzers zu verwenden. Anderenfalls ermöglicht es vollen Zugriff auf dein Konto und alle Räume, denen du beigetreten bist. Erstelle stattdessen einen neuen Benutzer und lade ihn nur in den Raum ein, in dem du die Benachrichtigung erhalten möchtest. Du kannst den Zugriffstoken erhalten, indem du Folgendes ausführst {0}",
Method: "Method", Method: "Method",
Body: "Body", Body: "Body",
Headers: "Headers", Headers: "Headers",
PushUrl: "Push URL", PushUrl: "Push URL",
HeadersInvalidFormat: "Die Header ist kein gültiges JSON: ", HeadersInvalidFormat: "Der Header ist kein gültiges JSON: ",
BodyInvalidFormat: "Der Body ist kein gültiges JSON: ", BodyInvalidFormat: "Der Body ist kein gültiges JSON: ",
"Monitor History": "Monitor Verlauf", "Monitor History": "Monitor Verlauf",
clearDataOlderThan: "Bewahre die Monitor-Verlaufsdaten für {0} Tage auf.", clearDataOlderThan: "Bewahre die Monitor-Verlaufsdaten für {0} Tage auf.",
@ -350,4 +351,15 @@ export default {
serwersmsPhoneNumber: "Telefonnummer", serwersmsPhoneNumber: "Telefonnummer",
serwersmsSenderName: "Name des SMS-Absenders (über Kundenportal registriert)", serwersmsSenderName: "Name des SMS-Absenders (über Kundenportal registriert)",
"stackfield": "Stackfield", "stackfield": "Stackfield",
clicksendsms: "ClickSend SMS",
apiCredentials: "API Zugangsdaten",
smtpDkimSettings: "DKIM Einstellungen",
smtpDkimDesc: "Details zur Konfiguration sind in der Nodemailer DKIM {0} zu finden.",
documentation: "Dokumentation",
smtpDkimDomain: "Domain Name",
smtpDkimKeySelector: "Schlüssel Auswahl",
smtpDkimPrivateKey: "Privater Schlüssel",
smtpDkimHashAlgo: "Hash-Algorithmus (Optional)",
smtpDkimheaderFieldNames: "Zu validierende Header-Schlüssel (optional)",
smtpDkimskipFields: "Zu ignorierende Header Schlüssel (optional)",
}; };

@ -13,7 +13,7 @@ export default {
pauseDashboardHome: "Pause", pauseDashboardHome: "Pause",
deleteMonitorMsg: "Are you sure want to delete this monitor?", deleteMonitorMsg: "Are you sure want to delete this monitor?",
deleteNotificationMsg: "Are you sure want to delete this notification for all monitors?", deleteNotificationMsg: "Are you sure want to delete this notification for all monitors?",
resoverserverDescription: "Cloudflare is the default server. You can change the resolver server anytime.", resolverserverDescription: "Cloudflare is the default server. You can change the resolver server anytime.",
rrtypeDescription: "Select the RR type you want to monitor", rrtypeDescription: "Select the RR type you want to monitor",
pauseMonitorMsg: "Are you sure want to pause?", pauseMonitorMsg: "Are you sure want to pause?",
enableDefaultNotificationDescription: "This notification will be enabled by default for new monitors. You can still disable the notification separately for each monitor.", enableDefaultNotificationDescription: "This notification will be enabled by default for new monitors. You can still disable the notification separately for each monitor.",
@ -183,6 +183,7 @@ export default {
"Edit Status Page": "Edit Status Page", "Edit Status Page": "Edit Status Page",
"Go to Dashboard": "Go to Dashboard", "Go to Dashboard": "Go to Dashboard",
"Status Page": "Status Page", "Status Page": "Status Page",
"Status Pages": "Status Pages",
defaultNotificationName: "My {notification} Alert ({number})", defaultNotificationName: "My {notification} Alert ({number})",
here: "here", here: "here",
Required: "Required", Required: "Required",
@ -238,6 +239,7 @@ export default {
"rocket.chat": "Rocket.Chat", "rocket.chat": "Rocket.Chat",
pushover: "Pushover", pushover: "Pushover",
pushy: "Pushy", pushy: "Pushy",
PushByTechulus: "Push by Techulus",
octopush: "Octopush", octopush: "Octopush",
promosms: "PromoSMS", promosms: "PromoSMS",
clicksendsms: "ClickSend SMS", clicksendsms: "ClickSend SMS",
@ -351,7 +353,7 @@ export default {
serwersmsAPIPassword: "API Password", serwersmsAPIPassword: "API Password",
serwersmsPhoneNumber: "Phone number", serwersmsPhoneNumber: "Phone number",
serwersmsSenderName: "SMS Sender Name (registered via customer portal)", serwersmsSenderName: "SMS Sender Name (registered via customer portal)",
"stackfield": "Stackfield", stackfield: "Stackfield",
smtpDkimSettings: "DKIM Settings", smtpDkimSettings: "DKIM Settings",
smtpDkimDesc: "Please refer to the Nodemailer DKIM {0} for usage.", smtpDkimDesc: "Please refer to the Nodemailer DKIM {0} for usage.",
documentation: "documentation", documentation: "documentation",
@ -361,4 +363,83 @@ export default {
smtpDkimHashAlgo: "Hash Algorithm (Optional)", smtpDkimHashAlgo: "Hash Algorithm (Optional)",
smtpDkimheaderFieldNames: "Header Keys to sign (Optional)", smtpDkimheaderFieldNames: "Header Keys to sign (Optional)",
smtpDkimskipFields: "Header Keys not to sign (Optional)", smtpDkimskipFields: "Header Keys not to sign (Optional)",
gorush: "Gorush",
alerta: "Alerta",
alertaApiEndpoint: "API Endpoint",
alertaEnvironment: "Environment",
alertaApiKey: "API Key",
alertaAlertState: "Alert State",
alertaRecoverState: "Recover State",
deleteStatusPageMsg: "Are you sure want to delete this status page?",
Proxies: "Proxies",
default: "Default",
enabled: "Enabled",
setAsDefault: "Set As Default",
deleteProxyMsg: "Are you sure want to delete this proxy for all monitors?",
proxyDescription: "Proxies must be assigned to a monitor to function.",
enableProxyDescription: "This proxy will not effect on monitor requests until it is activated. You can control temporarily disable the proxy from all monitors by activation status.",
setAsDefaultProxyDescription: "This proxy will be enabled by default for new monitors. You can still disable the proxy separately for each monitor.",
"Certificate Chain": "Certificate Chain",
Valid: "Valid",
Invalid: "Invalid",
AccessKeyId: "AccessKey ID",
SecretAccessKey: "AccessKey Secret",
PhoneNumbers: "PhoneNumbers",
TemplateCode: "TemplateCode",
SignName: "SignName",
"Sms template must contain parameters: ": "Sms template must contain parameters: ",
"Bark Endpoint": "Bark Endpoint",
WebHookUrl: "WebHookUrl",
SecretKey: "SecretKey",
"For safety, must use secret key": "For safety, must use secret key",
"Device Token": "Device Token",
Platform: "Platform",
iOS: "iOS",
Android: "Android",
Huawei: "Huawei",
High: "High",
Retry: "Retry",
Topic: "Topic",
"WeCom Bot Key": "WeCom Bot Key",
"Setup Proxy": "Setup Proxy",
"Proxy Protocol": "Proxy Protocol",
"Proxy Server": "Proxy Server",
"Proxy server has authentication": "Proxy server has authentication",
User: "User",
Installed: "Installed",
"Not installed": "Not installed",
Running: "Running",
"Not running": "Not running",
"Remove Token": "Remove Token",
Start: "Start",
Stop: "Stop",
"Uptime Kuma": "Uptime Kuma",
"Add New Status Page": "Add New Status Page",
Slug: "Slug",
"Accept characters:": "Accept characters:",
"startOrEndWithOnly": "Start or end with {0} only",
"No consecutive dashes": "No consecutive dashes",
Next: "Next",
"The slug is already taken. Please choose another slug.": "The slug is already taken. Please choose another slug.",
"No Proxy": "No Proxy",
"HTTP Basic Auth": "HTTP Basic Auth",
"New Status Page": "New Status Page",
"Page Not Found": "Page Not Found",
"Reverse Proxy": "Reverse Proxy",
Backup: "Backup",
About: "About",
wayToGetCloudflaredURL: "(Download cloudflared from {0})",
cloudflareWebsite: "Cloudflare Website",
"Message:": "Message:",
"Don't know how to get the token? Please read the guide:": "Don't know how to get the token? Please read the guide:",
"The current connection may be lost if you are currently connecting via Cloudflare Tunnel. Are you sure want to stop it? Type your current password to confirm it.": "The current connection may be lost if you are currently connecting via Cloudflare Tunnel. Are you sure want to stop it? Type your current password to confirm it.",
"Other Software": "Other Software",
"For example: nginx, Apache and Traefik.": "For example: nginx, Apache and Traefik.",
"Please read": "Please read",
"Subject:": "Subject:",
"Valid To:": "Valid To:",
"Days Remaining:": "Days Remaining:",
"Issuer:": "Issuer:",
"Fingerprint:": "Fingerprint:",
"No status pages": "No status pages",
}; };

@ -12,7 +12,7 @@ export default {
pauseDashboardHome: "Pausado", pauseDashboardHome: "Pausado",
deleteMonitorMsg: "¿Seguro que quieres eliminar este monitor?", deleteMonitorMsg: "¿Seguro que quieres eliminar este monitor?",
deleteNotificationMsg: "¿Seguro que quieres eliminar esta notificación para todos los monitores?", deleteNotificationMsg: "¿Seguro que quieres eliminar esta notificación para todos los monitores?",
resoverserverDescription: "Cloudflare es el servidor por defecto, puedes cambiar el servidor de resolución en cualquier momento.", resolverserverDescription: "Cloudflare es el servidor por defecto, puedes cambiar el servidor de resolución en cualquier momento.",
rrtypeDescription: "Selecciona el tipo de registro que quieres monitorizar", rrtypeDescription: "Selecciona el tipo de registro que quieres monitorizar",
pauseMonitorMsg: "¿Seguro que quieres pausar?", pauseMonitorMsg: "¿Seguro que quieres pausar?",
Settings: "Ajustes", Settings: "Ajustes",
@ -180,6 +180,7 @@ export default {
"Edit Status Page": "Editar página de estado", "Edit Status Page": "Editar página de estado",
"Go to Dashboard": "Ir al panel de control", "Go to Dashboard": "Ir al panel de control",
"Status Page": "Página de estado", "Status Page": "Página de estado",
"Status Pages": "Página de estado",
telegram: "Telegram", telegram: "Telegram",
webhook: "Webhook", webhook: "Webhook",
smtp: "Email (SMTP)", smtp: "Email (SMTP)",

@ -12,11 +12,12 @@ export default {
pauseDashboardHome: "Seisatud", pauseDashboardHome: "Seisatud",
deleteMonitorMsg: "Kas soovid eemaldada seire?", deleteMonitorMsg: "Kas soovid eemaldada seire?",
deleteNotificationMsg: "Kas soovid eemaldada selle teavitusteenuse kõikidelt seiretelt?", deleteNotificationMsg: "Kas soovid eemaldada selle teavitusteenuse kõikidelt seiretelt?",
resoverserverDescription: "Cloudflare on vaikimisi pöördserver.", resolverserverDescription: "Cloudflare on vaikimisi pöördserver.",
rrtypeDescription: "Vali kirje tüüp, mida soovid jälgida.", rrtypeDescription: "Vali kirje tüüp, mida soovid jälgida.",
pauseMonitorMsg: "Kas soovid peatada seire?", pauseMonitorMsg: "Kas soovid peatada seire?",
Settings: "Seaded", Settings: "Seaded",
"Status Page": "Ülevaade", "Status Page": "Ülevaade",
"Status Pages": "Ülevaated",
Dashboard: "Töölaud", Dashboard: "Töölaud",
"New Update": "Uuem tarkvara versioon on saadaval.", "New Update": "Uuem tarkvara versioon on saadaval.",
Language: "Keel", Language: "Keel",
@ -197,4 +198,10 @@ export default {
pushbullet: "Pushbullet", pushbullet: "Pushbullet",
line: "LINE", line: "LINE",
mattermost: "Mattermost", mattermost: "Mattermost",
alerta: "Alerta",
alertaApiEndpoint: "API otsik",
alertaEnvironment: "Keskkond",
alertaApiKey: "API võti",
alertaAlertState: "Häireseisund",
alertaRecoverState: "Taasta algolek",
}; };

@ -13,7 +13,7 @@ export default {
pauseDashboardHome: "متوقف شده", pauseDashboardHome: "متوقف شده",
deleteMonitorMsg: "آیا از حذف این مانیتور مطمئن هستید؟", deleteMonitorMsg: "آیا از حذف این مانیتور مطمئن هستید؟",
deleteNotificationMsg: "آیا مطمئن هستید که میخواهید این سرویس اطلاع‌رسانی را برای تمامی مانیتورها حذف کنید؟", deleteNotificationMsg: "آیا مطمئن هستید که میخواهید این سرویس اطلاع‌رسانی را برای تمامی مانیتورها حذف کنید؟",
resoverserverDescription: "سرویس CloudFlare به عنوان سرور پیش‌فرض استفاده می‌شود، شما میتوانید آنرا به هر سرور دیگری بعدا تغییر دهید.", resolverserverDescription: "سرویس CloudFlare به عنوان سرور پیش‌فرض استفاده می‌شود، شما میتوانید آنرا به هر سرور دیگری بعدا تغییر دهید.",
rrtypeDescription: "لطفا نوع Resource Record را انتخاب کنید.", rrtypeDescription: "لطفا نوع Resource Record را انتخاب کنید.",
pauseMonitorMsg: "آیا مطمئن هستید که میخواهید این مانیتور را متوقف کنید ؟", pauseMonitorMsg: "آیا مطمئن هستید که میخواهید این مانیتور را متوقف کنید ؟",
enableDefaultNotificationDescription: "برای هر مانیتور جدید، این سرویس اطلاع‌رسانی به صورت پیش‌فرض فعال خواهد شد. البته که شما میتوانید به صورت دستی آنرا برای هر مانیتور به صورت جداگانه غیر فعال کنید.", enableDefaultNotificationDescription: "برای هر مانیتور جدید، این سرویس اطلاع‌رسانی به صورت پیش‌فرض فعال خواهد شد. البته که شما میتوانید به صورت دستی آنرا برای هر مانیتور به صورت جداگانه غیر فعال کنید.",
@ -178,6 +178,7 @@ export default {
"Add a monitor": "اضافه کردن مانیتور", "Add a monitor": "اضافه کردن مانیتور",
"Edit Status Page": "ویرایش صفحه وضعیت", "Edit Status Page": "ویرایش صفحه وضعیت",
"Status Page": "صفحه وضعیت", "Status Page": "صفحه وضعیت",
"Status Pages": "صفحه وضعیت",
"Go to Dashboard": "رفتن به پیشخوان", "Go to Dashboard": "رفتن به پیشخوان",
"Uptime Kuma": "آپتایم کوما", "Uptime Kuma": "آپتایم کوما",
records: "مورد", records: "مورد",

@ -13,7 +13,7 @@ export default {
pauseDashboardHome: "En pause", pauseDashboardHome: "En pause",
deleteMonitorMsg: "Êtes-vous sûr de vouloir supprimer cette sonde ?", deleteMonitorMsg: "Êtes-vous sûr de vouloir supprimer cette sonde ?",
deleteNotificationMsg: "Êtes-vous sûr de vouloir supprimer ce type de notifications ? Une fois désactivée, les services qui l'utilisent ne pourront plus envoyer de notifications.", deleteNotificationMsg: "Êtes-vous sûr de vouloir supprimer ce type de notifications ? Une fois désactivée, les services qui l'utilisent ne pourront plus envoyer de notifications.",
resoverserverDescription: "Le DNS de Cloudflare est utilisé par défaut, mais vous pouvez le changer si vous le souhaitez.", resolverserverDescription: "Le DNS de Cloudflare est utilisé par défaut, mais vous pouvez le changer si vous le souhaitez.",
rrtypeDescription: "Veuillez sélectionner un type d'enregistrement DNS", rrtypeDescription: "Veuillez sélectionner un type d'enregistrement DNS",
pauseMonitorMsg: "Êtes-vous sûr de vouloir mettre en pause cette sonde ?", pauseMonitorMsg: "Êtes-vous sûr de vouloir mettre en pause cette sonde ?",
enableDefaultNotificationDescription: "Pour chaque nouvelle sonde, cette notification sera activée par défaut. Vous pouvez toujours désactiver la notification séparément pour chaque sonde.", enableDefaultNotificationDescription: "Pour chaque nouvelle sonde, cette notification sera activée par défaut. Vous pouvez toujours désactiver la notification séparément pour chaque sonde.",
@ -179,6 +179,7 @@ export default {
"Edit Status Page": "Modifier la page de statut", "Edit Status Page": "Modifier la page de statut",
"Go to Dashboard": "Accéder au tableau de bord", "Go to Dashboard": "Accéder au tableau de bord",
"Status Page": "Status Page", "Status Page": "Status Page",
"Status Pages": "Status Pages",
defaultNotificationName: "Ma notification {notification} numéro ({number})", defaultNotificationName: "Ma notification {notification} numéro ({number})",
here: "ici", here: "ici",
Required: "Requis", Required: "Requis",
@ -304,4 +305,9 @@ export default {
steamApiKeyDescription: "Pour surveiller un serveur Steam, vous avez besoin d'une clé Steam Web-API. Vous pouvez enregistrer votre clé ici : ", steamApiKeyDescription: "Pour surveiller un serveur Steam, vous avez besoin d'une clé Steam Web-API. Vous pouvez enregistrer votre clé ici : ",
"Current User": "Utilisateur actuel", "Current User": "Utilisateur actuel",
recent: "Récent", recent: "Récent",
alertaApiEndpoint: "API Endpoint",
alertaEnvironment: "Environement",
alertaApiKey: "Clé de l'API",
alertaAlertState: "État de l'Alerte",
alertaRecoverState: "État de récupération",
}; };

@ -12,7 +12,7 @@ export default {
keywordDescription: "Ključna riječ za pretragu, u obliku običnog HTML-a ili u JSON formatu. Pretraga je osjetljiva na velika i mala slova.", keywordDescription: "Ključna riječ za pretragu, u obliku običnog HTML-a ili u JSON formatu. Pretraga je osjetljiva na velika i mala slova.",
deleteMonitorMsg: "Jeste li sigurni da želite izbrisati monitor?", deleteMonitorMsg: "Jeste li sigurni da želite izbrisati monitor?",
deleteNotificationMsg: "Jeste li sigurni da želite izbrisati ovu obavijest za sve monitore?", deleteNotificationMsg: "Jeste li sigurni da želite izbrisati ovu obavijest za sve monitore?",
resoverserverDescription: "Cloudflare je zadani DNS poslužitelj. Možete to promijeniti u bilo kojem trenutku.", resolverserverDescription: "Cloudflare je zadani DNS poslužitelj. Možete to promijeniti u bilo kojem trenutku.",
rrtypeDescription: "Odaberite vrstu DNS zapisa o resursu kojeg želite pratiti", rrtypeDescription: "Odaberite vrstu DNS zapisa o resursu kojeg želite pratiti",
pauseMonitorMsg: "Jeste li sigurni da želite pauzirati?", pauseMonitorMsg: "Jeste li sigurni da želite pauzirati?",
enableDefaultNotificationDescription: "Ova će obavijesti biti omogućena za sve nove monitore. Možete ju ručno onemogućiti za pojedini monitor.", enableDefaultNotificationDescription: "Ova će obavijesti biti omogućena za sve nove monitore. Možete ju ručno onemogućiti za pojedini monitor.",
@ -183,6 +183,7 @@ export default {
"Edit Status Page": "Uredi Statusnu stranicu", "Edit Status Page": "Uredi Statusnu stranicu",
"Go to Dashboard": "Na Kontrolnu ploču", "Go to Dashboard": "Na Kontrolnu ploču",
"Status Page": "Statusna stranica", "Status Page": "Statusna stranica",
"Status Pages": "Statusne stranice",
defaultNotificationName: "Moja {number}. {notification} obavijest", defaultNotificationName: "Moja {number}. {notification} obavijest",
here: "ovdje", here: "ovdje",
Required: "Potrebno", Required: "Potrebno",
@ -338,13 +339,38 @@ export default {
"Hide Tags": "Sakrij oznake", "Hide Tags": "Sakrij oznake",
Description: "Opis", Description: "Opis",
"No monitors available.": "Nema dostupnih monitora.", "No monitors available.": "Nema dostupnih monitora.",
"Add one": "Add one",
"No Monitors": "Bez monitora",
"Add one": "Stvori jednog", "Add one": "Stvori jednog",
"No Monitors": "Bez monitora",
"Untitled Group": "Bezimena grupa", "Untitled Group": "Bezimena grupa",
Services: "Usluge", Services: "Usluge",
Discard: "Odbaci", Discard: "Odbaci",
Cancel: "Otkaži", Cancel: "Otkaži",
"Powered by": "Pokreće", "Powered by": "Pokreće",
Saved: "Spremljeno", Saved: "Spremljeno",
PushByTechulus: "Push by Techulus",
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",
serwersmsAPIUser: "API korisničko ime (uključujući webapi_ prefiks)",
serwersmsAPIPassword: "API lozinka",
serwersmsPhoneNumber: "Broj telefona",
serwersmsSenderName: "Ime SMS pošiljatelja (registrirano preko korisničkog portala)",
stackfield: "Stackfield",
smtpDkimSettings: "DKIM postavke",
smtpDkimDesc: "Za više informacija, postoji Nodemailer DKIM {0}.",
documentation: "dokumentacija",
smtpDkimDomain: "Domena",
smtpDkimKeySelector: "Odabir ključa",
smtpDkimPrivateKey: "Privatni ključ",
smtpDkimHashAlgo: "Hash algoritam (neobavezno)",
smtpDkimheaderFieldNames: "Ključevi zaglavlja za potpis (neobavezno)",
smtpDkimskipFields: "Ključevi zaglavlja koji se neće potpisati (neobavezno)",
gorush: "Gorush",
alerta: "Alerta",
alertaApiEndpoint: "Krajnja točka API-ja (Endpoint)",
alertaEnvironment: "Okruženje (Environment)",
alertaApiKey: "API ključ",
alertaAlertState: "Stanje upozorenja",
alertaRecoverState: "Stanje oporavka",
deleteStatusPageMsg: "Sigurno želite obrisati ovu statusnu stranicu?",
}; };

@ -13,7 +13,7 @@ export default {
pauseDashboardHome: "Szünetel", pauseDashboardHome: "Szünetel",
deleteMonitorMsg: "Biztos, hogy törölni akarja ezt a figyelőt?", deleteMonitorMsg: "Biztos, hogy törölni akarja ezt a figyelőt?",
deleteNotificationMsg: "Biztos, hogy törölni akarja ezt az értesítést az összes figyelőnél?", deleteNotificationMsg: "Biztos, hogy törölni akarja ezt az értesítést az összes figyelőnél?",
resoverserverDescription: "A Cloudflare az alapértelmezett szerver, bármikor meg tudja változtatni a resolver server-t.", resolverserverDescription: "A Cloudflare az alapértelmezett szerver, bármikor meg tudja változtatni a resolver server-t.",
rrtypeDescription: "Válassza ki az RR-típust a figyelőhöz", rrtypeDescription: "Válassza ki az RR-típust a figyelőhöz",
pauseMonitorMsg: "Biztos, hogy szüneteltetni akarja?", pauseMonitorMsg: "Biztos, hogy szüneteltetni akarja?",
enableDefaultNotificationDescription: "Minden új figyelőhöz ez az értesítés engedélyezett lesz alapértelmezetten. Kikapcsolhatja az értesítést külön minden figyelőnél.", enableDefaultNotificationDescription: "Minden új figyelőhöz ez az értesítés engedélyezett lesz alapértelmezetten. Kikapcsolhatja az értesítést külön minden figyelőnél.",
@ -197,6 +197,7 @@ export default {
line: "Line Messenger", line: "Line Messenger",
mattermost: "Mattermost", mattermost: "Mattermost",
"Status Page": "Státusz oldal", "Status Page": "Státusz oldal",
"Status Pages": "Státusz oldalak",
"Primary Base URL": "Elsődleges URL", "Primary Base URL": "Elsődleges URL",
"Push URL": "Meghívandó URL", "Push URL": "Meghívandó URL",
needPushEvery: "Ezt az URL-t kell meghívni minden {0} másodpercben.", needPushEvery: "Ezt az URL-t kell meghívni minden {0} másodpercben.",
@ -350,4 +351,24 @@ export default {
serwersmsAPIPassword: "API jelszó", serwersmsAPIPassword: "API jelszó",
serwersmsPhoneNumber: "Telefonszám", serwersmsPhoneNumber: "Telefonszám",
serwersmsSenderName: "SMS feladó neve (regisztrált név az oldalon)", serwersmsSenderName: "SMS feladó neve (regisztrált név az oldalon)",
GoogleChat: "Google Chat (csak Google Workspace)",
stackfield: "Stackfield",
smtpDkimSettings: "DKIM beállítások",
smtpDkimDesc: "Nézze meg a Nodemailer DKIM {0} használati szabályokat.",
documentation: "dokumentáció",
smtpDkimDomain: "Domain név",
smtpDkimKeySelector: "Kulcs választó",
smtpDkimPrivateKey: "Privát kulcs",
smtpDkimHashAlgo: "Hash algoritmus (nem kötelező)",
smtpDkimheaderFieldNames: "Fejléc kulcsok a bejelentkezéshez (nem kötelező)",
smtpDkimskipFields: "Fejléc kulcsok egyéb esetben (nem kötelező)",
PushByTechulus: "Techulus push",
gorush: "Gorush",
alerta: "Alerta",
alertaApiEndpoint: "API végpont",
alertaEnvironment: "Környezet",
alertaApiKey: "API kulcs",
alertaAlertState: "Figyelmeztetési állapot",
alertaRecoverState: "Visszaállási állapot",
deleteStatusPageMsg: "Biztos, hogy törölni akarja a státusz oldalt?",
}; };

@ -7,13 +7,13 @@ export default {
upsideDownModeDescription: "Balikkan statusnya. Jika layanan dapat dijangkau, TIDAK AKTIF.", upsideDownModeDescription: "Balikkan statusnya. Jika layanan dapat dijangkau, TIDAK AKTIF.",
maxRedirectDescription: "Jumlah maksimum pengalihan untuk diikuti. Setel ke 0 untuk menonaktifkan pengalihan.", maxRedirectDescription: "Jumlah maksimum pengalihan untuk diikuti. Setel ke 0 untuk menonaktifkan pengalihan.",
acceptedStatusCodesDescription: "Pilih kode status yang dianggap sebagai tanggapan yang berhasil.", acceptedStatusCodesDescription: "Pilih kode status yang dianggap sebagai tanggapan yang berhasil.",
passwordNotMatchMsg: "Sandi kedua tidak cocok.", passwordNotMatchMsg: "Kata sandi kedua tidak cocok.",
notificationDescription: "Harap atur notifikasi ke monitor agar berfungsi.", notificationDescription: "Harap atur notifikasi ke monitor agar berfungsi.",
keywordDescription: "Cari kata kunci dalam code html atau JSON huruf besar-kecil berpengaruh", keywordDescription: "Cari kata kunci dalam code html atau JSON huruf besar-kecil berpengaruh",
pauseDashboardHome: "Jeda", pauseDashboardHome: "Jeda",
deleteMonitorMsg: "Apakah Anda mau menghapus monitor ini?", deleteMonitorMsg: "Apakah Anda mau menghapus monitor ini?",
deleteNotificationMsg: "Apakah Anda mau menghapus notifikasi ini untuk semua monitor?", deleteNotificationMsg: "Apakah Anda mau menghapus notifikasi untuk semua monitor?",
resoverserverDescription: "Cloudflare adalah server bawaan, Anda dapat mengubah server resolver kapan saja.", resolverserverDescription: "Cloudflare adalah server bawaan, Anda dapat mengubah server resolver kapan saja.",
rrtypeDescription: "Pilih RR-Type yang mau Anda monitor", rrtypeDescription: "Pilih RR-Type yang mau Anda monitor",
pauseMonitorMsg: "Apakah Anda yakin mau menjeda?", pauseMonitorMsg: "Apakah Anda yakin mau menjeda?",
enableDefaultNotificationDescription: "Untuk setiap monitor baru, notifikasi ini akan diaktifkan secara bawaan. Anda masih dapat menonaktifkan notifikasi secara terpisah untuk setiap monitor.", enableDefaultNotificationDescription: "Untuk setiap monitor baru, notifikasi ini akan diaktifkan secara bawaan. Anda masih dapat menonaktifkan notifikasi secara terpisah untuk setiap monitor.",
@ -23,9 +23,9 @@ export default {
importHandleDescription: "Pilih 'Lewati yang ada' jika Anda ingin melewati setiap monitor atau notifikasi dengan nama yang sama. 'Timpa' akan menghapus setiap monitor dan notifikasi yang ada.", importHandleDescription: "Pilih 'Lewati yang ada' jika Anda ingin melewati setiap monitor atau notifikasi dengan nama yang sama. 'Timpa' akan menghapus setiap monitor dan notifikasi yang ada.",
confirmImportMsg: "Apakah Anda yakin untuk mengimpor cadangan? Pastikan Anda telah memilih opsi impor yang tepat.", confirmImportMsg: "Apakah Anda yakin untuk mengimpor cadangan? Pastikan Anda telah memilih opsi impor yang tepat.",
twoFAVerifyLabel: "Silakan ketik token Anda untuk memverifikasi bahwa 2FA berfungsi", twoFAVerifyLabel: "Silakan ketik token Anda untuk memverifikasi bahwa 2FA berfungsi",
tokenValidSettingsMsg: "Tokennya benar! Anda sekarang dapat menyimpan pengaturan 2FA.", tokenValidSettingsMsg: "Token benar! Anda sekarang dapat menyimpan pengaturan 2FA.",
confirmEnableTwoFAMsg: "Apakah Anda yakin ingin mengaktifkan 2FA?", confirmEnableTwoFAMsg: "Apakah anda yakin ingin mengaktifkan 2FA?",
confirmDisableTwoFAMsg: "Apakah Anda yakin ingin menonaktifkan 2FA?", confirmDisableTwoFAMsg: "Apakah anda yakin ingin menonaktifkan 2FA?",
Settings: "Pengaturan", Settings: "Pengaturan",
Dashboard: "Dasbor", Dashboard: "Dasbor",
"New Update": "Pembaruan Baru", "New Update": "Pembaruan Baru",
@ -179,6 +179,7 @@ export default {
"Edit Status Page": "Edit Halaman Status", "Edit Status Page": "Edit Halaman Status",
"Go to Dashboard": "Pergi ke Dasbor", "Go to Dashboard": "Pergi ke Dasbor",
"Status Page": "Halaman Status", "Status Page": "Halaman Status",
"Status Pages": "Halaman Status",
defaultNotificationName: "{notification} saya Peringatan ({number})", defaultNotificationName: "{notification} saya Peringatan ({number})",
here: "di sini", here: "di sini",
Required: "Dibutuhkan", Required: "Dibutuhkan",
@ -276,7 +277,7 @@ export default {
promosmsTypeEco: "SMS ECO - murah tapi lambat dan sering kelebihan beban. Terbatas hanya untuk penerima Polandia.", promosmsTypeEco: "SMS ECO - murah tapi lambat dan sering kelebihan beban. Terbatas hanya untuk penerima Polandia.",
promosmsTypeFlash: "SMS FLASH - Pesan akan otomatis muncul di perangkat penerima. Terbatas hanya untuk penerima Polandia.", promosmsTypeFlash: "SMS FLASH - Pesan akan otomatis muncul di perangkat penerima. Terbatas hanya untuk penerima Polandia.",
promosmsTypeFull: "SMS FULL - SMS tingkat premium, Anda dapat menggunakan Nama Pengirim Anda (Anda harus mendaftarkan nama terlebih dahulu). Dapat diAndalkan untuk peringatan.", promosmsTypeFull: "SMS FULL - SMS tingkat premium, Anda dapat menggunakan Nama Pengirim Anda (Anda harus mendaftarkan nama terlebih dahulu). Dapat diAndalkan untuk peringatan.",
promosmsTypeSpeed: "SMS SPEED - Prioritas tertinggi dalam sistem. Sangat cepat dan dapat diAndalkan tetapi mahal (sekitar dua kali lipat dari harga SMS FULL).", promosmsTypeSpeed: "SMS SPEED - Prioritas tertinggi dalam sistem. Sangat cepat dan dapat diandalkan tetapi mahal (sekitar dua kali lipat dari harga SMS FULL).",
promosmsPhoneNumber: "Nomor telepon (untuk penerima Polandia Anda dapat melewati kode area)", promosmsPhoneNumber: "Nomor telepon (untuk penerima Polandia Anda dapat melewati kode area)",
promosmsSMSSender: "Nama Pengirim SMS : Nama pra-registrasi atau salah satu bawaan: InfoSMS, Info SMS, MaxSMS, INFO, SMS", promosmsSMSSender: "Nama Pengirim SMS : Nama pra-registrasi atau salah satu bawaan: InfoSMS, Info SMS, MaxSMS, INFO, SMS",
"Feishu WebHookUrl": "Feishu WebHookUrl", "Feishu WebHookUrl": "Feishu WebHookUrl",

@ -2,49 +2,49 @@ export default {
languageName: "Italiano (Italian)", languageName: "Italiano (Italian)",
checkEverySecond: "controlla ogni {0} secondi", checkEverySecond: "controlla ogni {0} secondi",
retryCheckEverySecond: "Riprova ogni {0} secondi.", retryCheckEverySecond: "Riprova ogni {0} secondi.",
retriesDescription: "Tentativi da fare prima che il servizio venga marcato come \"giù\" e che una notifica venga inviata.", retriesDescription: "Tentativi prima che il servizio venga marcato come \"DOWN\" e che una notifica venga inviata.",
ignoreTLSError: "Ignora gli errori TLS/SSL per i siti in HTTPS.", ignoreTLSError: "Ignora gli errori TLS/SSL per i siti HTTPS.",
upsideDownModeDescription: "Capovolgi lo stato. Se il servizio è raggiungibile viene marcato come \"GIÙ\".", upsideDownModeDescription: "Se il servizio risulta raggiungibile viene marcato come \"DOWN\".",
maxRedirectDescription: "Numero massimo di redirezionamenti consentito. Per disabilitare impostare \"0\".", maxRedirectDescription: "Numero massimo di redirezionamenti consentito. Per disabilitare, impostare \"0\".",
acceptedStatusCodesDescription: "Inserire i codici di stato considerati come risposte corrette.", acceptedStatusCodesDescription: "Elenco di codici di stato HTTP che sono considerati validi.",
passwordNotMatchMsg: "La password non coincide.", passwordNotMatchMsg: "La password non coincide.",
notificationDescription: "Assegnare la notifica a uno o più oggetti monitorati per metterla in funzione.", notificationDescription: "Assegnare la notifica a uno o più oggetti monitorati per metterla in funzione.",
keywordDescription: "Cerca la parola chiave nella risposta in html o JSON e fai distinzione tra maiuscole e minuscole", keywordDescription: "Cerca la parola chiave nella risposta in html o JSON e fai distinzione tra maiuscole e minuscole",
pauseDashboardHome: "In Pausa", pauseDashboardHome: "In Pausa",
deleteMonitorMsg: "Si è certi di voler eliminare questo oggetto monitorato?", deleteMonitorMsg: "Si è certi di voler eliminare questo oggetto monitorato?",
deleteNotificationMsg: "Si è certi di voler eliminare questa notifica per tutti gli oggetti monitorati?", deleteNotificationMsg: "Si è certi di voler eliminare questa notifica per tutti gli oggetti monitorati?",
resoverserverDescription: "Cloudflare è il server predefinito, è possibile cambiare il server DNS.", resolverserverDescription: "Cloudflare è il server predefinito, è possibile cambiare il server DNS.",
rrtypeDescription: "Scegliere il tipo di RR che si vuole monitorare", rrtypeDescription: "Scegliere il tipo di RR che si vuole monitorare",
pauseMonitorMsg: "Si è certi di voler mettere in pausa?", pauseMonitorMsg: "Si è certi di voler mettere in pausa?",
enableDefaultNotificationDescription: "Per ogni nuovo oggetto monitorato questa notifica sarà abilitata di default. È comunque possibile disabilitare la notifica separatamente per ogni oggetto.", enableDefaultNotificationDescription: "Per ogni nuovo monitor questa notifica sarà abilitata di default. È comunque possibile disabilitare la notifica singolarmente.",
clearEventsMsg: "Si è certi di voler eliminare tutti gli eventi per questo servizio?", clearEventsMsg: "Si è certi di voler eliminare tutti gli eventi per questo servizio?",
clearHeartbeatsMsg: "Si è certi di voler eliminare tutti gli intervalli di controllo per questo servizio?", clearHeartbeatsMsg: "Si è certi di voler eliminare tutti gli intervalli di controllo per questo servizio?",
confirmClearStatisticsMsg: "Si è certi di voler eliminare TUTTE le statistiche?", confirmClearStatisticsMsg: "Si è certi di voler eliminare TUTTE le statistiche?",
importHandleDescription: "Selezionare 'Ignora gli esistenti' si vuole ignorare l'importazione degli oggetti monitorati o delle notifiche con lo stesso nome. 'Sovrascrivi' eliminerà ogni oggetto e notifica esistente.", importHandleDescription: "Selezionare \"Ignora esistenti\" se si vuole ignorare l'importazione dei monitor o delle notifiche con lo stesso nome. \"Sovrascrivi\" rimpiazzerà tutti i monitor e le notifiche presenti con quelli nel backup.",
confirmImportMsg: "Si è certi di voler importare il backup? Essere certi di aver selezionato l'opzione corretta di importazione.", confirmImportMsg: "Si è certi di voler importare il backup? Essere certi di aver selezionato l'opzione corretta di importazione.",
twoFAVerifyLabel: "Scrivi il token per verificare che l'autenticazione a due fattori funzioni", twoFAVerifyLabel: "Digita il token per verificare che l'autenticazione a due fattori funzioni correttamente:",
tokenValidSettingsMsg: "Il token è valido! È ora possibile salvare le impostazioni.", tokenValidSettingsMsg: "Il token è valido! È ora possibile salvare le impostazioni.",
confirmEnableTwoFAMsg: "Si è certi di voler abilitare l'autenticazione a due fattori?", confirmEnableTwoFAMsg: "Si è certi di voler abilitare l'autenticazione a due fattori?",
confirmDisableTwoFAMsg: "Si è certi di voler disabilitare l'autenticazione a due fattori?", confirmDisableTwoFAMsg: "Si è certi di voler disabilitare l'autenticazione a due fattori?",
Settings: "Impostazioni", Settings: "Impostazioni",
Dashboard: "Cruscotto", Dashboard: "Dashboard",
"New Update": "Nuovo Aggiornamento Disponibile", "New Update": "Nuovo aggiornamento disponibile!",
Language: "Lingua", Language: "Lingua",
Appearance: "Aspetto", Appearance: "Aspetto",
Theme: "Tema", Theme: "Tema",
General: "Generali", General: "Generale",
"Primary Base URL": "URL base primario", "Primary Base URL": "URL base primario",
Version: "Versione", Version: "Versione",
"Check Update On GitHub": "Controlla aggiornamenti su GitHub", "Check Update On GitHub": "Controlla aggiornamenti su GitHub",
List: "Lista", List: "Lista",
Add: "Aggiungi", Add: "Aggiungi",
"Add New Monitor": "Aggiungi un nuovo oggetto da monitorare", "Add New Monitor": "Aggiungi nuovo monitor",
"Quick Stats": "Statistiche rapide", "Quick Stats": "Statistiche rapide",
Up: "Su", Up: "Up",
Down: "Giù", Down: "Down",
Pending: "Pendente", Pending: "In attesa",
Unknown: "Sconosciuti", Unknown: "Sconosciuti",
Pause: "Metti in Pausa", Pause: "Metti in pausa",
Name: "Nome", Name: "Nome",
Status: "Stato", Status: "Stato",
DateTime: "Data e Ora", DateTime: "Data e Ora",
@ -65,15 +65,15 @@ export default {
Ping: "Ping", Ping: "Ping",
"Monitor Type": "Modalità di monitoraggio", "Monitor Type": "Modalità di monitoraggio",
Keyword: "Parola chiave", Keyword: "Parola chiave",
"Friendly Name": "Nomignolo", "Friendly Name": "Nome",
URL: "URL", URL: "URL",
Hostname: "Nome Host", Hostname: "Nome Host",
Port: "Porta", Port: "Porta",
"Heartbeat Interval": "Intervallo di controllo", "Heartbeat Interval": "Intervallo di controllo",
Retries: "Tentativi", Retries: "Tentativi",
"Heartbeat Retry Interval": "Intervallo tra un tentativo di controllo e l'altro", "Heartbeat Retry Interval": "Intervallo tra i tentativo di controllo",
Advanced: "Avanzate", Advanced: "Avanzate",
"Upside Down Mode": "Modalità capovolta", "Upside Down Mode": "Modalità invertita",
"Max. Redirects": "Reindirizzamenti massimi", "Max. Redirects": "Reindirizzamenti massimi",
"Accepted Status Codes": "Codici di stato accettati", "Accepted Status Codes": "Codici di stato accettati",
"Push URL": "Push URL", "Push URL": "Push URL",
@ -81,52 +81,52 @@ export default {
pushOptionalParams: "Parametri aggiuntivi: {0}", pushOptionalParams: "Parametri aggiuntivi: {0}",
Save: "Salva", Save: "Salva",
Notifications: "Notifiche", Notifications: "Notifiche",
"Not available, please setup.": "Non disponibili, da impostare.", "Not available, please setup.": "Non disponibili, da configurare.",
"Setup Notification": "Imposta le notifiche", "Setup Notification": "Configura le notifiche",
Light: "Chiaro", Light: "Chiaro",
Dark: "Scuro", Dark: "Scuro",
Auto: "Automatico", Auto: "Automatico",
"Theme - Heartbeat Bar": "Tema - Barra di Stato", "Theme - Heartbeat Bar": "Tema (barra di stato)",
Normal: "Normale", Normal: "Normale",
Bottom: "Sotto", Bottom: "Sotto",
None: "Nessuna", None: "Nessuna",
Timezone: "Fuso Orario", Timezone: "Fuso Orario",
"Search Engine Visibility": "Visibilità ai motori di ricerca", "Search Engine Visibility": "Visibilità ai motori di ricerca",
"Allow indexing": "Permetti l'indicizzazione", "Allow indexing": "Consenti l'indicizzazione",
"Discourage search engines from indexing site": "Scoraggia l'indicizzazione da parte dei motori di ricerca", "Discourage search engines from indexing site": "Evita l'indicizzazione ai motori di ricerca",
"Change Password": "Cambio Password", "Change Password": "Cambia password",
"Current Password": "Password Corrente", "Current Password": "Password corrente",
"New Password": "Nuova Password", "New Password": "Nuova password",
"Repeat New Password": "Ripetere la nuova Password", "Repeat New Password": "Ripeti nuova password",
"Update Password": "Modifica Password", "Update Password": "Modifica password",
"Disable Auth": "Disabilita l'autenticazione", "Disable Auth": "Disabilita autenticazione",
"Enable Auth": "Abilita Autenticazione", "Enable Auth": "Abilita autenticazione",
Logout: "Esci", Logout: "Esci",
Leave: "Annulla", Leave: "Annulla",
"I understand, please disable": "Lo capisco, disabilitare l'autenticazione.", "I understand, please disable": "Lo capisco, disabilitare l'autenticazione.",
Confirm: "Conferma", Confirm: "Conferma",
Yes: "Sì", Yes: "Sì",
No: "No", No: "No",
Username: "Nome Utente", Username: "Nome utente",
Password: "Password", Password: "Password",
"Remember me": "Ricordami", "Remember me": "Ricorda credenziali",
Login: "Accesso", Login: "Accesso",
"No Monitors, please": "Nessun oggetto monitorato,", "No Monitors, please": "Nessun monitor presente,",
"add one": "aggiungerne uno", "add one": "aggiungine uno!",
"Notification Type": "Tipo di notifica", "Notification Type": "Servizio di notifica",
Email: "E-mail", Email: "E-mail",
Test: "Prova", Test: "Fai una prova",
"Certificate Info": "Informazioni sul certificato", "Certificate Info": "Informazioni sul certificato",
"Resolver Server": "Server DNS", "Resolver Server": "Server DNS",
"Resource Record Type": "Tipo di Resource Record", "Resource Record Type": "Tipo di Resource Record",
"Last Result": "Ultimo risultato", "Last Result": "Ultimo risultato",
"Create your admin account": "Crea l'account amministratore", "Create your admin account": "Crea l'account amministratore",
"Repeat Password": "Ripeti Password", "Repeat Password": "Ripeti password",
"Import Backup": "Importa Backup", "Import Backup": "Importa backup",
"Export Backup": "Esporta Backup", "Export Backup": "Esporta backup",
Export: "Esporta", Export: "Esporta",
Import: "Importa", Import: "Importa",
respTime: "Tempo di Risposta (ms)", respTime: "Tempo di risposta (ms)",
notAvailableShort: "N/D", notAvailableShort: "N/D",
"Default enabled": "Abilitato di default", "Default enabled": "Abilitato di default",
"Apply on all existing monitors": "Applica su tutti i monitoraggi", "Apply on all existing monitors": "Applica su tutti i monitoraggi",
@ -136,21 +136,21 @@ export default {
Heartbeats: "Controlli", Heartbeats: "Controlli",
"Auto Get": "Rileva", "Auto Get": "Rileva",
backupDescription: "È possibile fare il backup di tutti i monitoraggi e di tutte le notifiche in un file JSON.", backupDescription: "È possibile fare il backup di tutti i monitoraggi e di tutte le notifiche in un file JSON.",
backupDescription2: "P.S.: lo storico e i dati relativi agli eventi non saranno inclusi", backupDescription2: "NOTA: lo storico e i dati relativi agli eventi non saranno inclusi nel backup",
backupDescription3: "Dati sensibili come i token di autenticazione saranno inclusi nel backup, tenere quindi in un luogo sicuro.", backupDescription3: "Dati sensibili come i token di autenticazione saranno inclusi nel backup, custodisci il file in un luogo sicuro!",
alertNoFile: "Selezionare il file da importare.", alertNoFile: "Selezionare il file da importare.",
alertWrongFileType: "Selezionare un file JSON.", alertWrongFileType: "Selezionare un file JSON.",
"Clear all statistics": "Pulisci tutte le statistiche", "Clear all statistics": "Cancella tutte le statistiche",
"Skip existing": "Ignora gli esistenti", "Skip existing": "Ignora esistenti",
Overwrite: "Sovrascrivi", Overwrite: "Sovrascrivi",
Options: "Opzioni", Options: "Opzioni",
"Keep both": "Mantieni entrambi", "Keep both": "Mantieni entrambi",
"Verify Token": "Verifica Token", "Verify Token": "Verifica token",
"Setup 2FA": "Imposta l'autenticazione a due fattori", "Setup 2FA": "Configura 2FA",
"Enable 2FA": "Abilita l'autenticazione a due fattori", "Enable 2FA": "Abilita 2FA",
"Disable 2FA": "Disabilita l'autenticazione a due fattori", "Disable 2FA": "Disabilita 2FA",
"2FA Settings": "Impostazioni autenticazione a due fattori", "2FA Settings": "Gestisci l'autenticazione a due fattori",
"Two Factor Authentication": "Autenticazione a due fattori", "Two Factor Authentication": "Autenticazione a due fattori (2FA)",
Active: "Attivata", Active: "Attivata",
Inactive: "Disattivata", Inactive: "Disattivata",
Token: "Token", Token: "Token",
@ -173,31 +173,32 @@ export default {
"Avg. Ping": "Tempo medio di risposta al ping", "Avg. Ping": "Tempo medio di risposta al ping",
"Avg. Response": "Tempo medio di risposta", "Avg. Response": "Tempo medio di risposta",
"Entry Page": "Pagina Principale", "Entry Page": "Pagina Principale",
statusPageNothing: "Non c'è nulla qui, aggiungere un gruppo oppure un oggetto da monitorare.", statusPageNothing: "Non c'è nulla qui, aggiungi un gruppo oppure un monitor.",
"No Services": "Nessun Servizio", "No Services": "Nessun servizio",
"All Systems Operational": "Tutti i sistemi sono funzionali", "All Systems Operational": "Tutti i sistemi sono funzionali",
"Partially Degraded Service": "Servizio parzialmente degradato", "Partially Degraded Service": "Servizio parzialmente degradato",
"Degraded Service": "Servizio degradato", "Degraded Service": "Servizio degradato",
"Add Group": "Aggiungi Gruppo", "Add Group": "Aggiungi gruppo",
"Add a monitor": "Aggiungi un oggetto", "Add a monitor": "Aggiungi monitor",
"Edit Status Page": "Modifica pagina di stato", "Edit Status Page": "Modifica pagina di stato",
"Go to Dashboard": "Vai al Cruscotto", "Go to Dashboard": "Vai alla dashboard",
"Status Page": "Pagina di stato", "Status Page": "Pagina di stato",
defaultNotificationName: "Allarme {notification} ({number})", "Status Pages": "Pagina di stato",
defaultNotificationName: "Notifica {notification} ({number})",
here: "qui", here: "qui",
Required: "Richiesto", Required: "Obbligatorio",
telegram: "Telegram", telegram: "Telegram",
"Bot Token": "Token del Bot", "Bot Token": "Token del bot",
wayToGetTelegramToken: "È possibile ricevere un token da {0}.", wayToGetTelegramToken: "Puoi ottenere il token da {0}.",
"Chat ID": "ID Chat", "Chat ID": "ID Chat",
supportTelegramChatID: "Supporta Chat dirette / di Gruppo / ID Canale", supportTelegramChatID: "Supporta chat private, gruppi e canali.",
wayToGetTelegramChatID: "È possibile ricereve l'ID chat mandando un messaggio al bot e poi andando in questo URL per visualizzare il chat_id:", wayToGetTelegramChatID: "È possibile ricereve l'ID chat mandando un messaggio al bot e poi andando in questo URL per visualizzare il chat_id:",
"YOUR BOT TOKEN HERE": "QUI IL TOKEN DEL BOT", "YOUR BOT TOKEN HERE": "QUI IL TOKEN DEL BOT",
chatIDNotFound: "Non trovo l'ID chat. Prima bisogna mandare un messaggio al bot", chatIDNotFound: "Non trovo l'ID chat. Prima bisogna mandare un messaggio al bot",
webhook: "Webhook", webhook: "Webhook",
"Post URL": "Post URL", "Post URL": "Post URL",
"Content Type": "Content Type", "Content Type": "Content Type",
webhookJsonDesc: "{0} va bene per qualsiasi server http moderno ad esempio express.js", webhookJsonDesc: "{0} va bene per qualsiasi server HTTP moderno ad esempio express.js",
webhookFormDataDesc: "{multipart} va bene per PHP, c'è solo bisogno di analizzare il json con {decodeFunction}", webhookFormDataDesc: "{multipart} va bene per PHP, c'è solo bisogno di analizzare il json con {decodeFunction}",
smtp: "E-mail (SMTP)", smtp: "E-mail (SMTP)",
secureOptionNone: "Nessuno / STARTTLS (25, 587)", secureOptionNone: "Nessuno / STARTTLS (25, 587)",
@ -294,29 +295,29 @@ export default {
matrixDesc1: "È possibile recuperare l'ID della stanza all'interno delle impostazioni avanzate della stanza nel client Matrix. Dovrebbe essere simile a !QMdRCpUIfLwsfjxye6:server.di.casa.", matrixDesc1: "È possibile recuperare l'ID della stanza all'interno delle impostazioni avanzate della stanza nel client Matrix. Dovrebbe essere simile a !QMdRCpUIfLwsfjxye6:server.di.casa.",
matrixDesc2: "È altamente raccomandata la creazione di un nuovo utente e di non utilizare il proprio token di accesso Matrix poiché darà pieno controllo al proprio account e a tutte le stanze in cui si ha accesso. Piuttosto, si crei un nuovo utente per invitarlo nella stanza dove si vuole ricevere le notifiche. Si può accedere al token eseguendo {0}", matrixDesc2: "È altamente raccomandata la creazione di un nuovo utente e di non utilizare il proprio token di accesso Matrix poiché darà pieno controllo al proprio account e a tutte le stanze in cui si ha accesso. Piuttosto, si crei un nuovo utente per invitarlo nella stanza dove si vuole ricevere le notifiche. Si può accedere al token eseguendo {0}",
Method: "Metodo", Method: "Metodo",
Body: "Corpo", Body: "Body",
Headers: "Intestazioni", Headers: "Intestazioni",
PushUrl: "URL di Push", PushUrl: "URL di Push",
HeadersInvalidFormat: "L'intestazione di richiesta non è un JSON valido: ", HeadersInvalidFormat: "L'intestazione di richiesta non è un JSON valido: ",
BodyInvalidFormat: "Il corpo di richiesta non è un JSON valido: ", BodyInvalidFormat: "Il corpo di richiesta non è un JSON valido: ",
"Monitor History": "Storicizzazione", "Monitor History": "Storico monitor",
clearDataOlderThan: "Mantieni lo storico per {0} giorni.", clearDataOlderThan: "Mantieni lo storico per {0} giorni.",
PasswordsDoNotMatch: "Le password non corrispondono.", PasswordsDoNotMatch: "Le password non corrispondono!",
records: "records", records: "records",
"One record": "One record", "One record": "One record",
steamApiKeyDescription: "Per monitorare un server di gioco Steam si necessita della chiave Web-API di Steam. È possibile registrare la propria chiave API qui: ", steamApiKeyDescription: "Per monitorare un server di gioco Steam è necessaria una Web-API Key di Steam. È possibile registrarne una qui: ",
"Current User": "Utente corrente", "Current User": "Utente corrente",
recent: "Recenti", recent: "Recenti",
Done: "Fatto", Done: "Fatto",
Info: "Info", Info: "Info",
Security: "Sicurezza", Security: "Sicurezza",
"Steam API Key": "Chiave API di Steam", "Steam API Key": "API Key di Steam",
"Shrink Database": "Comprimi Database", "Shrink Database": "Comprimi database",
"Pick a RR-Type...": "Scegli un tipo di RR...", "Pick a RR-Type...": "Scegli un tipo di RR...",
"Pick Accepted Status Codes...": "Scegli i codici di Stato Accettati...", "Pick Accepted Status Codes...": "Scegli i codici di Stato Accettati...",
Default: "Predefinito", Default: "Predefinito",
"HTTP Options": "Opzioni HTTP", "HTTP Options": "Opzioni HTTP",
"Create Incident": "Crea Incident", "Create Incident": "Segnala incidente",
Title: "Titolo", Title: "Titolo",
Content: "Contenuto", Content: "Contenuto",
Style: "Stile", Style: "Stile",
@ -331,20 +332,20 @@ export default {
Created: "Creato", Created: "Creato",
"Last Updated": "Ultima modifica", "Last Updated": "Ultima modifica",
Unpin: "Rimuovi", Unpin: "Rimuovi",
"Switch to Light Theme": "Utilizza tema chiaro", "Switch to Light Theme": "Utilizza il tema chiaro",
"Switch to Dark Theme": "Utilizza tema scuro", "Switch to Dark Theme": "Utilizza il tema scuro",
"Show Tags": "Mostra etichette", "Show Tags": "Mostra etichette",
"Hide Tags": "Nascondi etichette", "Hide Tags": "Nascondi etichette",
Description: "Descrizione", Description: "Descrizione",
"No monitors available.": "Nessun oggetto monitorato disponibile.", "No monitors available.": "Nessun monitor disponibile.",
"Add one": "Aggiungi", "Add one": "Aggiungine uno!",
"No Monitors": "Nessun oggetto monitorato inserito", "No Monitors": "Nessun monitor presente.",
"Untitled Group": "Gruppo senza titolo", "Untitled Group": "Gruppo senza titolo",
Services: "Servizi", Services: "Servizi",
Discard: "Scarta", Discard: "Scarta modifiche",
Cancel: "Annulla", Cancel: "Annulla",
"Powered by": "Servito da", "Powered by": "Powered by",
shrinkDatabaseDescription: "Lancia il comando VACUUM sul database SQLite. Se il database è stato creato dopo la versione 1.10.0, AUTO_VACUUM è già abilitato e questa azione non è necessaria.", 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",
@ -360,4 +361,5 @@ export default {
smtpDkimHashAlgo: "Algoritmo di hashing (opzionale)", smtpDkimHashAlgo: "Algoritmo di hashing (opzionale)",
smtpDkimheaderFieldNames: "Campi Intestazione da firmare (opzionale)", smtpDkimheaderFieldNames: "Campi Intestazione da firmare (opzionale)",
smtpDkimskipFields: "Campi Intestazione da non firmare (opzionale)", smtpDkimskipFields: "Campi Intestazione da non firmare (opzionale)",
GoogleChat: "Google Chat (solo per Google Workspace)",
}; };

@ -12,7 +12,7 @@ export default {
pauseDashboardHome: "一時停止", pauseDashboardHome: "一時停止",
deleteMonitorMsg: "この監視を削除してよろしいですか?", deleteMonitorMsg: "この監視を削除してよろしいですか?",
deleteNotificationMsg: "全ての監視のこの通知を削除してよろしいですか?", deleteNotificationMsg: "全ての監視のこの通知を削除してよろしいですか?",
resoverserverDescription: "Cloudflareがデフォルトのサーバーですが、いつでもリゾルバサーバーを変更できます。", resolverserverDescription: "Cloudflareがデフォルトのサーバーですが、いつでもリゾルバサーバーを変更できます。",
rrtypeDescription: "監視するRRタイプを選択します", rrtypeDescription: "監視するRRタイプを選択します",
pauseMonitorMsg: "一時停止しますか?", pauseMonitorMsg: "一時停止しますか?",
Settings: "設定", Settings: "設定",
@ -180,6 +180,7 @@ export default {
"Edit Status Page": "ステータスページ編集", "Edit Status Page": "ステータスページ編集",
"Go to Dashboard": "ダッシュボード", "Go to Dashboard": "ダッシュボード",
"Status Page": "ステータスページ", "Status Page": "ステータスページ",
"Status Pages": "ステータスページ",
telegram: "Telegram", telegram: "Telegram",
webhook: "Webhook", webhook: "Webhook",
smtp: "Email (SMTP)", smtp: "Email (SMTP)",

@ -13,7 +13,7 @@ export default {
pauseDashboardHome: "일시 정지", pauseDashboardHome: "일시 정지",
deleteMonitorMsg: "정말 이 모니터링을 삭제할까요?", deleteMonitorMsg: "정말 이 모니터링을 삭제할까요?",
deleteNotificationMsg: "정말 이 알림을 모든 모니터링에서 삭제할까요?", deleteNotificationMsg: "정말 이 알림을 모든 모니터링에서 삭제할까요?",
resoverserverDescription: "Cloudflare가 기본 서버예요, 원한다면 언제나 다른 Resolver 서버로 변경할 수 있어요.", resolverserverDescription: "Cloudflare가 기본 서버예요, 원한다면 언제나 다른 Resolver 서버로 변경할 수 있어요.",
rrtypeDescription: "모니터링할 RR-Type을 선택해요.", rrtypeDescription: "모니터링할 RR-Type을 선택해요.",
pauseMonitorMsg: "정말 이 모니터링을 일시 정지할까요?", pauseMonitorMsg: "정말 이 모니터링을 일시 정지할까요?",
enableDefaultNotificationDescription: "새로 추가하는 모든 모니터링에 이 알림을 기본적으로 활성화해요. 각 모니터에 대해 별도로 알림을 비활성화할 수 있어요.", enableDefaultNotificationDescription: "새로 추가하는 모든 모니터링에 이 알림을 기본적으로 활성화해요. 각 모니터에 대해 별도로 알림을 비활성화할 수 있어요.",
@ -179,6 +179,7 @@ export default {
"Edit Status Page": "상태 페이지 수정", "Edit Status Page": "상태 페이지 수정",
"Go to Dashboard": "대시보드로 가기", "Go to Dashboard": "대시보드로 가기",
"Status Page": "상태 페이지", "Status Page": "상태 페이지",
"Status Pages": "상태 페이지",
defaultNotificationName: "내 {notification} 알림 ({number})", defaultNotificationName: "내 {notification} 알림 ({number})",
here: "여기", here: "여기",
Required: "필수", Required: "필수",
@ -188,7 +189,7 @@ export default {
"Chat ID": "채팅 ID", "Chat ID": "채팅 ID",
supportTelegramChatID: "Direct Chat / Group / Channel's Chat ID를 지원해요.", supportTelegramChatID: "Direct Chat / Group / Channel's Chat ID를 지원해요.",
wayToGetTelegramChatID: "봇에 메시지를 보내 채팅 ID를 얻고 밑에 URL로 이동해 chat_id를 볼 수 있어요.", wayToGetTelegramChatID: "봇에 메시지를 보내 채팅 ID를 얻고 밑에 URL로 이동해 chat_id를 볼 수 있어요.",
"YOUR BOT TOKEN HERE": "YOUR BOT TOKEN HERE", "YOUR BOT TOKEN HERE": "여기에 BOT 토큰을 적어주세요.",
chatIDNotFound: "채팅 ID를 찾을 수 없어요. 먼저 봇에게 메시지를 보내주세요.", chatIDNotFound: "채팅 ID를 찾을 수 없어요. 먼저 봇에게 메시지를 보내주세요.",
webhook: "Webhook", webhook: "Webhook",
"Post URL": "Post URL", "Post URL": "Post URL",
@ -281,15 +282,15 @@ export default {
promosmsSMSSender: "SMS 보내는 사람 이름 : 미리 등록된 이름 혹은 기본값 중 하나예요: InfoSMS, SMS Info, MaxSMS, INFO, SMS", promosmsSMSSender: "SMS 보내는 사람 이름 : 미리 등록된 이름 혹은 기본값 중 하나예요: InfoSMS, SMS Info, MaxSMS, INFO, SMS",
"Primary Base URL": "기본 URL", "Primary Base URL": "기본 URL",
"Push URL": "Push URL", "Push URL": "Push URL",
needPushEvery: "You should call this URL every {0} seconds.", needPushEvery: "이 URL을 {0} 초 마다 호출할 수 있어요.",
pushOptionalParams: "Optional parameters: {0}", pushOptionalParams: "선택적 파라미터: {0}",
emailCustomSubject: "Custom Subject", emailCustomSubject: "커스텀 주제",
clicksendsms: "ClickSend SMS", clicksendsms: "ClickSend SMS",
checkPrice: "{0} 가격 확인:", checkPrice: "{0} 가격 확인:",
apiCredentials: "API credentials", apiCredentials: "API 인증정보",
octopushLegacyHint: "Octopush 레거시 버전 (2011-2020) 을 사용하시나요? 아니면 새 버전을 사용하시나요?", octopushLegacyHint: "Octopush 레거시 버전 (2011-2020) 을 사용하시나요? 아니면 새 버전을 사용하시나요?",
"Feishu WebHookUrl": "Feishu WebHookURL", "Feishu WebHookUrl": "Feishu WebHookURL",
matrixHomeserverURL: "Homeserver URL (with http(s):// and optionally port)", matrixHomeserverURL: "Homeserver URL (http(s):// 와 함께 적어주세요. 그리고 포트 번호는 선택적 입니다.)",
"Internal Room Id": "내부 방 ID", "Internal Room Id": "내부 방 ID",
matrixDesc1: "Matrix 클라이언트 방 설정의 고급 섹션에서 내부 방 ID를 찾을 수 있어요. 내부 방 ID는 이렇게 생겼답니다: !QMdRCpUIfLwsfjxye6:home.server.", matrixDesc1: "Matrix 클라이언트 방 설정의 고급 섹션에서 내부 방 ID를 찾을 수 있어요. 내부 방 ID는 이렇게 생겼답니다: !QMdRCpUIfLwsfjxye6:home.server.",
matrixDesc2: "사용자의 모든 방에 대한 엑세스가 허용될 수 있어서 새로운 사용자를 만들고 원하는 방에만 초대한 후 엑세스 토큰을 사용하는 것이 좋아요. {0} 이 명령어를 통해 엑세스 토큰을 얻을 수 있어요.", matrixDesc2: "사용자의 모든 방에 대한 엑세스가 허용될 수 있어서 새로운 사용자를 만들고 원하는 방에만 초대한 후 엑세스 토큰을 사용하는 것이 좋아요. {0} 이 명령어를 통해 엑세스 토큰을 얻을 수 있어요.",
@ -349,6 +350,6 @@ export default {
serwersmsAPIUser: "API Usename (webapi_ 접두사 포함)", serwersmsAPIUser: "API Usename (webapi_ 접두사 포함)",
serwersmsAPIPassword: "API 비밀번호", serwersmsAPIPassword: "API 비밀번호",
serwersmsPhoneNumber: "휴대전화 번호", serwersmsPhoneNumber: "휴대전화 번호",
serwersmsSenderName: "보내는 사람 이름 (registered via customer portal)", serwersmsSenderName: "보내는 사람 이름 (customer portal를 통해 가입된 정보)",
stackfield: "Stackfield", stackfield: "Stackfield",
}; };

@ -4,24 +4,24 @@ export default {
retryCheckEverySecond: "Prøv igjen hvert {0} sekund.", retryCheckEverySecond: "Prøv igjen hvert {0} sekund.",
retriesDescription: "Maksimalt antall forsøk før tjenesten er merket som nede og et varsel sendes", retriesDescription: "Maksimalt antall forsøk før tjenesten er merket som nede og et varsel sendes",
ignoreTLSError: "Ignorer TLS/SSL-feil for HTTPS-nettsteder", ignoreTLSError: "Ignorer TLS/SSL-feil for HTTPS-nettsteder",
upsideDownModeDescription: "Snu statusen opp ned. Hvis tjenesten er tilgjengelig, er den NED.", upsideDownModeDescription: "Snu statusen opp ned. Hvis tjenesten er tilgjengelig, er den NEDE.",
maxRedirectDescription: "Maksimalt antall viderekoblinger å følge. Sett til 0 for å deaktivere viderekoblinger.", maxRedirectDescription: "Maksimalt antall viderekoblinger å følge. Sett til 0 for å deaktivere viderekoblinger.",
acceptedStatusCodesDescription: "Velg statuskoder som anses som et vellykket svar.", acceptedStatusCodesDescription: "Velg statuskoder som anses som en vellykket respons.",
passwordNotMatchMsg: "Passordene stemmer ikke overens.", passwordNotMatchMsg: "Passordene stemmer ikke overens.",
notificationDescription: "Tilordne et varsel for å overvåkningen for å få det til å fungere.", notificationDescription: "Varsler må tilordnes en overvåkning for å fungere.",
keywordDescription: "Søk etter nøkkelord i vanlig HTML eller JSON, og det er versalfølsom", keywordDescription: "Søk etter nøkkelord i ren HTML eller JSON. Søket skiller mellom store og små bokstaver.",
pauseDashboardHome: "Pause", pauseDashboardHome: "Pause",
deleteMonitorMsg: "Er du sikker på at du vil slette denne overvåkningen?", deleteMonitorMsg: "Er du sikker på at du vil slette denne overvåkningen?",
deleteNotificationMsg: "Er du sikker på at du vil slette dette varselet for alle overvåkningene?", deleteNotificationMsg: "Er du sikker på at du vil slette dette varselet for alle overvåkningene?",
resoverserverDescription: "Cloudflare er standardserveren, kan du når som helst endre DNS-serveren.", resolverserverDescription: "Cloudflare er standardserveren. Du kan endre DNS-serveren når som helst.",
rrtypeDescription: "Velg RR-typen du vil overvåke", rrtypeDescription: "Velg RR-typen du vil overvåke",
pauseMonitorMsg: "Er du sikker på at du vil sette en pause?", pauseMonitorMsg: "Er du sikker på at du vil sette pause?",
enableDefaultNotificationDescription: "For hver ny overvåkning vil denne varslingen være aktivert som standard. Du kan fortsatt deaktivere varselet separat for hver overvåkning.", enableDefaultNotificationDescription: "For hver ny overvåkning vil denne varslingen være aktivert som standard. Du kan fortsatt deaktivere varselet separat for hver overvåkning.",
clearEventsMsg: "Er du sikker på at du vil slette alle hendelser for denne overvåkningen?", clearEventsMsg: "Er du sikker på at du vil slette alle hendelser for denne overvåkningen?",
clearHeartbeatsMsg: "Er du sikker på at du vil slette alle hjerteslag for denne overvåkningen?", clearHeartbeatsMsg: "Er du sikker på at du vil slette alle hjerteslag for denne overvåkningen?",
confirmClearStatisticsMsg: "Er du sikker på at du vil slette ALL statistikk?", confirmClearStatisticsMsg: "Er du sikker på at du vil slette ALL statistikk?",
importHandleDescription: "Velg 'Hopp over eksisterende' hvis du vil hoppe over hver overvåkning eller varsel med samme navn. 'Overskriv' sletter alle eksisterende overvåkninger og varsler.", importHandleDescription: "Velg 'Hopp over eksisterende' hvis du vil hoppe over hver overvåkning eller varsel med samme navn. 'Overskriv' sletter alle eksisterende overvåkninger og varsler.",
confirmImportMsg: "Er du sikker på å importere sikkerhetskopien? Sørg for at du har valgt riktig importalternativ.", confirmImportMsg: "Er du sikker på at du vil importere denne sikkerhetskopien? Sørg for at du har valgt riktig importalternativ.",
twoFAVerifyLabel: "Skriv inn tokenet ditt for å bekrefte at 2FA fungerer", twoFAVerifyLabel: "Skriv inn tokenet ditt for å bekrefte at 2FA fungerer",
tokenValidSettingsMsg: "Token er gyldig! Du kan nå lagre 2FA-innstillingene.", tokenValidSettingsMsg: "Token er gyldig! Du kan nå lagre 2FA-innstillingene.",
confirmEnableTwoFAMsg: "Er du sikker på at du vil aktivere 2FA?", confirmEnableTwoFAMsg: "Er du sikker på at du vil aktivere 2FA?",
@ -50,7 +50,7 @@ export default {
Message: "Melding", Message: "Melding",
"No important events": "Ingen viktige hendelser", "No important events": "Ingen viktige hendelser",
Resume: "Fortsett", Resume: "Fortsett",
Edit: "Endre", Edit: "Rediger",
Delete: "Slett", Delete: "Slett",
Current: "Nåværende", Current: "Nåværende",
Uptime: "Oppetid", Uptime: "Oppetid",
@ -77,7 +77,7 @@ export default {
"Accepted Status Codes": "Godkjente statuskoder", "Accepted Status Codes": "Godkjente statuskoder",
Save: "Lagre", Save: "Lagre",
Notifications: "Varsler", Notifications: "Varsler",
"Not available, please setup.": "Ikke tilgjengelig, sett opp.", "Not available, please setup.": "Ikke tilgjengelig, venligst sett opp.",
"Setup Notification": "Sett opp varsel", "Setup Notification": "Sett opp varsel",
Light: "Lys", Light: "Lys",
Dark: "Mørk", Dark: "Mørk",
@ -87,9 +87,9 @@ export default {
Bottom: "Bunn", Bottom: "Bunn",
None: "Ingen", None: "Ingen",
Timezone: "Tidssone", Timezone: "Tidssone",
"Search Engine Visibility": "Søkemotor synlighet", "Search Engine Visibility": "Søkemotor-synlighet",
"Allow indexing": "Tillat indeksering", "Allow indexing": "Tillat indeksering",
"Discourage search engines from indexing site": "Avskrekk søkemotorer fra å indeksere nettstedet", "Discourage search engines from indexing site": "Fraråd søkemotorer fra å indeksere nettstedet",
"Change Password": "Endre passord", "Change Password": "Endre passord",
"Current Password": "Nåværende passord", "Current Password": "Nåværende passord",
"New Password": "Nytt passord", "New Password": "Nytt passord",
@ -99,7 +99,7 @@ export default {
"Enable Auth": "Aktiver autentisering", "Enable Auth": "Aktiver autentisering",
Logout: "Logg ut", Logout: "Logg ut",
Leave: "Forlat", Leave: "Forlat",
"I understand, please disable": "Jeg forstår, deaktiver", "I understand, please disable": "Jeg forstår, vennligst deaktiver",
Confirm: "Bekreft", Confirm: "Bekreft",
Yes: "Ja", Yes: "Ja",
No: "Nei", No: "Nei",
@ -125,15 +125,15 @@ export default {
respTime: "Svartid (ms)", respTime: "Svartid (ms)",
notAvailableShort: "N/A", notAvailableShort: "N/A",
"Default enabled": "Standard aktivert", "Default enabled": "Standard aktivert",
"Apply on all existing monitors": "Påfør på alle eksisterende overvåkninger", "Apply on all existing monitors": "Anvend for alle eksisterende overvåkninger",
Create: "Opprett", Create: "Opprett",
"Clear Data": "Slett data", "Clear Data": "Slett data",
Events: "Hendelser", Events: "Hendelser",
Heartbeats: "Hjerteslag", Heartbeats: "Hjerteslag",
"Auto Get": "Auto Get", "Auto Get": "Auto Hent",
backupDescription: "Du kan sikkerhetskopiere alle overvåkninger og alle varsler til en JSON-fil.", backupDescription: "Du kan sikkerhetskopiere alle overvåkninger og alle varsler til en JSON-fil.",
backupDescription2: "PS: Historikk og hendelsesdata er ikke inkludert.", backupDescription2: "PS: Historikk og hendelsesdata er ikke inkludert.",
backupDescription3: "Følsomme data som varslingstokener er inkludert i eksportfilen. Vennligst oppbevar dem nøye.", backupDescription3: "Følsomme data som varslingstokener er inkludert i eksportfilen. Vennligst oppbevar dem sikkert.",
alertNoFile: "Velg en fil som skal importeres.", alertNoFile: "Velg en fil som skal importeres.",
alertWrongFileType: "Velg en JSON-fil.", alertWrongFileType: "Velg en JSON-fil.",
"Clear all statistics": "Fjern all statistikk", "Clear all statistics": "Fjern all statistikk",
@ -154,7 +154,7 @@ export default {
Tags: "Etiketter", Tags: "Etiketter",
"Add New below or Select...": "Legg til nytt nedenfor eller Velg ...", "Add New below or Select...": "Legg til nytt nedenfor eller Velg ...",
"Tag with this name already exist.": "Etikett med dette navnet eksisterer allerede.", "Tag with this name already exist.": "Etikett med dette navnet eksisterer allerede.",
"Tag with this value already exist.": "Etikett med denne verdien finnes allerede.", "Tag with this value already exist.": "Etikett med denne verdien eksisterer allerede.",
color: "farge", color: "farge",
"value (optional)": "verdi (valgfritt)", "value (optional)": "verdi (valgfritt)",
Gray: "Grå", Gray: "Grå",
@ -172,29 +172,30 @@ export default {
statusPageNothing: "Ingenting her, vennligst legg til en gruppe eller en overvåkning.", statusPageNothing: "Ingenting her, vennligst legg til en gruppe eller en overvåkning.",
"No Services": "Ingen tjenester", "No Services": "Ingen tjenester",
"All Systems Operational": "Alle systemer i drift", "All Systems Operational": "Alle systemer i drift",
"Partially Degraded Service": "Delvis degradert drift", "Partially Degraded Service": "Delvis degradert tjeneste",
"Degraded Service": "Degradert drift", "Degraded Service": "Degradert tjeneste",
"Add Group": "Legg til gruppe", "Add Group": "Legg til gruppe",
"Add a monitor": "Legg til en overvåkning", "Add a monitor": "Legg til en overvåkning",
"Edit Status Page": "Rediger statusside", "Edit Status Page": "Rediger statusside",
"Go to Dashboard": "Gå til Dashboard", "Go to Dashboard": "Gå til Dashboard",
"Status Page": "Statusside", "Status Page": "Statusside",
"Status Pages": "Statusside",
defaultNotificationName: "Min {notification} varsling ({number})", defaultNotificationName: "Min {notification} varsling ({number})",
here: "here", here: "her",
Required: "Obligatorisk", Required: "Obligatorisk",
telegram: "Telegram", telegram: "Telegram",
"Bot Token": "Bot Token", "Bot Token": "Bot Token",
wayToGetTelegramToken: "Du kan få et token fra {0}.", wayToGetTelegramToken: "Du kan få et token fra {0}.",
"Chat ID": "Chat ID", "Chat ID": "Chat ID",
supportTelegramChatID: "Support Direct Chat / Group / Channel's Chat ID", supportTelegramChatID: "Support Direct Chat / Group / Channel's Chat ID",
wayToGetTelegramChatID: "Du kan få chat-ID-en din ved å sende meldingen til boten og gå til denne nettadressen for å se chat_id:", wayToGetTelegramChatID: "Du kan få chat-ID-en din ved å sende en melding til boten og gå til denne nettadressen for å se chat_id:",
"YOUR BOT TOKEN HERE": "DITT BOT TOKEN HER", "YOUR BOT TOKEN HERE": "DITT BOT TOKEN HER",
chatIDNotFound: "Chat-ID ble ikke funnet. Send en melding til denne boten først", chatIDNotFound: "Chat-ID ble ikke funnet. Send en melding til denne boten først",
webhook: "Webhook", webhook: "Webhook",
"Post URL": "Post URL", "Post URL": "Post URL",
"Content Type": "Content Type", "Content Type": "Innholdstype",
webhookJsonDesc: "{0} er bra for alle moderne HTTP-servere som express.js", webhookJsonDesc: "{0} er bra for alle moderne HTTP-servere som express.js",
webhookFormDataDesc: "{multipart} er bra for PHP, du trenger bare å analysere JSON etter {decodeFunction}", webhookFormDataDesc: "{multipart} er bra for PHP. JSON trenger å bli analysert med {decodeFunction}",
smtp: "E-post (SMTP)", smtp: "E-post (SMTP)",
secureOptionNone: "None / STARTTLS (25, 587)", secureOptionNone: "None / STARTTLS (25, 587)",
secureOptionTLS: "TLS (465)", secureOptionTLS: "TLS (465)",
@ -205,7 +206,7 @@ export default {
smtpBCC: "BCC", smtpBCC: "BCC",
discord: "Discord", discord: "Discord",
"Discord Webhook URL": "Discord Webhook URL", "Discord Webhook URL": "Discord Webhook URL",
wayToGetDiscordURL: "Du kan få dette ved å gå til Serverinnstillinger -> Integrasjoner -> Webhooks -> Ny webhook", wayToGetDiscordURL: "Du kan få denne ved å gå til Serverinnstillinger -> Integrasjoner -> Opprett en Webhook",
"Bot Display Name": "Bot Visningsnavn", "Bot Display Name": "Bot Visningsnavn",
"Prefix Custom Message": "Prefiks tilpasset melding", "Prefix Custom Message": "Prefiks tilpasset melding",
"Hello @everyone is...": "Hei {'@'}everyone det er...", "Hello @everyone is...": "Hei {'@'}everyone det er...",
@ -240,43 +241,43 @@ export default {
pushbullet: "Pushbullet", pushbullet: "Pushbullet",
line: "Line Messenger", line: "Line Messenger",
mattermost: "Mattermost", mattermost: "Mattermost",
"User Key": "User Key", "User Key": "Bruker-nøkkel",
Device: "Device", Device: "Enhet",
"Message Title": "Message Title", "Message Title": "Meldingstittel",
"Notification Sound": "Notification Sound", "Notification Sound": "Notifikasjonslyd",
"More info on:": "More info on: {0}", "More info on:": "Mer info på: {0}",
pushoverDesc1: "Emergency priority (2) has default 30 second timeout between retries and will expire after 1 hour.", pushoverDesc1: "Nødsprioritet (2) har en standard 30 sekunders tidsavbrudd mellom forsøk og vil utløpe etter 1 time.",
pushoverDesc2: "If you want to send notifications to different devices, fill out Device field.", pushoverDesc2: "Hvis du vil sende varsler til forskjellige enheteter, fyll ut Enhet-feltet.",
"SMS Type": "SMS Type", "SMS Type": "SMS Type",
octopushTypePremium: "Premium (Fast - recommended for alerting)", octopushTypePremium: "Premium (Raskt - anbefalt for varsling)",
octopushTypeLowCost: "Low Cost (Slow, sometimes blocked by operator)", octopushTypeLowCost: "Lav kostnad (Sakte, noen ganger blokkert av leverandør)",
"Check octopush prices": "Check octopush prices {0}.", "Check octopush prices": "Sjekk octopush priser {0}.",
octopushPhoneNumber: "Phone number (intl format, eg : +33612345678) ", octopushPhoneNumber: "Telefonnummer (intl format, eg : +4791234567) ",
octopushSMSSender: "SMS Sender Name : 3-11 alphanumeric characters and space (a-zA-Z0-9)", octopushSMSSender: "SMS Avsendernavn : 3-11 alphanumeriske tegn og mellomrom (a-zA-Z0-9)",
"LunaSea Device ID": "LunaSea Device ID", "LunaSea Device ID": "LunaSea Enhet ID",
"Apprise URL": "Apprise URL", "Apprise URL": "Apprise URL",
"Example:": "Example: {0}", "Example:": "Eksempel: {0}",
"Read more:": "Read more: {0}", "Read more:": "Les mer: {0}",
"Status:": "Status: {0}", "Status:": "Status: {0}",
"Read more": "Read more", "Read more": "Les mer",
appriseInstalled: "Apprise is installed.", appriseInstalled: "Apprise er installert.",
appriseNotInstalled: "Apprise is not installed. {0}", appriseNotInstalled: "Apprise ikke installert. {0}",
"Access Token": "Access Token", "Access Token": "Tilgangs-Token",
"Channel access token": "Channel access token", "Channel access token": "Kanal tilgangs-token",
"Line Developers Console": "Line Developers Console", "Line Developers Console": "Line Utviklserskonsoll",
lineDevConsoleTo: "Line Developers Console - {0}", lineDevConsoleTo: "Line Utviklserskonsoll - {0}",
"Basic Settings": "Basic Settings", "Basic Settings": "Grunnleggende instillinger",
"User ID": "User ID", "User ID": "Bruker-ID",
"Messaging API": "Messaging API", "Messaging API": "Meldings-API",
wayToGetLineChannelToken: "First access the {0}, create a provider and channel (Messaging API), then you can get the channel access token and user id from the above mentioned menu items.", wayToGetLineChannelToken: "Først, få tilgang til {0}, lag en leverandør og kanal (Meldings-API), deretter kan du hente kanaltilgangs-token og bruker id fra menu-valgene nevnt over.",
"Icon URL": "Icon URL", "Icon URL": "Ikon URL",
aboutIconURL: "You can provide a link to a picture in \"Icon URL\" to override the default profile picture. Will not be used if Icon Emoji is set.", aboutIconURL: "Du kan gi en link til et bilde i \"Ikon URL\" for å overskrive det standard profilbildet. Vil ikke bli brukt hvis Ikon Emoji ikke er satt.",
aboutMattermostChannelName: "You can override the default channel that webhook posts to by entering the channel name into \"Channel Name\" field. This needs to be enabled in Mattermost webhook settings. Ex: #other-channel", aboutMattermostChannelName: "Du kan overskrive standardkanalen som webhook-en poster i ved å skrive enn kanalnavnet i \"Kanalnavn\" feltet. Dette må være skrudd på i Mattermost webhook-instillingene. Eks: #other-channel",
matrix: "Matrix", matrix: "Matrix",
promosmsTypeEco: "SMS ECO - cheap but slow and often overloaded. Limited only to Polish recipients.", promosmsTypeEco: "SMS ECO - billig, men treg og ofte overbelastet. Begrenset til bare polske mottakere.",
promosmsTypeFlash: "SMS FLASH - Message will automatically show on recipient device. Limited only to Polish recipients.", promosmsTypeFlash: "SMS FLASH - Melding vil automatisk vises på mottakker-enhet. Begrenset til bare polske mottakere.",
promosmsTypeFull: "SMS FULL - Premium tier of SMS, You can use Your Sender Name (You need to register name first). Reliable for alerts.", promosmsTypeFull: "SMS FULL - Premuimnivå SMS. Du kan bruke dit avsendernavn (Du må registerere et navn først). Pålitelig for alle varslinger.",
promosmsTypeSpeed: "SMS SPEED - Highest priority in system. Very quick and reliable but costly (about twice of SMS FULL price).", promosmsTypeSpeed: "SMS SPEED - Høyest prioritet i systemet.Veldig rask på pålitelig, men dyrt (omtrent det dobbeltet av SMS FULL pris).",
promosmsPhoneNumber: "Phone number (for Polish recipient You can skip area codes)", promosmsPhoneNumber: "Telefonnummber (for polske mottakere. Du trenger ikke områdekode.)",
promosmsSMSSender: "SMS Sender Name : Pre-registred name or one of defaults: InfoSMS, SMS Info, MaxSMS, INFO, SMS", promosmsSMSSender: "SMS Avsendernavn : Forhåndsregistert navn eller en av standardnavnene: InfoSMS, SMS Info, MaxSMS, INFO, SMS",
}; };

@ -12,7 +12,7 @@ export default {
pauseDashboardHome: "Gepauzeerd", pauseDashboardHome: "Gepauzeerd",
deleteMonitorMsg: "Weet u zeker dat u deze monitor wilt verwijderen?", deleteMonitorMsg: "Weet u zeker dat u deze monitor wilt verwijderen?",
deleteNotificationMsg: "Weet u zeker dat u deze melding voor alle monitoren wilt verwijderen?", deleteNotificationMsg: "Weet u zeker dat u deze melding voor alle monitoren wilt verwijderen?",
resoverserverDescription: "Cloudflare is de standaardserver, u kunt de resolver server op elk moment wijzigen.", resolverserverDescription: "Cloudflare is de standaardserver, u kunt de resolver server op elk moment wijzigen.",
rrtypeDescription: "Selecteer het RR-type dat u wilt monitoren", rrtypeDescription: "Selecteer het RR-type dat u wilt monitoren",
pauseMonitorMsg: "Weet je zeker dat je wilt pauzeren?", pauseMonitorMsg: "Weet je zeker dat je wilt pauzeren?",
enableDefaultNotificationDescription: "Voor elke nieuwe monitor wordt deze melding standaard ingeschakeld. U kunt de melding nog steeds afzonderlijk uitschakelen voor elke monitor.", enableDefaultNotificationDescription: "Voor elke nieuwe monitor wordt deze melding standaard ingeschakeld. U kunt de melding nog steeds afzonderlijk uitschakelen voor elke monitor.",
@ -180,6 +180,7 @@ export default {
"Edit Status Page": "Wijzig status pagina", "Edit Status Page": "Wijzig status pagina",
"Go to Dashboard": "Ga naar Dashboard", "Go to Dashboard": "Ga naar Dashboard",
"Status Page": "Status Pagina", "Status Page": "Status Pagina",
"Status Pages": "Status Pagina",
telegram: "Telegram", telegram: "Telegram",
webhook: "Webhook", webhook: "Webhook",
smtp: "Email (SMTP)", smtp: "Email (SMTP)",

@ -13,7 +13,7 @@ export default {
pauseDashboardHome: "Wstrzymane", pauseDashboardHome: "Wstrzymane",
deleteMonitorMsg: "Czy na pewno chcesz usunąć ten monitor?", deleteMonitorMsg: "Czy na pewno chcesz usunąć ten monitor?",
deleteNotificationMsg: "Czy na pewno chcesz usunąć to powiadomienie dla wszystkich monitorów?", deleteNotificationMsg: "Czy na pewno chcesz usunąć to powiadomienie dla wszystkich monitorów?",
resoverserverDescription: "Cloudflare jest domyślnym serwerem, możesz zmienić serwer resolver w każdej chwili.", resolverserverDescription: "Cloudflare jest domyślnym serwerem, możesz zmienić serwer resolver w każdej chwili.",
rrtypeDescription: "Wybierz rodzaj rekordu, który chcesz monitorować.", rrtypeDescription: "Wybierz rodzaj rekordu, który chcesz monitorować.",
pauseMonitorMsg: "Czy na pewno chcesz wstrzymać monitorowanie?", pauseMonitorMsg: "Czy na pewno chcesz wstrzymać monitorowanie?",
enableDefaultNotificationDescription: "Dla każdego nowego monitora to powiadomienie będzie domyślnie włączone. Nadal możesz wyłączyć powiadomienia osobno dla każdego monitora.", enableDefaultNotificationDescription: "Dla każdego nowego monitora to powiadomienie będzie domyślnie włączone. Nadal możesz wyłączyć powiadomienia osobno dla każdego monitora.",
@ -22,8 +22,8 @@ export default {
confirmClearStatisticsMsg: "Jesteś pewien, że chcesz usunąć WSZYSTKIE statystyki?", confirmClearStatisticsMsg: "Jesteś pewien, że chcesz usunąć WSZYSTKIE statystyki?",
importHandleDescription: "Wybierz 'Pomiń istniejące', jeśli chcesz pominąć każdy monitor lub powiadomienie o tej samej nazwie. 'Nadpisz' spowoduje usunięcie każdego istniejącego monitora i powiadomienia.", importHandleDescription: "Wybierz 'Pomiń istniejące', jeśli chcesz pominąć każdy monitor lub powiadomienie o tej samej nazwie. 'Nadpisz' spowoduje usunięcie każdego istniejącego monitora i powiadomienia.",
confirmImportMsg: "Czy na pewno chcesz zaimportować kopię zapasową? Upewnij się, że wybrałeś właściwą opcję importu.", confirmImportMsg: "Czy na pewno chcesz zaimportować kopię zapasową? Upewnij się, że wybrałeś właściwą opcję importu.",
twoFAVerifyLabel: "Proszę podaj swój token 2FA, aby sprawdzić czy 2FA działa.", twoFAVerifyLabel: "Proszę, podaj swój token 2FA, aby sprawdzić, czy 2FA działa.",
tokenValidSettingsMsg: "Token jest prawdiłowy! Teraz możesz zapisać ustawienia 2FA.", tokenValidSettingsMsg: "Token jest prawidłowy! Teraz możesz zapisać ustawienia 2FA.",
confirmEnableTwoFAMsg: "Jesteś pewien, że chcesz włączyć 2FA?", confirmEnableTwoFAMsg: "Jesteś pewien, że chcesz włączyć 2FA?",
confirmDisableTwoFAMsg: "Jesteś pewien, że chcesz wyłączyć 2FA?", confirmDisableTwoFAMsg: "Jesteś pewien, że chcesz wyłączyć 2FA?",
Settings: "Ustawienia", Settings: "Ustawienia",
@ -68,7 +68,7 @@ export default {
URL: "URL", URL: "URL",
Hostname: "Hostname", Hostname: "Hostname",
Port: "Port", Port: "Port",
"Heartbeat Interval": "Czętotliwość bicia serca", "Heartbeat Interval": "Częstotliwość bicia serca",
Retries: "Prób", Retries: "Prób",
"Heartbeat Retry Interval": "Częstotliwość ponawiania bicia serca", "Heartbeat Retry Interval": "Częstotliwość ponawiania bicia serca",
Advanced: "Zaawansowane", Advanced: "Zaawansowane",
@ -110,7 +110,7 @@ export default {
"No Monitors, please": "Brak monitorów, proszę", "No Monitors, please": "Brak monitorów, proszę",
"add one": "dodać jeden", "add one": "dodać jeden",
"Notification Type": "Rodzaj powiadomienia", "Notification Type": "Rodzaj powiadomienia",
Email: "Email", Email: "E-mail",
Test: "Test", Test: "Test",
"Certificate Info": "Informacje o certyfikacie", "Certificate Info": "Informacje o certyfikacie",
"Resolver Server": "Serwer rozwiązywania nazw", "Resolver Server": "Serwer rozwiązywania nazw",
@ -179,6 +179,7 @@ export default {
"Edit Status Page": "Edytuj stronę statusu", "Edit Status Page": "Edytuj stronę statusu",
"Go to Dashboard": "Idź do panelu", "Go to Dashboard": "Idź do panelu",
"Status Page": "Strona statusu", "Status Page": "Strona statusu",
"Status Pages": "Strona statusu",
defaultNotificationName: "Moje powiadomienie {notification} ({number})", defaultNotificationName: "Moje powiadomienie {notification} ({number})",
here: "tutaj", here: "tutaj",
Required: "Wymagane", Required: "Wymagane",
@ -188,13 +189,13 @@ export default {
"Chat ID": "Identyfikator czatu", "Chat ID": "Identyfikator czatu",
supportTelegramChatID: "Czat wsparcia technicznego / Bezpośrednia rozmowa / Czat grupowy", supportTelegramChatID: "Czat wsparcia technicznego / Bezpośrednia rozmowa / Czat grupowy",
wayToGetTelegramChatID: "Możesz uzyskać swój identyfikator czatu, wysyłając wiadomość do bota i przechodząc pod ten adres URL, aby wyświetlić identyfikator czatu:", wayToGetTelegramChatID: "Możesz uzyskać swój identyfikator czatu, wysyłając wiadomość do bota i przechodząc pod ten adres URL, aby wyświetlić identyfikator czatu:",
"YOUR BOT TOKEN HERE": "TWOJ TOKEN BOTA", "YOUR BOT TOKEN HERE": "TWÓJ TOKEN BOTA",
chatIDNotFound: "Identyfikator czatu nie znaleziony, najpierw napisz do bota", chatIDNotFound: "Identyfikator czatu nie znaleziony, najpierw napisz do bota",
webhook: "Webhook", webhook: "Webhook",
"Post URL": "Adres URL", "Post URL": "Adres URL",
"Content Type": "Rodzaj danych", "Content Type": "Rodzaj danych",
webhookJsonDesc: "{0} jest dobry w przypadku serwerów HTTP, takich jak express.js", webhookJsonDesc: "{0} jest dobry w przypadku serwerów HTTP, takich jak express.js",
webhookFormDataDesc: "{multipart} jest dobry dla PHP, musisz jedynie przetowrzyć dane przez {decodeFunction}", webhookFormDataDesc: "{multipart} jest dobry dla PHP, musisz jedynie przetworzyć dane przez {decodeFunction}",
smtp: "Email (SMTP)", smtp: "Email (SMTP)",
secureOptionNone: "Brak / STARTTLS (25, 587)", secureOptionNone: "Brak / STARTTLS (25, 587)",
secureOptionTLS: "TLS (465)", secureOptionTLS: "TLS (465)",
@ -205,7 +206,7 @@ export default {
smtpBCC: "UDW", smtpBCC: "UDW",
discord: "Discord", discord: "Discord",
"Discord Webhook URL": "URL webhook Discorda", "Discord Webhook URL": "URL webhook Discorda",
wayToGetDiscordURL: "Możesz go uzyskać przechodząc do Ustawienia serwera -> Integracje -> Tworzenie webhooka", wayToGetDiscordURL: "Możesz go uzyskać, przechodząc do Ustawienia serwera -> Integracje -> Tworzenie webhooka",
"Bot Display Name": "Wyświetlana nazwa bota", "Bot Display Name": "Wyświetlana nazwa bota",
"Prefix Custom Message": "Własny początek wiadomości", "Prefix Custom Message": "Własny początek wiadomości",
"Hello @everyone is...": "Hej {'@'}everyone ...", "Hello @everyone is...": "Hej {'@'}everyone ...",
@ -237,6 +238,7 @@ export default {
promosms: "PromoSMS", promosms: "PromoSMS",
lunasea: "LunaSea", lunasea: "LunaSea",
apprise: "Apprise (obsługuje 50+ usług powiadomień)", apprise: "Apprise (obsługuje 50+ usług powiadomień)",
GoogleChat: "Google Chat (wyłącznie Google Workspace)",
pushbullet: "Pushbullet", pushbullet: "Pushbullet",
line: "Line Messenger", line: "Line Messenger",
mattermost: "Mattermost", mattermost: "Mattermost",
@ -259,7 +261,7 @@ export default {
"Read more:": "Czytaj dalej: {0}", "Read more:": "Czytaj dalej: {0}",
"Status:": "Status: {0}", "Status:": "Status: {0}",
"Read more": "Czytaj dalej", "Read more": "Czytaj dalej",
appriseInstalled: "Apprise jest zostało zainstalowane.", appriseInstalled: "Apprise jest zainstalowane.",
appriseNotInstalled: "Apprise nie zostało zainstalowane. {0}", appriseNotInstalled: "Apprise nie zostało zainstalowane. {0}",
"Access Token": "Token dostępu", "Access Token": "Token dostępu",
"Channel access token": "Token dostępu kanału", "Channel access token": "Token dostępu kanału",
@ -289,7 +291,7 @@ export default {
"Feishu WebHookUrl": "Feishu WebHookURL", "Feishu WebHookUrl": "Feishu WebHookURL",
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.",
matrixDesc2: "Jest wysoce zalecane, abyś stworzył nowego użytkownika i nie używał tokena dostępu swojego użytkownika Matrix, ponieważ pozwoli on na pełny dostęp do twojego konta i wszystkich pokoi, do których dołączyłeś. Zamiast tego, utwórz nowego użytkownika i zaproś go tylko do pokoju, w którym chcesz otrzymywać powiadomienia. Możesz uzyskać token dostępu przez uruchomienie {0}", matrixDesc2: "Jest wysoce zalecane, abyś stworzył nowego użytkownika i nie używał tokena dostępu swojego użytkownika Matrix, ponieważ pozwoli on na pełny dostęp do twojego konta i wszystkich pokoi, do których dołączyłeś. Zamiast tego, utwórz nowego użytkownika i zaproś go tylko do pokoju, w którym chcesz otrzymywać powiadomienia. Możesz uzyskać token dostępu przez uruchomienie {0}",
Method: "Metoda", Method: "Metoda",
Body: "Treść", Body: "Treść",
@ -305,6 +307,44 @@ export default {
steamApiKeyDescription: "Do monitorowania serwera gier Steam potrzebny jest klucz Steam Web-API. Możesz zarejestrować swój klucz API tutaj: ", steamApiKeyDescription: "Do monitorowania serwera gier Steam potrzebny jest klucz Steam Web-API. Możesz zarejestrować swój klucz API tutaj: ",
"Current User": "Aktualny użytkownik", "Current User": "Aktualny użytkownik",
recent: "Ostatnie", recent: "Ostatnie",
Done: "Zrobione",
Info: "Info",
Security: "Bezpieczeństwo",
"Steam API Key": "Klucz Steam API",
"Shrink Database": "Zmniejsz bazę danych",
"Pick a RR-Type...": "Wybierz typ RR...",
"Pick Accepted Status Codes...": "Wybierz akceptowalne kody statusu...",
Default: "Domyślnie",
"HTTP Options": "Opcje HTTP",
"Create Incident": "Stwórz incydent",
Title: "Tytuł",
Content: "Treść",
Style: "Styl",
info: "info",
warning: "ostrzeżenie",
danger: "niebezpieczeństwo",
primary: "podstawowy",
light: "jasny",
dark: "ciemny",
Post: "Wyślij",
"Please input title and content": "Podaj tytuł i treść",
Created: "Stworzony",
"Last Updated": "Ostatnio zaktualizowany",
Unpin: "Odepnij",
"Switch to Light Theme": "Przełącz na jasny motyw",
"Switch to Dark Theme": "Przełącz na ciemny motyw",
"Show Tags": "Pokaż tagi",
"Hide Tags": "Ukryj tagi",
Description: "Opis",
"No monitors available.": "Brak dostępnych monitorów.",
"Add one": "Dodaj jeden",
"No Monitors": "Brak monitorów",
"Untitled Group": "Nienazwana grupa",
Services: "Usługi",
Discard: "Odrzuć",
Cancel: "Anuluj",
"Powered by": "Napędzane przez",
shrinkDatabaseDescription: "Uruchom VACUUM na bazie SQLite. Jeżeli twoja baza została stworzona po wersji 1.10.0, to posiada 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",
@ -312,4 +352,14 @@ export default {
serwersmsAPIPassword: "Hasło API", serwersmsAPIPassword: "Hasło API",
serwersmsPhoneNumber: "Numer telefonu", serwersmsPhoneNumber: "Numer telefonu",
serwersmsSenderName: "Nazwa nadawcy (zatwierdzona w panelu klienta)", serwersmsSenderName: "Nazwa nadawcy (zatwierdzona w panelu klienta)",
"stackfield": "Stackfield",
smtpDkimSettings: "Ustawienia DKIM",
smtpDkimDesc: "Zapoznaj się z Nodemailer DKIM {0}, aby dowiedzieć się więcej",
documentation: "dokumentacja",
smtpDkimDomain: "Nazwa domeny",
smtpDkimKeySelector: "Selektor klucza",
smtpDkimPrivateKey: "Klucz prywatny",
smtpDkimHashAlgo: "Algorytm Hashowania (opcjonalne)",
smtpDkimheaderFieldNames: "Klucze nagłówka do podpisu (opcjonalne)",
smtpDkimskipFields: "Klucze nagłówka do pominięcia (opcjonalne)",
}; };

@ -13,7 +13,7 @@ export default {
pauseDashboardHome: "Pausar", pauseDashboardHome: "Pausar",
deleteMonitorMsg: "Tem certeza de que deseja excluir este monitor?", deleteMonitorMsg: "Tem certeza de que deseja excluir este monitor?",
deleteNotificationMsg: "Tem certeza de que deseja excluir esta notificação para todos os monitores?", deleteNotificationMsg: "Tem certeza de que deseja excluir esta notificação para todos os monitores?",
resoverserverDescription: "Cloudflare é o servidor padrão, você pode alterar o servidor resolvedor a qualquer momento.", resolverserverDescription: "Cloudflare é o servidor padrão, você pode alterar o servidor resolvedor a qualquer momento.",
rrtypeDescription: "Selecione o RR-Type que você deseja monitorar", rrtypeDescription: "Selecione o RR-Type que você deseja monitorar",
pauseMonitorMsg: "Tem certeza que deseja fazer uma pausa?", pauseMonitorMsg: "Tem certeza que deseja fazer uma pausa?",
enableDefaultNotificationDescription: "Para cada novo monitor, esta notificação será habilitada por padrão. Você ainda pode desativar a notificação separadamente para cada monitor.", enableDefaultNotificationDescription: "Para cada novo monitor, esta notificação será habilitada por padrão. Você ainda pode desativar a notificação separadamente para cada monitor.",
@ -169,6 +169,7 @@ export default {
"Avg. Ping": "Ping Médio.", "Avg. Ping": "Ping Médio.",
"Avg. Response": "Resposta Média. ", "Avg. Response": "Resposta Média. ",
"Status Page": "Página de Status", "Status Page": "Página de Status",
"Status Pages": "Página de Status",
"Entry Page": "Página de entrada", "Entry Page": "Página de entrada",
statusPageNothing: "Nada aqui, por favor, adicione um grupo ou monitor.", statusPageNothing: "Nada aqui, por favor, adicione um grupo ou monitor.",
"No Services": "Nenhum Serviço", "No Services": "Nenhum Serviço",

@ -12,11 +12,11 @@ export default {
pauseDashboardHome: "Пауза", pauseDashboardHome: "Пауза",
deleteMonitorMsg: "Вы действительно хотите удалить данный монитор?", deleteMonitorMsg: "Вы действительно хотите удалить данный монитор?",
deleteNotificationMsg: "Вы действительно хотите удалить это уведомление для всех мониторов?", deleteNotificationMsg: "Вы действительно хотите удалить это уведомление для всех мониторов?",
resoverserverDescription: "Cloudflare является сервером по умолчанию. Вы всегда можете сменить данный сервер.", resolverserverDescription: "Cloudflare является сервером по умолчанию. Вы всегда можете сменить данный сервер.",
rrtypeDescription: "Выберите тип ресурсной записи, который вы хотите отслеживать", rrtypeDescription: "Выберите тип ресурсной записи, который вы хотите отслеживать",
pauseMonitorMsg: "Вы действительно хотите поставить на паузу?", pauseMonitorMsg: "Вы действительно хотите поставить на паузу?",
Settings: "Настройки", Settings: "Настройки",
Dashboard: "Панель мониторов", Dashboard: "Панель управления",
"New Update": "Обновление", "New Update": "Обновление",
Language: "Язык", Language: "Язык",
Appearance: "Внешний вид", Appearance: "Внешний вид",
@ -60,7 +60,7 @@ export default {
"Heartbeat Interval": "Частота опроса", "Heartbeat Interval": "Частота опроса",
Retries: "Попыток", Retries: "Попыток",
Advanced: "Дополнительно", Advanced: "Дополнительно",
"Upside Down Mode": "Режим реверса статуса", "Upside Down Mode": "Реверс статуса",
"Max. Redirects": "Макс. количество перенаправлений", "Max. Redirects": "Макс. количество перенаправлений",
"Accepted Status Codes": "Допустимые коды статуса", "Accepted Status Codes": "Допустимые коды статуса",
Save: "Сохранить", Save: "Сохранить",
@ -75,9 +75,9 @@ export default {
Bottom: "Снизу", Bottom: "Снизу",
None: "Отсутствует", None: "Отсутствует",
Timezone: "Часовой пояс", Timezone: "Часовой пояс",
"Search Engine Visibility": "Видимость поисковым движком", "Search Engine Visibility": "Индексация поисковыми системами:",
"Allow indexing": "Разрешить индексирование", "Allow indexing": "Разрешить индексирование",
"Discourage search engines from indexing site": "Не позволять индексировать сайт", "Discourage search engines from indexing site": "Запретить индексирование",
"Change Password": "Сменить пароль", "Change Password": "Сменить пароль",
"Current Password": "Текущий пароль", "Current Password": "Текущий пароль",
"New Password": "Новый пароль", "New Password": "Новый пароль",
@ -96,6 +96,7 @@ export default {
"Remember me": "Запомнить меня", "Remember me": "Запомнить меня",
Login: "Вход в систему", Login: "Вход в систему",
"No Monitors, please": "Мониторов нет, пожалуйста", "No Monitors, please": "Мониторов нет, пожалуйста",
"No Monitors": "Мониторы отсутствуют",
"add one": "создайте новый", "add one": "создайте новый",
"Notification Type": "Тип уведомления", "Notification Type": "Тип уведомления",
Email: "Почта", Email: "Почта",
@ -107,7 +108,7 @@ export default {
"Create your admin account": "Создайте аккаунт администратора", "Create your admin account": "Создайте аккаунт администратора",
"Repeat Password": "Повторите пароль", "Repeat Password": "Повторите пароль",
respTime: "Время ответа (мс)", respTime: "Время ответа (мс)",
notAvailableShort: "Н", notAvailableShort: "N/A",
Create: "Создать", Create: "Создать",
clearEventsMsg: "Вы действительно хотите удалить всю статистику событий данного монитора?", clearEventsMsg: "Вы действительно хотите удалить всю статистику событий данного монитора?",
clearHeartbeatsMsg: "Вы действительно хотите удалить всю статистику опросов данного монитора?", clearHeartbeatsMsg: "Вы действительно хотите удалить всю статистику опросов данного монитора?",
@ -119,8 +120,8 @@ export default {
enableDefaultNotificationDescription: "Для каждого нового монитора это уведомление будет включено по умолчанию. Вы всё ещё можете отключить уведомления в каждом мониторе отдельно.", enableDefaultNotificationDescription: "Для каждого нового монитора это уведомление будет включено по умолчанию. Вы всё ещё можете отключить уведомления в каждом мониторе отдельно.",
"Default enabled": "Использовать по умолчанию", "Default enabled": "Использовать по умолчанию",
"Also apply to existing monitors": "Применить к существующим мониторам", "Also apply to existing monitors": "Применить к существующим мониторам",
Export: "Резервная копия", Export: "Экспорт",
Import: "Восстановление", Import: "Импорт",
backupDescription: "Вы можете сохранить резервную копию всех мониторов и уведомлений в виде JSON-файла", backupDescription: "Вы можете сохранить резервную копию всех мониторов и уведомлений в виде JSON-файла",
backupDescription2: "P.S. История и события сохранены не будут", backupDescription2: "P.S. История и события сохранены не будут",
backupDescription3: "Важные данные, такие как токены уведомлений, добавляются при экспорте, поэтому храните файлы в безопасном месте", backupDescription3: "Важные данные, такие как токены уведомлений, добавляются при экспорте, поэтому храните файлы в безопасном месте",
@ -141,13 +142,13 @@ export default {
Inactive: "Неактивно", Inactive: "Неактивно",
Token: "Токен", Token: "Токен",
"Show URI": "Показать URI", "Show URI": "Показать URI",
"Clear all statistics": "Удалить всю статистику", "Clear all statistics": "Очистить статистику",
retryCheckEverySecond: "Повтор каждые {0} секунд", retryCheckEverySecond: "Повтор каждые {0} секунд",
importHandleDescription: "Выберите \"Пропустить существующие\", если вы хотите пропустить каждый монитор или уведомление с таким же именем. \"Перезаписать\" удалит каждый существующий монитор или уведомление и добавит заново. Вариант \"Не проверять\" принудительно восстанавливает все мониторы и уведомления, даже если они уже существуют.", importHandleDescription: "Выберите \"Пропустить существующие\", если вы хотите пропустить каждый монитор или уведомление с таким же именем. \"Перезаписать\" удалит каждый существующий монитор или уведомление и добавит заново. Вариант \"Не проверять\" принудительно восстанавливает все мониторы и уведомления, даже если они уже существуют.",
confirmImportMsg: "Вы действительно хотите восстановить резервную копию? Убедитесь, что вы выбрали подходящий вариант импорта.", confirmImportMsg: "Вы действительно хотите восстановить резервную копию? Убедитесь, что вы выбрали подходящий вариант импорта.",
"Heartbeat Retry Interval": "Интервал повтора опроса", "Heartbeat Retry Interval": "Интервал повтора опроса",
"Import Backup": "Восстановление резервной копии", "Import Backup": "Импорт",
"Export Backup": "Резервная копия", "Export Backup": "Скачать",
"Skip existing": "Пропустить существующие", "Skip existing": "Пропустить существующие",
Overwrite: "Перезаписать", Overwrite: "Перезаписать",
Options: "Опции", Options: "Опции",
@ -167,19 +168,20 @@ export default {
Purple: "Пурпурный", Purple: "Пурпурный",
Pink: "Розовый", Pink: "Розовый",
"Search...": "Поиск...", "Search...": "Поиск...",
"Avg. Ping": "Средн. пинг", "Avg. Ping": "Среднее значение пинга",
"Avg. Response": "Средн. ответ", "Avg. Response": "Среднее время ответа",
"Entry Page": "Главная страница", "Entry Page": "Главная страница",
statusPageNothing: "Здесь пусто. Добавьте группу или монитор.", statusPageNothing: "Здесь пусто. Добавьте группу или монитор.",
"No Services": "Нет сервисов", "No Services": "Нет сервисов",
"All Systems Operational": "Все сервисы работают", "All Systems Operational": "Все системы работают в штатном режиме",
"Partially Degraded Service": "Сервисы частично не работают", "Partially Degraded Service": "Сервисы работают частично",
"Degraded Service": "Все сервисы не работают", "Degraded Service": "Все сервисы не работают",
"Add Group": "Добавить группу", "Add Group": "Добавить группу",
"Add a monitor": "Добавить монитор", "Add a monitor": "Добавить монитор",
"Edit Status Page": "Редактировать", "Edit Status Page": "Редактировать",
"Go to Dashboard": "Панель мониторов", "Go to Dashboard": "Панель управления",
"Status Page": "Статус сервисов", "Status Page": "Страница статуса",
"Status Pages": "Страницы статуса",
Discard: "Отмена", Discard: "Отмена",
"Create Incident": "Создать инцидент", "Create Incident": "Создать инцидент",
"Switch to Dark Theme": "Тёмная тема", "Switch to Dark Theme": "Тёмная тема",
@ -302,10 +304,89 @@ export default {
PushUrl: "URL пуша", PushUrl: "URL пуша",
HeadersInvalidFormat: "Заголовки запроса некорректны JSON: ", HeadersInvalidFormat: "Заголовки запроса некорректны JSON: ",
BodyInvalidFormat: "Тело запроса некорректно JSON: ", BodyInvalidFormat: "Тело запроса некорректно JSON: ",
"Monitor History": "История мониторов", "Monitor History": "Статистика",
clearDataOlderThan: "Сохранять историю мониторов в течение {0} дней.", clearDataOlderThan: "Сохранять статистику за {0} дней.",
PasswordsDoNotMatch: "Пароли не совпадают.", PasswordsDoNotMatch: "Пароли не совпадают.",
records: "записей", records: "записей",
"One record": "Одна запись", "One record": "Одна запись",
steamApiKeyDescription: "Для мониторинга игрового сервера Steam вам необходим Web-API ключ Steam. Зарегистрировать его можно здесь: ", steamApiKeyDescription: "Для мониторинга игрового сервера Steam вам необходим Web-API ключ Steam. Зарегистрировать его можно здесь: ",
"Certificate Chain": "Цепочка сертификатов",
Valid: "Действительный",
"Hide Tags": "Скрыть тэги",
Title: "Название инцидента:",
Content: "Содержание инцидента:",
Post: "Опубликовать",
Cancel: "Отмена",
Created: "Создано",
Unpin: "Открепить",
"Show Tags": "Показать тэги",
recent: "Сейчас",
"3h": "3 часа",
"6h": "6 часов",
"24h": "24 часа",
"1w": "1 неделя",
"No monitors available.": "Нет доступных мониторов",
"Add one": "Добавить новый",
Backup: "Резервная копия",
Security: "Безопасность",
"Shrink Database": "Сжать Базу Данных",
"Current User": "Текущий пользователь",
About: "О программе",
Description: "Описание",
"Powered by": "Работает на основе скрипта от",
shrinkDatabaseDescription: "Включает VACUUM для базы данных SQLite. Если ваша база данных была создана на версии 1.10.0 и более, AUTO_VACUUM уже включен и это действие не требуется.",
deleteStatusPageMsg: "Вы действительно хотите удалить эту страницу статуса сервисов?",
Style: "Стиль",
info: "ИНФО",
warning: "ВНИМАНИЕ",
danger: "ОШИБКА",
primary: "ОСНОВНОЙ",
light: "СВЕТЛЫЙ",
dark: "ТЕМНЫЙ",
"New Status Page": "Новая страница статуса",
"Show update if available": "Показывать доступные обновления",
"Also check beta release": "Проверять обновления для бета версий",
"Add New Status Page": "Добавить страницу статуса",
Next: "Далее",
"Accept characters: a-z 0-9 -": "Разрешены символы: a-z 0-9 -",
"Start or end with a-z 0-9 only": "Начало и окончание имени только на символы: a-z 0-9",
"No consecutive dashes --": "Запрещено использовать тире --",
"HTTP Options": "HTTP Опции",
"Basic Auth": "HTTP Авторизация",
PushByTechulus: "Push by Techulus",
clicksendsms: "ClickSend SMS",
GoogleChat: "Google Chat (только Google Workspace)",
apiCredentials: "API реквизиты",
Done: "Готово",
Info: "Инфо",
"Steam API Key": "Steam API-Ключ",
"Pick a RR-Type...": "Выберите RR-Тип...",
"Pick Accepted Status Codes...": "Выберите принятые коды состояния...",
Default: "По умолчанию",
"Please input title and content": "Пожалуйста, введите название и содержание",
"Last Updated": "Последнее Обновление",
"Untitled Group": "Группа без названия",
Services: "Сервисы",
serwersms: "SerwerSMS.pl",
serwersmsAPIUser: "API Пользователь (включая префикс webapi_)",
serwersmsAPIPassword: "API Пароль",
serwersmsPhoneNumber: "Номер телефона",
serwersmsSenderName: "SMS Имя Отправителя (регистрированный через пользовательский портал)",
stackfield: "Stackfield",
smtpDkimSettings: "DKIM Настройки",
smtpDkimDesc: "Please refer to the Nodemailer DKIM {0} for usage.",
documentation: "документация",
smtpDkimDomain: "Имя Домена",
smtpDkimKeySelector: "Ключ",
smtpDkimPrivateKey: "Приватный ключ",
smtpDkimHashAlgo: "Алгоритм хэша (опционально)",
smtpDkimheaderFieldNames: "Заголовок ключей для подписи (опционально)",
smtpDkimskipFields: "Заколовок ключей не для подписи (опционально)",
gorush: "Gorush",
alerta: "Alerta",
alertaApiEndpoint: "Конечная точка API",
alertaEnvironment: "Среда",
alertaApiKey: "Ключ API",
alertaAlertState: "Состояние алерта",
alertaRecoverState: "Состояние восстановления",
}; };

@ -2,9 +2,9 @@ export default {
languageName: "Slovenščina", languageName: "Slovenščina",
checkEverySecond: "Preveri na vsakih {0} sekund", checkEverySecond: "Preveri na vsakih {0} sekund",
retryCheckEverySecond: "Ponovno poskusi na vsakih {0} sekund", retryCheckEverySecond: "Ponovno poskusi na vsakih {0} sekund",
retriesDescription: "Maksimalno število poskusov predenj se storitev označi kot nedosegljiva in se pošlje obvestilo", retriesDescription: "Maksimalno število poskusov predenj se storitev označi kot 'ne deluje' in se pošlje obvestilo",
ignoreTLSError: "Ignoriraj TLS/SSL napake za HTTPS spletne strani", ignoreTLSError: "Ignoriraj TLS/SSL napake za HTTPS spletne strani",
upsideDownModeDescription: "Negiraj status. Če je storitev dosegljiva je NEDOSEGLJIVA.", upsideDownModeDescription: "Negiraj status. Če je storitev deluje bo označena kot 'ne deluje'.",
maxRedirectDescription: "Maksimalno število sledečih preusmeritev. 0 onemogoči preusmeritve.", maxRedirectDescription: "Maksimalno število sledečih preusmeritev. 0 onemogoči preusmeritve.",
acceptedStatusCodesDescription: "Izberi kode statusa veljavna kot uspešen odgovor.", acceptedStatusCodesDescription: "Izberi kode statusa veljavna kot uspešen odgovor.",
passwordNotMatchMsg: "Ponovljeno geslo se ne ujema.", passwordNotMatchMsg: "Ponovljeno geslo se ne ujema.",
@ -13,7 +13,7 @@ export default {
pauseDashboardHome: "Pavza", pauseDashboardHome: "Pavza",
deleteMonitorMsg: "Ste prepričani, da želite izbrisati ta monitor?", deleteMonitorMsg: "Ste prepričani, da želite izbrisati ta monitor?",
deleteNotificationMsg: "Ste prepričani, da želite izbrisati to obvestilo za vse monitorje?", deleteNotificationMsg: "Ste prepričani, da želite izbrisati to obvestilo za vse monitorje?",
resoverserverDescription: "Cloudflare je privzeti strežnik. DNS strežnik lahko spremenite kadarkoli.", resolverserverDescription: "Cloudflare je privzeti strežnik. DNS strežnik lahko spremenite kadarkoli.",
rrtypeDescription: "Izberite RR tip, ki ga želite spremljati", rrtypeDescription: "Izberite RR tip, ki ga želite spremljati",
pauseMonitorMsg: "Ste prepričani, da želite pavzirati?", pauseMonitorMsg: "Ste prepričani, da želite pavzirati?",
enableDefaultNotificationDescription: "To obvestilo bo kot privzeto omogočeno za vse nove monitorje. Še vedno ga lahko izključite posebej za vsak monitor.", enableDefaultNotificationDescription: "To obvestilo bo kot privzeto omogočeno za vse nove monitorje. Še vedno ga lahko izključite posebej za vsak monitor.",
@ -40,8 +40,8 @@ export default {
Add: "Dodaj", Add: "Dodaj",
"Add New Monitor": "Dodaj nov monitor", "Add New Monitor": "Dodaj nov monitor",
"Quick Stats": "Hitro stanje", "Quick Stats": "Hitro stanje",
Up: "Dosegljiv", Up: "Deluje",
Down: "Nedosegljiv", Down: "Ne deluje",
Pending: "Na čakanju", Pending: "Na čakanju",
Unknown: "Neznano", Unknown: "Neznano",
Pause: "Pavza", Pause: "Pavza",
@ -58,14 +58,14 @@ export default {
"Cert Exp.": "Potek certifikata", "Cert Exp.": "Potek certifikata",
days: "dni", days: "dni",
day: "dan", day: "dan",
"-day": "-dan", "-day": "-dni",
hour: "ura", hour: "ura",
"-hour": "-ura", "-hour": "-ur",
Response: "Odgovor", Response: "Odgovor",
Ping: "Ping", Ping: "Ping",
"Monitor Type": "Tip monitorja", "Monitor Type": "Tip monitorja",
Keyword: "Ključna beseda", Keyword: "Ključna beseda",
"Friendly Name": "Prijazno ime", "Friendly Name": "Ime za prikaz",
URL: "URL", URL: "URL",
Hostname: "Hostname", Hostname: "Hostname",
Port: "Vrata", Port: "Vrata",
@ -182,7 +182,8 @@ export default {
"Add a monitor": "Dodaj monitor", "Add a monitor": "Dodaj monitor",
"Edit Status Page": "Uredi statusno stran", "Edit Status Page": "Uredi statusno stran",
"Go to Dashboard": "Pojdi na nadzorno ploščo", "Go to Dashboard": "Pojdi na nadzorno ploščo",
"Status Page": "Statusna stran", "Status Page": "Página de Status",
"Status Pages": "Página de Status",
defaultNotificationName: "Moje {notification} Obvestilo ({number})", defaultNotificationName: "Moje {notification} Obvestilo ({number})",
here: "tukaj", here: "tukaj",
Required: "Obvezno", Required: "Obvezno",
@ -339,7 +340,6 @@ export default {
"No monitors available.": "Nobenega monitorja ni na voljo.", "No monitors available.": "Nobenega monitorja ni na voljo.",
"Add one": "Dodaj enega", "Add one": "Dodaj enega",
"No Monitors": "Ni monitorjev", "No Monitors": "Ni monitorjev",
"Add one": "Dodaj enega",
"Untitled Group": "Skupina brez imena", "Untitled Group": "Skupina brez imena",
Services: "Storitve", Services: "Storitve",
Discard: "zavrzi", Discard: "zavrzi",
@ -352,4 +352,4 @@ export default {
serwersmsPhoneNumber: "Telefonska številka", serwersmsPhoneNumber: "Telefonska številka",
serwersmsSenderName: "Ime SMS pošiljatelja (registrirani prek portala za stranke)", serwersmsSenderName: "Ime SMS pošiljatelja (registrirani prek portala za stranke)",
"stackfield": "Stackfield", "stackfield": "Stackfield",
}; };

@ -12,7 +12,7 @@ export default {
pauseDashboardHome: "Pauziraj", pauseDashboardHome: "Pauziraj",
deleteMonitorMsg: "Da li ste sigurni da želite da obrišete ovog posmatrača?", deleteMonitorMsg: "Da li ste sigurni da želite da obrišete ovog posmatrača?",
deleteNotificationMsg: "Da li ste sigurni d aželite da uklonite ovo obaveštenje za sve posmatrače?", deleteNotificationMsg: "Da li ste sigurni d aželite da uklonite ovo obaveštenje za sve posmatrače?",
resoverserverDescription: "Cloudflare je podrazumevani server. Možete promeniti server za raszrešavanje u bilo kom trenutku.", resolverserverDescription: "Cloudflare je podrazumevani server. Možete promeniti server za raszrešavanje u bilo kom trenutku.",
rrtypeDescription: "Odaberite RR-Type koji želite da posmatrate", rrtypeDescription: "Odaberite RR-Type koji želite da posmatrate",
pauseMonitorMsg: "Da li ste sigurni da želite da pauzirate?", pauseMonitorMsg: "Da li ste sigurni da želite da pauzirate?",
Settings: "Podešavanja", Settings: "Podešavanja",
@ -180,6 +180,7 @@ export default {
"Edit Status Page": "Edit Status Page", "Edit Status Page": "Edit Status Page",
"Go to Dashboard": "Go to Dashboard", "Go to Dashboard": "Go to Dashboard",
"Status Page": "Status Page", "Status Page": "Status Page",
"Status Pages": "Status Pages",
telegram: "Telegram", telegram: "Telegram",
webhook: "Webhook", webhook: "Webhook",
smtp: "Email (SMTP)", smtp: "Email (SMTP)",

@ -12,7 +12,7 @@ export default {
pauseDashboardHome: "Паузирај", pauseDashboardHome: "Паузирај",
deleteMonitorMsg: "Да ли сте сигурни да желите да обришете овог посматрача?", deleteMonitorMsg: "Да ли сте сигурни да желите да обришете овог посматрача?",
deleteNotificationMsg: "Да ли сте сигурни д ажелите да уклоните ово обавештење за све посматраче?", deleteNotificationMsg: "Да ли сте сигурни д ажелите да уклоните ово обавештење за све посматраче?",
resoverserverDescription: "Cloudflare је подразумевани сервер. Можете променити сервер за расзрешавање у било ком тренутку.", resolverserverDescription: "Cloudflare је подразумевани сервер. Можете променити сервер за расзрешавање у било ком тренутку.",
rrtypeDescription: "Одаберите RR-Type који желите да посматрате", rrtypeDescription: "Одаберите RR-Type који желите да посматрате",
pauseMonitorMsg: "Да ли сте сигурни да желите да паузирате?", pauseMonitorMsg: "Да ли сте сигурни да желите да паузирате?",
Settings: "Подешавања", Settings: "Подешавања",
@ -180,6 +180,7 @@ export default {
"Edit Status Page": "Edit Status Page", "Edit Status Page": "Edit Status Page",
"Go to Dashboard": "Go to Dashboard", "Go to Dashboard": "Go to Dashboard",
"Status Page": "Status Page", "Status Page": "Status Page",
"Status Pages": "Status Pages",
telegram: "Telegram", telegram: "Telegram",
webhook: "Webhook", webhook: "Webhook",
smtp: "Email (SMTP)", smtp: "Email (SMTP)",

@ -12,7 +12,7 @@ export default {
pauseDashboardHome: "Pausa", pauseDashboardHome: "Pausa",
deleteMonitorMsg: "Är du säker på att du vill ta bort den här övervakningen?", deleteMonitorMsg: "Är du säker på att du vill ta bort den här övervakningen?",
deleteNotificationMsg: "Är du säker på att du vill ta bort den här notisen för alla övervakare?", deleteNotificationMsg: "Är du säker på att du vill ta bort den här notisen för alla övervakare?",
resoverserverDescription: "Cloudflare är den förvalda servern. Du kan byta resolver när som helst.", resolverserverDescription: "Cloudflare är den förvalda servern. Du kan byta resolver när som helst.",
rrtypeDescription: "Välj den RR-typ du vill övervaka", rrtypeDescription: "Välj den RR-typ du vill övervaka",
pauseMonitorMsg: "Är du säker på att du vill pausa?", pauseMonitorMsg: "Är du säker på att du vill pausa?",
Settings: "Inställningar", Settings: "Inställningar",
@ -108,94 +108,4 @@ export default {
"Repeat Password": "Upprepa Lösenord", "Repeat Password": "Upprepa Lösenord",
respTime: "Svarstid (ms)", respTime: "Svarstid (ms)",
notAvailableShort: "Ej Tillg.", notAvailableShort: "Ej Tillg.",
Create: "Create",
clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",
"Clear Data": "Clear Data",
Events: "Events",
Heartbeats: "Heartbeats",
"Auto Get": "Auto Get",
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.",
"Default enabled": "Default enabled",
"Also apply to existing monitors": "Also apply to existing monitors",
Export: "Export",
Import: "Import",
backupDescription: "You can backup all monitors and all notifications into a JSON file.",
backupDescription2: "PS: History and event data is not included.",
backupDescription3: "Sensitive data such as notification tokens is included in the export file, please keep it carefully.",
alertNoFile: "Please select a file to import.",
alertWrongFileType: "Please select a JSON file.",
twoFAVerifyLabel: "Please type in your token to verify that 2FA is working",
tokenValidSettingsMsg: "Token is valid! You can now save the 2FA settings.",
confirmEnableTwoFAMsg: "Are you sure you want to enable 2FA?",
confirmDisableTwoFAMsg: "Are you sure you want to disable 2FA?",
"Apply on all existing monitors": "Apply on all existing monitors",
"Verify Token": "Verify Token",
"Setup 2FA": "Setup 2FA",
"Enable 2FA": "Enable 2FA",
"Disable 2FA": "Disable 2FA",
"2FA Settings": "2FA Settings",
"Two Factor Authentication": "Two Factor Authentication",
Active: "Active",
Inactive: "Inactive",
Token: "Token",
"Show URI": "Show URI",
"Clear all statistics": "Clear all Statistics",
retryCheckEverySecond: "Retry every {0} seconds.",
importHandleDescription: "Choose 'Skip existing' if you want to skip every monitor or notification with the same name. 'Overwrite' will delete every existing monitor and notification.",
confirmImportMsg: "Are you sure to import the backup? Please make sure you've selected the right import option.",
"Heartbeat Retry Interval": "Heartbeat Retry Interval",
"Import Backup": "Import Backup",
"Export Backup": "Export Backup",
"Skip existing": "Skip existing",
Overwrite: "Overwrite",
Options: "Options",
"Keep both": "Keep both",
Tags: "Tags",
"Add New below or Select...": "Add New below or Select...",
"Tag with this name already exist.": "Tag with this name already exist.",
"Tag with this value already exist.": "Tag with this value already exist.",
color: "color",
"value (optional)": "value (optional)",
Gray: "Gray",
Red: "Red",
Orange: "Orange",
Green: "Green",
Blue: "Blue",
Indigo: "Indigo",
Purple: "Purple",
Pink: "Pink",
"Search...": "Search...",
"Avg. Ping": "Avg. Ping",
"Avg. Response": "Avg. Response",
"Entry Page": "Entry Page",
statusPageNothing: "Nothing here, please add a group or a monitor.",
"No Services": "No Services",
"All Systems Operational": "All Systems Operational",
"Partially Degraded Service": "Partially Degraded Service",
"Degraded Service": "Degraded Service",
"Add Group": "Add Group",
"Add a monitor": "Add a monitor",
"Edit Status Page": "Edit Status Page",
"Go to Dashboard": "Go to Dashboard",
"Status Page": "Status Page",
telegram: "Telegram",
webhook: "Webhook",
smtp: "Email (SMTP)",
discord: "Discord",
teams: "Microsoft Teams",
signal: "Signal",
gotify: "Gotify",
slack: "Slack",
"rocket.chat": "Rocket.chat",
pushover: "Pushover",
pushy: "Pushy",
octopush: "Octopush",
promosms: "PromoSMS",
lunasea: "LunaSea",
apprise: "Apprise (Support 50+ Notification services)",
pushbullet: "Pushbullet",
line: "Line Messenger",
mattermost: "Mattermost",
}; };

@ -12,7 +12,7 @@ export default {
pauseDashboardHome: "Durdur", pauseDashboardHome: "Durdur",
deleteMonitorMsg: "Servisi silmek istediğinden emin misin?", deleteMonitorMsg: "Servisi silmek istediğinden emin misin?",
deleteNotificationMsg: "Bu bildirimi tüm servisler için silmek istediğinden emin misin?", deleteNotificationMsg: "Bu bildirimi tüm servisler için silmek istediğinden emin misin?",
resoverserverDescription: "Cloudflare varsayılan sunucudur, çözümleyici sunucusunu istediğiniz zaman değiştirebilirsiniz.", resolverserverDescription: "Cloudflare varsayılan sunucudur, çözümleyici sunucusunu istediğiniz zaman değiştirebilirsiniz.",
rrtypeDescription: "İzlemek istediğiniz servisin RR-Tipini seçin", rrtypeDescription: "İzlemek istediğiniz servisin RR-Tipini seçin",
pauseMonitorMsg: "Durdurmak istediğinden emin misin?", pauseMonitorMsg: "Durdurmak istediğinden emin misin?",
clearEventsMsg: "Bu servisin bütün kayıtlarını silmek istediğinden emin misin?", clearEventsMsg: "Bu servisin bütün kayıtlarını silmek istediğinden emin misin?",
@ -124,7 +124,7 @@ export default {
tokenValidSettingsMsg: "Token geçerli! Şimdi 2FA ayarlarını kaydedebilirsiniz. ", tokenValidSettingsMsg: "Token geçerli! Şimdi 2FA ayarlarını kaydedebilirsiniz. ",
confirmEnableTwoFAMsg: "2FA'ı etkinleştirmek istediğinizden emin misiniz?", confirmEnableTwoFAMsg: "2FA'ı etkinleştirmek istediğinizden emin misiniz?",
confirmDisableTwoFAMsg: "2FA'ı devre dışı bırakmak istediğinize emin misiniz?", confirmDisableTwoFAMsg: "2FA'ı devre dışı bırakmak istediğinize emin misiniz?",
"Heartbeat Retry Interval": "Sağlık Dırımları Tekrar Deneme Sıklığı", "Heartbeat Retry Interval": "Sağlık Durumları Tekrar Deneme Sıklığı",
"Import Backup": "Yedeği içe aktar", "Import Backup": "Yedeği içe aktar",
"Export Backup": "Yedeği dışa aktar", "Export Backup": "Yedeği dışa aktar",
Export: "Dışa aktar", Export: "Dışa aktar",
@ -149,52 +149,4 @@ export default {
"Two Factor Authentication": "İki Faktörlü Kimlik Doğrulama (2FA)", "Two Factor Authentication": "İki Faktörlü Kimlik Doğrulama (2FA)",
Active: "Aktif", Active: "Aktif",
Inactive: "İnaktif", Inactive: "İnaktif",
Token: "Token",
"Show URI": "Show URI",
Tags: "Tags",
"Add New below or Select...": "Add New below or Select...",
"Tag with this name already exist.": "Tag with this name already exist.",
"Tag with this value already exist.": "Tag with this value already exist.",
color: "color",
"value (optional)": "value (optional)",
Gray: "Gray",
Red: "Red",
Orange: "Orange",
Green: "Green",
Blue: "Blue",
Indigo: "Indigo",
Purple: "Purple",
Pink: "Pink",
"Search...": "Search...",
"Avg. Ping": "Avg. Ping",
"Avg. Response": "Avg. Response",
"Entry Page": "Entry Page",
statusPageNothing: "Nothing here, please add a group or a monitor.",
"No Services": "No Services",
"All Systems Operational": "All Systems Operational",
"Partially Degraded Service": "Partially Degraded Service",
"Degraded Service": "Degraded Service",
"Add Group": "Add Group",
"Add a monitor": "Add a monitor",
"Edit Status Page": "Edit Status Page",
"Go to Dashboard": "Go to Dashboard",
"Status Page": "Status Page",
telegram: "Telegram",
webhook: "Webhook",
smtp: "Email (SMTP)",
discord: "Discord",
teams: "Microsoft Teams",
signal: "Signal",
gotify: "Gotify",
slack: "Slack",
"rocket.chat": "Rocket.chat",
pushover: "Pushover",
pushy: "Pushy",
octopush: "Octopush",
promosms: "PromoSMS",
lunasea: "LunaSea",
apprise: "Apprise (Support 50+ Notification services)",
pushbullet: "Pushbullet",
line: "Line Messenger",
mattermost: "Mattermost",
}; };

@ -0,0 +1,392 @@
export default {
languageName: "Український",
checkEverySecond: "Перевірка кожні {0} секунд",
retriesDescription: "Максимальна кількість спроб перед позначенням сервісу як недоступного та надсиланням повідомлення",
ignoreTLSError: "Ігнорувати помилку TLS/SSL для сайтів HTTPS",
upsideDownModeDescription: "Реверс статусу сервісу. Якщо сервіс доступний, він позначається як НЕДОСТУПНИЙ.",
maxRedirectDescription: "Максимальна кількість перенаправлень. Поставте 0, щоб вимкнути перенаправлення.",
acceptedStatusCodesDescription: "Виберіть коди статусів для визначення доступності сервісу.",
passwordNotMatchMsg: "Повторення паролю не збігається.",
notificationDescription: "Прив'яжіть повідомлення до моніторів.",
keywordDescription: "Пошук слова в чистому HTML або JSON-відповіді (чутливо до регістру)",
pauseDashboardHome: "Пауза",
deleteMonitorMsg: "Ви дійсно хочете видалити цей монітор?",
deleteNotificationMsg: "Ви дійсно хочете видалити це повідомлення для всіх моніторів?",
resolverserverDescription: "Cloudflare є сервером за замовчуванням. Ви завжди можете змінити цей сервер.",
rrtypeDescription: "Виберіть тип ресурсного запису, який ви хочете відстежувати",
pauseMonitorMsg: "Ви дійсно хочете поставити на паузу?",
Settings: "Налаштування",
Dashboard: "Панель управління",
"New Update": "Оновлення",
Language: "Мова",
Appearance: "Зовнішній вигляд",
Theme: "Тема",
General: "Загальне",
Version: "Версія",
"Check Update On GitHub": "Перевірити оновлення на GitHub",
List: "Список",
Add: "Додати",
"Add New Monitor": "Новий монітор",
"Quick Stats": "Статистика",
Up: "Доступний",
Down: "Недоступний",
Pending: "Очікування",
Unknown: "Невідомо",
Pause: "Пауза",
Name: "Ім'я",
Status: "Статус",
DateTime: "Дата і час",
Message: "Повідомлення",
"No important events": "Важливих подій немає",
Resume: "Відновити",
Edit: "Змінити",
Delete: "Видалити",
Current: "Поточний",
Uptime: "Аптайм",
"Cert Exp.": "Сертифікат спливає",
days: "днів",
day: "день",
"-day": " днів",
hour: "година",
"-hour": " години",
Response: "Відповідь",
Ping: "Пінг",
"Monitor Type": "Тип монітора",
Keyword: "Ключове слово",
"Friendly Name": "Ім'я",
URL: "URL",
Hostname: "Ім'я хоста",
Port: "Порт",
"Heartbeat Interval": "Частота опитування",
Retries: "Спроб",
Advanced: "Додатково",
"Upside Down Mode": "Реверс статусу",
"Max. Redirects": "Макс. кількість перенаправлень",
"Accepted Status Codes": "Припустимі коди статусу",
Save: "Зберегти",
Notifications: "Повідомлення",
"Not available, please setup.": "Доступних сповіщень немає, необхідно створити.",
"Setup Notification": "Створити сповіщення",
Light: "Світла",
Dark: "Темна",
Auto: "Авто",
"Theme - Heartbeat Bar": "Тема - Смуга частоти опитування",
Normal: "Звичайний",
Bottom: "Знизу",
None: "Відсутня",
Timezone: "Часовий пояс",
"Search Engine Visibility": "Індексація пошуковими системами:",
"Allow indexing": "Дозволити індексування",
"Discourage search engines from indexing site": "Заборонити індексування",
"Change Password": "Змінити пароль",
"Current Password": "Поточний пароль",
"New Password": "Новий пароль",
"Repeat New Password": "Повтор нового пароля",
"Update Password": "Оновити пароль",
"Disable Auth": "Вимкнути авторизацію",
"Enable Auth": "Увімкнути авторизацію",
Logout: "Вийти",
Leave: "Відміна",
"I understand, please disable": "Я розумію, все одно відключити",
Confirm: "Підтвердити",
Yes: "Так",
No: "Ні",
Username: "Логін",
Password: "Пароль",
"Remember me": "Запам'ятати мене",
Login: "Вхід до системи",
"No Monitors, please": "Моніторів немає, будь ласка",
"No Monitors": "Монітори відсутні",
"add one": "створіть новий",
"Notification Type": "Тип повідомлення",
Email: "Пошта",
Test: "Перевірка",
"Certificate Info": "Інформація про сертифікат",
"Resolver Server": "DNS сервер",
"Resource Record Type": "Тип ресурсного запису",
"Last Result": "Останній результат",
"Create your admin account": "Створіть обліковий запис адміністратора",
"Repeat Password": "Повторіть пароль",
respTime: "Час відповіді (мс)",
notAvailableShort: "Н/д",
Create: "Створити",
clearEventsMsg: "Ви дійсно хочете видалити всю статистику подій цього монітора?",
clearHeartbeatsMsg: "Ви дійсно хочете видалити всю статистику опитувань цього монітора?",
confirmClearStatisticsMsg: "Ви дійсно хочете видалити ВСЮ статистику?",
"Clear Data": "Видалити статистику",
Events: "Події",
Heartbeats: "Опитування",
"Auto Get": "Авто-отримання",
enableDefaultNotificationDescription: "Для кожного нового монітора це повідомлення буде включено за замовчуванням. Ви все ще можете відключити повідомлення в кожному моніторі окремо.",
"Default enabled": "Використовувати за промовчанням",
"Also apply to existing monitors": "Застосувати до існуючих моніторів",
Export: "Експорт",
Import: "Імпорт",
backupDescription: "Ви можете зберегти резервну копію всіх моніторів та повідомлень у вигляді JSON-файлу",
backupDescription2: "P.S.: Історія та події збережені не будуть",
backupDescription3: "Важливі дані, такі як токени повідомлень, додаються під час експорту, тому зберігайте файли в безпечному місці",
alertNoFile: "Виберіть файл для імпорту.",
alertWrongFileType: "Виберіть JSON-файл.",
twoFAVerifyLabel: "Будь ласка, введіть свій токен, щоб перевірити роботу 2FA",
tokenValidSettingsMsg: "Токен дійсний! Тепер ви можете зберегти налаштування 2FA.",
confirmEnableTwoFAMsg: "Ви дійсно хочете увімкнути 2FA?",
confirmDisableTwoFAMsg: "Ви дійсно хочете вимкнути 2FA?",
"Apply on all existing monitors": "Застосувати до всіх існуючих моніторів",
"Verify Token": "Перевірити токен",
"Setup 2FA": "Налаштування 2FA",
"Enable 2FA": "Увімкнути 2FA",
"Disable 2FA": "Вимкнути 2FA",
"2FA Settings": "Налаштування 2FA",
"Two Factor Authentication": "Двофакторна аутентифікація",
Active: "Активно",
Inactive: "Неактивно",
Token: "Токен",
"Show URI": "Показати URI",
"Clear all statistics": "Очистити статистику",
retryCheckEverySecond: "Повтор кожні {0} секунд",
importHandleDescription: "Виберіть \"Пропустити існуючі\", якщо ви хочете пропустити кожен монітор або повідомлення з таким же ім'ям. \"Перезаписати\" видалить кожен існуючий монітор або повідомлення та додасть заново. Варіант \"Не перевіряти\" примусово відновлює всі монітори і повідомлення, навіть якщо вони вже існують.",
confirmImportMsg: "Ви дійсно хочете відновити резервну копію? Переконайтеся, що ви вибрали відповідний варіант імпорту.",
"Heartbeat Retry Interval": "Інтервал повтору опитування",
"Import Backup": "Імпорт",
"Export Backup": "Експорт",
"Skip existing": "Пропустити існуючі",
Overwrite: "Перезаписати",
Options: "Опції",
"Keep both": "Не перевіряти",
Tags: "Теги",
"Add New below or Select...": "Додати новий або вибрати...",
"Tag with this name already exist.": "Такий тег вже існує.",
"Tag with this value already exist.": "Тег із таким значенням вже існує.",
color: "колір",
"value (optional)": "значення (опціонально)",
Gray: "Сірий",
Red: "Червоний",
Orange: "Помаранчевий",
Green: "Зелений",
Blue: "Синій",
Indigo: "Індиго",
Purple: "Пурпурний",
Pink: "Рожевий",
"Search...": "Пошук...",
"Avg. Ping": "Середнє значення пінгу",
"Avg. Response": "Середній час відповіді",
"Entry Page": "Головна сторінка",
statusPageNothing: "Тут порожньо. Додайте групу або монітор.",
"No Services": "Немає сервісів",
"All Systems Operational": "Всі системи працюють у штатному режимі",
"Partially Degraded Service": "Сервіси працюють частково",
"Degraded Service": "Всі сервіси не працюють",
"Add Group": "Додати групу",
"Add a monitor": "Додати монітор",
"Edit Status Page": "Редагувати",
"Go to Dashboard": "Панель управління",
"Status Page": "Сторінка статусу",
"Status Pages": "Сторінки статусу",
Discard: "Скасування",
"Create Incident": "Створити інцидент",
"Switch to Dark Theme": "Темна тема",
"Switch to Light Theme": "Світла тема",
telegram: "Telegram",
webhook: "Вебхук",
smtp: "Email (SMTP)",
discord: "Discord",
teams: "Microsoft Teams",
signal: "Signal",
gotify: "Gotify",
slack: "Slack",
"rocket.chat": "Rocket.chat",
pushover: "Pushover",
pushy: "Pushy",
octopush: "Octopush",
promosms: "PromoSMS",
lunasea: "LunaSea",
apprise: "Apprise (Підтримка 50+ сервісів повідомлень)",
pushbullet: "Pushbullet",
line: "Line Messenger",
mattermost: "Mattermost",
"Primary Base URL": "Основна URL",
"Push URL": "URL пуша",
needPushEvery: "До цієї URL необхідно звертатися кожні {0} секунд",
pushOptionalParams: "Опціональні параметри: {0}",
defaultNotificationName: "Моє повідомлення {notification} ({number})",
here: "тут",
Required: "Потрібно",
"Bot Token": "Токен бота",
wayToGetTelegramToken: "Ви можете взяти токен тут - {0}.",
"Chat ID": "ID чату",
supportTelegramChatID: "Підтримуються ID чатів, груп та каналів",
wayToGetTelegramChatID: "Ви можете взяти ID вашого чату, відправивши повідомлення боту і перейшовши по цьому URL для перегляду chat_id:",
"YOUR BOT TOKEN HERE": "ВАШ ТОКЕН БОТА ТУТ",
chatIDNotFound: "ID чату не знайдено; будь ласка, відправте спочатку повідомлення боту",
"Post URL": "Post URL",
"Content Type": "Тип контенту",
webhookJsonDesc: "{0} підходить для будь-яких сучасних HTTP-серверів, наприклад Express.js",
webhookFormDataDesc: "{multipart} підходить для PHP. JSON-вивід необхідно буде обробити за допомогою {decodeFunction}",
secureOptionNone: "Ні / STARTTLS (25, 587)",
secureOptionTLS: "TLS (465)",
"Ignore TLS Error": "Ігнорувати помилки TLS",
"From Email": "Від кого",
emailCustomSubject: "Своя тема",
"To Email": "Кому",
smtpCC: "Копія",
smtpBCC: "Прихована копія",
"Discord Webhook URL": "Discord Вебхук URL",
wayToGetDiscordURL: "Ви можете створити його в Параметрах сервера -> Інтеграції -> Створити вебхук",
"Bot Display Name": "Ім'я бота, що відображається",
"Prefix Custom Message": "Свій префікс повідомлення",
"Hello @everyone is...": "Привіт {'@'}everyone це...",
"Webhook URL": "URL вебхука",
wayToGetTeamsURL: "Як створити URL вебхука ви можете дізнатися тут - {0}.",
Номер: "Номер",
Recipients: "Одержувачі",
needSignalAPI: "Вам необхідний клієнт Signal із підтримкою REST API.",
wayToCheckSignalURL: "Пройдіть по цьому URL, щоб дізнатися як налаштувати такий клієнт:",
signalImportant: "ВАЖЛИВО: Не можна змішувати в Одержувачах групи та номери!",
"Application Token": "Токен програми",
"Server URL": "URL сервера",
Priority: "Пріоритет",
"Icon Emoji": "Іконка Emoji",
"Channel Name": "Ім'я каналу",
"Uptime Kuma URL": "Uptime Kuma URL",
aboutWebhooks: "Більше інформації про вебхуки: {0}",
aboutChannelName: "Введіть ім'я каналу в поле {0} Ім'я каналу, якщо ви хочете обійти канал вебхука. Наприклад: #other-channel",
aboutKumaURL: "Якщо поле Uptime Kuma URL в налаштуваннях залишиться порожнім, за замовчуванням буде використовуватися посилання на проект на GitHub.",
emojiCheatSheet: "Шпаргалка по Emoji: {0}",
"User Key": "Ключ користувача",
Device: "Пристрій",
"Message Title": "Заголовок повідомлення",
"Notification Sound": "Звук повідомлення",
"More info on:": "Більше інформації: {0}",
pushoverDesc1: "Екстренний пріоритет (2) має таймуут повтору за замовчуванням 30 секунд і закінчується через 1 годину.",
pushoverDesc2: "Якщо ви бажаєте надсилати повідомлення різним пристроям, необхідно заповнити поле Пристрій.",
"SMS Type": "Тип SMS",
octopushTypePremium: "Преміум (Швидкий - рекомендується для алертів)",
octopushTypeLowCost: "Дешевий (Повільний - іноді блокується операторами)",
checkPrice: "Тарифи {0}:",
octopushLegacyHint: "Ви використовуєте стару версію Octopush (2011-2020) або нову?",
"Check octopush prices": "Тарифи Octopush {0}.",
octopushPhoneNumber: "Номер телефону (між. формат, наприклад: +380123456789)",
octopushSMSSender: "Ім'я відправника SMS: 3-11 символів алвафіту, цифр та пробілів (a-zA-Z0-9)",
"LunaSea Device ID": "ID пристрою LunaSea",
"Apprise URL": "Apprise URL",
"Example:": "Приклад: {0}",
"Read more:": "Докладніше: {0}",
"Status:": "Статус: {0}",
"Read more": "Докладніше",
appriseInstalled: "Apprise встановлено.",
appriseNotInstalled: "Apprise не встановлено. {0}",
"Access Token": "Токен доступу",
"Channel access token": "Токен доступу каналу",
"Line Developers Console": "Консоль розробників Line",
lineDevConsoleTo: "Консоль розробників Line - {0}",
"Basic Settings": "Базові налаштування",
"User ID": "ID користувача",
"Messaging API": "API повідомлень",
wayToGetLineChannelToken: "Спочатку зайдіть в {0}, створіть провайдера та канал (API повідомлень), потім ви зможете отримати токен доступу каналу та ID користувача з вищезгаданих пунктів меню.",
"Icon URL": "URL іконки",
aboutIconURL: "Ви можете надати посилання на іконку в полі \"URL іконки\", щоб перевизначити картинку профілю за замовчуванням. Не використовується, якщо задана іконка Emoji.",
aboutMattermostChannelName: "Ви можете перевизначити канал за замовчуванням, в який пише вебхук, ввівши ім'я каналу в полі \"Ім'я каналу\". Це необхідно включити в налаштуваннях вебхука Mattermost. Наприклад: #other-channel",
matrix: "Matrix",
promosmsTypeEco: "SMS ECO - дешево та повільно, часто перевантажений. Тільки для одержувачів з Польщі.",
promosmsTypeFlash: "SMS FLASH - повідомлення автоматично з'являться на пристрої одержувача. Тільки для одержувачів з Польщі.",
promosmsTypeFull: "SMS FULL - преміум-рівень SMS, можна використовувати своє ім'я відправника (попередньо зареєструвавши його). Надійно для алертів.",
promosmsTypeSpeed: "SMS SPEED - найвищий пріоритет у системі. Дуже швидко і надійно, але дуже дорого (вдвічі дорожче, ніж SMS FULL).",
promosmsPhoneNumber: "Номер телефону (для одержувачів з Польщі можна пропустити код регіону)",
promosmsSMSSender: "Ім'я відправника SMS: Зареєстроване або одне з імен за замовчуванням: InfoSMS, SMS Info, MaxSMS, INFO, SMS",
"Feishu WebHookURL": "Feishu WebHookURL",
matrixHomeserverURL: "URL сервера (разом з http(s):// і опціонально порт)",
"Internal Room Id": "Внутрішній ID кімнати",
matrixDesc1: "Внутрішній ID кімнати можна знайти в Подробицях у параметрах каналу вашого Matrix клієнта. Він повинен виглядати приблизно як !QMdRCpUIfLwsfjxye6:home.server.",
matrixDesc2: "Рекомендується створити нового користувача і не використовувати токен доступу особистого користувача Matrix, тому що це спричиняє повний доступ до облікового запису та до кімнат, в яких ви є. Замість цього створіть нового користувача і запросіть його тільки в ту кімнату, в якій ви хочете отримувати повідомлення.Токен доступу можна отримати, виконавши команду {0}",
Method: "Метод",
Body: "Тіло",
Headers: "Заголовки",
PushUrl: "URL пуша",
HeadersInvalidFormat: "Заголовки запиту некоректні JSON: ",
BodyInvalidFormat: "Тіло запиту некоректне JSON: ",
"Monitor History": "Статистика",
clearDataOlderThan: "Зберігати статистику за {0} днів.",
PasswordsDoNotMatch: "Паролі не співпадають.",
records: "записів",
"One record": "Один запис",
steamApiKeyDescription: "Для моніторингу ігрового сервера Steam вам потрібен Web-API ключ Steam. Зареєструвати його можна тут: ",
"Certificate Chain": "Ланцюжок сертифікатів",
Valid: "Дійсний",
"Hide Tags": "Приховати теги",
Title: "Назва інциденту:",
Content: "Зміст інциденту:",
Post: "Опублікувати",
Cancel: "Скасувати",
Created: "Створено",
Unpin: "Відкріпити",
"Show Tags": "Показати теги",
recent: "Зараз",
"3h": "3 години",
"6h": "6 годин",
"24h": "24 години",
"1w": "1 тиждень",
"No monitors available.": "Немає доступних моніторів",
"Add one": "Додати новий",
Backup: "Резервна копія",
Security: "Безпека",
"Shrink Database": "Стиснути базу даних",
"Current User": "Поточний користувач",
About: "Про програму",
Description: "Опис",
"Powered by": "Працює на основі скрипту від",
shrinkDatabaseDescription: "Включає VACUUM для бази даних SQLite. Якщо база даних була створена на версії 1.10.0 і більше, AUTO_VACUUM вже включений і ця дія не потрібна.",
Style: "Стиль",
info: "ІНФО",
warning: "УВАГА",
danger: "ПОМИЛКА",
primary: "ОСНОВНИЙ",
light: "СВІТЛИЙ",
dark: "ТЕМНИЙ",
"New Status Page": "Нова сторінка статусу",
"Show update if available": "Показувати доступні оновлення",
"Also check beta release": "Перевіряти оновлення для бета версій",
"Add New Status Page": "Додати сторінку статусу",
Next: "Далі",
"Acz characters: a-z 0-9 -": "Дозволені символи: a-z 0-9 -",
"Start or end with a-z 0-9 only": "Початок та закінчення імені лише на символи: a-z 0-9",
"No consecutive dashes --": "Заборонено використовувати тире --",
"HTTP Options": "HTTP Опції",
"Basic Auth": "HTTP Авторизація",
PushByTechulus: "Push by Techulus",
clicksendsms: "ClickSend SMS",
GoogleChat: "Google Chat (тільки Google Workspace)",
apiCredentials: "API реквізити",
Done: "Готово",
Info: "Інфо",
"Steam API Key": "Steam API-Ключ",
"Pick a RR-Type...": "Виберіть RR-тип...",
"Pick Accepted Status Codes...": "Виберіть прийняті коди стану...",
Default: "За замовчуванням",
"Please input title and content": "Будь ласка, введіть назву та зміст",
"Last Updated": "Останнє Оновлення",
"Untitled Group": "Група без назви",
Services: "Сервіси",
serwersms: "SerwerSMS.pl",
serwersmsAPIUser: "API Користувач (включаючи префікс webapi_)",
serwersmsAPIPassword: "API Пароль",
serwersmsPhoneNumber: "Номер телефону",
serwersmsSenderName: "SMS ім'я відправника (реєстрований через портал користувача)",
stackfield: "Stackfield",
smtpDkimSettings: "DKIM Налаштування",
smtpDkimDesc: "Повернутися до Nodemailer DKIM {0} для використання.",
documentation: "документація",
smtpDkimDomain: "Ім'я домена",
smtpDkimKeySelector: "Ключ",
smtpDkimPrivateKey: "Приватний ключ",
smtpDkimHashAlgo: "Алгоритм хеша (опціонально)",
smtpDkimheaderFieldNames: "Заголовок ключів для підпису (опціонально)",
smtpDkimskipFields: "Заколовок ключів не для підпису (опціонально)",
gorush: "Gorush",
alerta: "Alerta",
alertaApiEndpoint: "Кінцева точка API",
alertaEnvironment: "Середовище",
alertaApiKey: "Ключ API",
alertaAlertState: "Стан алерту",
alertaRecoverState: "Стан відновлення",
deleteStatusPageMsg: "Дійсно хочете видалити цю сторінку статусів?",
};

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save