Compare commits

..

1 Commits

Author SHA1 Message Date
Louis Lam
2d26037bce Revert "Extend Prometheus Labels to include tags (requires restart for NEW la…"
This reverts commit 643d28cebc.
2024-10-09 07:13:43 +08:00
87 changed files with 372 additions and 2687 deletions

View File

@@ -22,7 +22,7 @@ jobs:
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest, ARM64]
node: [ 18, 20, 22 ]
node: [ 18, 20 ]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
@@ -78,7 +78,7 @@ jobs:
e2e-test:
needs: [ ]
runs-on: e2e
runs-on: ARM64
steps:
- run: git config --global core.autocrlf false # Mainly for Windows
- uses: actions/checkout@v4

View File

@@ -25,13 +25,3 @@ jobs:
with:
comment: "true" # enable comment mode
exclude_file: ".github/config/exclude.txt" # gitignore style file for exclusions
check-lang-json:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: 20
- run: node ./extra/check-lang-json.js

View File

@@ -1,6 +1,6 @@
# Project Info
First of all, I want to thank everyone who has submitted issues or shared pull requests for Uptime Kuma.
First of all, I want to thank everyone who have wrote issues or shared pull requests for Uptime Kuma.
I never thought the GitHub community would be so nice!
Because of this, I also never thought that other people would actually read and edit my code.
Parts of the code are not very well-structured or commented, sorry about that.
@@ -9,7 +9,7 @@ The project was created with `vite.js` and is written in `vue3`.
Our backend lives in the `server`-directory and mostly communicates via websockets.
Both frontend and backend share the same `package.json`.
For production, the frontend is built into the `dist`-directory and the server (`express.js`) exposes the `dist` directory as the root of the endpoint.
For production, the frontend is build into `dist`-directory and the server (`express.js`) exposes the `dist` directory as the root of the endpoint.
For development, we run vite in development mode on another port.
## Directories
@@ -28,7 +28,7 @@ For development, we run vite in development mode on another port.
## Can I create a pull request for Uptime Kuma?
Yes or no, it depends on what you will try to do.
Both yours and our maintainers' time is precious, and we don't want to waste either.
Both your and our maintainers time is precious, and we don't want to waste both time.
If you have any questions about any process/.. is not clear, you are likely not alone => please ask them ^^
@@ -49,11 +49,11 @@ Different guidelines exist for different types of pull requests (PRs):
<p>
If you come across a bug and think you can solve, we appreciate your work.
Please make sure that you follow these rules:
Please make sure that you follow by these rules:
- keep the PR as small as possible, fix only one thing at a time => keeping it reviewable
- test that your code does what you claim it does.
- test that your code does what you came it does.
<sub>Because maintainer time is precious, junior maintainers may merge uncontroversial PRs in this area.</sub>
<sub>Because maintainer time is precious junior maintainers may merge uncontroversial PRs in this area.</sub>
</p>
</details>
- <details><summary><b>translations / internationalisation (i18n)</b></summary>
@@ -68,7 +68,7 @@ Different guidelines exist for different types of pull requests (PRs):
- language keys need to be **added to `en.json`** to be visible in weblate. If this has not happened, a PR is appreciated.
- **Adding a new language** requires a new file see [these instructions](https://github.com/louislam/uptime-kuma/blob/master/src/lang/README.md)
<sub>Because maintainer time is precious, junior maintainers may merge uncontroversial PRs in this area.</sub>
<sub>Because maintainer time is precious junior maintainers may merge uncontroversial PRs in this area.</sub>
</p>
</details>
- <details><summary><b>new notification providers</b></summary>
@@ -102,7 +102,7 @@ Different guidelines exist for different types of pull requests (PRs):
Therefore, making sure that they work is also really important.
Because testing notification providers is quite time intensive, we mostly offload this onto the person contributing a notification provider.
To make sure you have tested the notification provider, please include screenshots of the following events in the pull-request description:
To make shure you have tested the notification provider, please include screenshots of the following events in the pull-request description:
- `UP`/`DOWN`
- Certificate Expiry via https://expired.badssl.com/
- Testing (the test button on the notification provider setup page)
@@ -117,7 +117,7 @@ Different guidelines exist for different types of pull requests (PRs):
| Testing | paste-image-here | paste-image-here |
```
<sub>Because maintainer time is precious, junior maintainers may merge uncontroversial PRs in this area.</sub>
<sub>Because maintainer time is precious junior maintainers may merge uncontroversial PRs in this area.</sub>
</p>
</details>
- <details><summary><b>new monitoring types</b></summary>
@@ -138,14 +138,14 @@ Different guidelines exist for different types of pull requests (PRs):
-
<sub>Because maintainer time is precious, junior maintainers may merge uncontroversial PRs in this area.</sub>
<sub>Because maintainer time is precious junior maintainers may merge uncontroversial PRs in this area.</sub>
</p>
</details>
- <details><summary><b>new features/ major changes / breaking bugfixes</b></summary>
<p>
be sure to **create an empty draft pull request or open an issue, so we can have a discussion first**.
This is especially important for a large pull request or when you don't know if it will be merged or not.
This is especially important for a large pull request or you don't know if it will be merged or not.
<sub>Because of the large impact of this work, only senior maintainers may merge PRs in this area.</sub>
</p>
@@ -201,7 +201,7 @@ The rationale behind this is that we can align the direction and scope of the fe
## Project Styles
I personally do not like something that requires a lot of configuration before you can finally start the app.
I personally do not like something that requires so many configurations before you can finally start the app.
The goal is to make the Uptime Kuma installation as easy as installing a mobile app.
- Easy to install for non-Docker users
@@ -260,7 +260,7 @@ Port `3000` and port `3001` will be used.
npm run dev
```
But sometimes you may want to restart the server without restarting the frontend. In that case, you can run these commands in two terminals:
But sometimes, you would like to restart the server, but not the frontend, you can run these commands in two terminals:
```bash
npm run start-frontend-dev
@@ -409,7 +409,7 @@ https://github.com/louislam/uptime-kuma/issues?q=sort%3Aupdated-desc
### What is a maintainer and what are their roles?
This project has multiple maintainers who specialise in different areas.
This project has multiple maintainers which specialise in different areas.
Currently, there are 3 maintainers:
| Person | Role | Main Area |

View File

@@ -1,17 +0,0 @@
exports.up = function (knex) {
return knex.schema.alterTable("monitor", function (table) {
table.text("rabbitmq_nodes");
table.string("rabbitmq_username");
table.string("rabbitmq_password");
});
};
exports.down = function (knex) {
return knex.schema.alterTable("monitor", function (table) {
table.dropColumn("rabbitmq_nodes");
table.dropColumn("rabbitmq_username");
table.dropColumn("rabbitmq_password");
});
};

View File

@@ -1,5 +1,5 @@
# If the image changed, the second stage image should be changed too
FROM node:22-bookworm-slim AS base2-slim
FROM node:20-bookworm-slim AS base2-slim
ARG TARGETPLATFORM
# Specify --no-install-recommends to skip unused dependencies, make the base much smaller!

View File

@@ -1,27 +0,0 @@
// For #5231
const fs = require("fs");
let path = "./src/lang";
// list directories in the lang directory
let jsonFileList = fs.readdirSync(path);
for (let jsonFile of jsonFileList) {
if (!jsonFile.endsWith(".json")) {
continue;
}
let jsonPath = path + "/" + jsonFile;
let originalContent = fs.readFileSync(jsonPath, "utf8");
let langData = JSON.parse(originalContent);
let formattedContent = JSON.stringify(langData, null, 4) + "\n";
if (originalContent !== formattedContent) {
console.error(`File ${jsonFile} is not formatted correctly.`);
process.exit(1);
}
}
console.log("All lang json files are formatted correctly.");

View File

@@ -1,32 +0,0 @@
// Go to http://ftp.debian.org/debian/pool/main/a/apprise/ using fetch api, where it is a apache directory listing page
// Use cheerio to parse the html and get the latest version of Apprise
// call curl to download the latest version of Apprise
// Target file: the latest version of Apprise, which the format is apprise_{VERSION}_all.deb
import * as cheerio from "cheerio";
const response = await fetch("http://ftp.debian.org/debian/pool/main/a/apprise/");
if (!response.ok) {
throw new Error("Failed to fetch page of Apprise Debian repository.");
}
const html = await response.text();
const $ = cheerio.load(html);
// Get all the links in the page
const linkElements = $("a");
// Filter the links which match apprise_{VERSION}_all.deb
const links = [];
for (let i = 0; i < linkElements.length; i++) {
const link = linkElements[i];
if (link.attribs.href.match(/apprise_(.*?)_all.deb/) && !link.attribs.href.includes("~")) {
links.push(link.attribs.href);
}
}
console.log(links);
// TODO: semver compare and download?

View File

@@ -1,25 +0,0 @@
// For #5231
const fs = require("fs");
let path = "../src/lang";
// list directories in the lang directory
let jsonFileList = fs.readdirSync(path);
for (let jsonFile of jsonFileList) {
if (!jsonFile.endsWith(".json")) {
continue;
}
let jsonPath = path + "/" + jsonFile;
let langData = JSON.parse(fs.readFileSync(jsonPath, "utf8"));
for (let key in langData) {
if (langData[key] === "") {
delete langData[key];
}
}
fs.writeFileSync(jsonPath, JSON.stringify(langData, null, 4) + "\n");
}

View File

@@ -1,12 +0,0 @@
// If Node.js >= 22, run `npm run test-backend-node22`, otherwise run `npm run test-backend-node20`
import * as childProcess from "child_process";
const version = parseInt(process.version.slice(1));
console.log(`Node.js version: ${version}`);
if (version >= 22) {
childProcess.execSync("npm run test-backend-node22", { stdio: "inherit" });
} else {
childProcess.execSync("npm run test-backend-node20", { stdio: "inherit" });
}

997
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -27,9 +27,7 @@
"build": "vite build --config ./config/vite.config.js",
"test": "npm run test-backend && npm run test-e2e",
"test-with-build": "npm run build && npm test",
"test-backend": "node extra/test-backend.mjs",
"test-backend-node20": "cross-env TEST_BACKEND=1 node --test test/backend-test",
"test-backend-node22": "cross-env TEST_BACKEND=1 node --test test/backend-test/test-*.js && node --test test/backend-test/**/test-*.js",
"test-backend": "cross-env TEST_BACKEND=1 node --test test/backend-test",
"test-e2e": "playwright test --config ./config/playwright.config.js",
"test-e2e-ui": "playwright test --config ./config/playwright.config.js --ui --ui-port=51063",
"playwright-codegen": "playwright codegen localhost:3000 --save-storage=./private/e2e-auth.json",
@@ -156,8 +154,6 @@
"@fortawesome/vue-fontawesome": "~3.0.0-5",
"@playwright/test": "~1.39.0",
"@popperjs/core": "~2.10.2",
"@testcontainers/hivemq": "^10.13.1",
"@testcontainers/rabbitmq": "^10.13.2",
"@types/bootstrap": "~5.1.9",
"@types/node": "^20.8.6",
"@typescript-eslint/eslint-plugin": "^6.7.5",
@@ -193,7 +189,6 @@
"stylelint-config-standard": "~25.0.0",
"terser": "~5.15.0",
"test": "~3.3.0",
"testcontainers": "^10.13.1",
"typescript": "~4.4.4",
"v-pagination-3": "~0.1.7",
"vite": "~5.2.8",

View File

@@ -1,7 +1,6 @@
const basicAuth = require("express-basic-auth");
const passwordHash = require("./password-hash");
const { R } = require("redbean-node");
const { setting } = require("./util-server");
const { log } = require("../src/util");
const { loginRateLimiter, apiRateLimiter } = require("./rate-limiter");
const { Settings } = require("./settings");
@@ -139,7 +138,7 @@ exports.basicAuth = async function (req, res, next) {
challenge: true,
});
const disabledAuth = await setting("disableAuth");
const disabledAuth = await Settings.get("disableAuth");
if (!disabledAuth) {
middleware(req, res, next);

View File

@@ -1,7 +1,7 @@
const { setSetting, setting } = require("./util-server");
const axios = require("axios");
const compareVersions = require("compare-versions");
const { log } = require("../src/util");
const { Settings } = require("./settings");
exports.version = require("../package.json").version;
exports.latestVersion = null;
@@ -14,7 +14,7 @@ let interval;
exports.startInterval = () => {
let check = async () => {
if (await setting("checkUpdate") === false) {
if (await Settings.get("checkUpdate") === false) {
return;
}
@@ -28,7 +28,7 @@ exports.startInterval = () => {
res.data.slow = "1000.0.0";
}
let checkBeta = await setting("checkBeta");
let checkBeta = await Settings.get("checkBeta");
if (checkBeta && res.data.beta) {
if (compareVersions.compare(res.data.beta, res.data.slow, ">")) {
@@ -57,7 +57,7 @@ exports.startInterval = () => {
* @returns {Promise<void>}
*/
exports.enableCheckUpdate = async (value) => {
await setSetting("checkUpdate", value);
await Settings.set("checkUpdate", value);
clearInterval(interval);

View File

@@ -6,8 +6,8 @@ const { R } = require("redbean-node");
const { UptimeKumaServer } = require("./uptime-kuma-server");
const server = UptimeKumaServer.getInstance();
const io = server.io;
const { setting } = require("./util-server");
const checkVersion = require("./check-version");
const { Settings } = require("./settings");
const Database = require("./database");
/**
@@ -158,8 +158,8 @@ async function sendInfo(socket, hideVersion = false) {
version,
latestVersion,
isContainer,
primaryBaseURL: await Settings.get("primaryBaseURL"),
dbType,
primaryBaseURL: await setting("primaryBaseURL"),
serverTimezone: await server.getTimezone(),
serverTimezoneOffset: server.getTimezoneOffset(),
});

View File

@@ -1,11 +1,11 @@
const fs = require("fs");
const { R } = require("redbean-node");
const { setSetting, setting } = require("./util-server");
const { log, sleep } = require("../src/util");
const knex = require("knex");
const path = require("path");
const { EmbeddedMariaDB } = require("./embedded-mariadb");
const mysql = require("mysql2/promise");
const { Settings } = require("./settings");
/**
* Database & App Data Folder
@@ -420,7 +420,7 @@ class Database {
* @deprecated
*/
static async patchSqlite() {
let version = parseInt(await setting("database_version"));
let version = parseInt(await Settings.get("database_version"));
if (! version) {
version = 0;
@@ -445,7 +445,7 @@ class Database {
log.info("db", `Patching ${sqlFile}`);
await Database.importSQLFile(sqlFile);
log.info("db", `Patched ${sqlFile}`);
await setSetting("database_version", i);
await Settings.set("database_version", i);
}
} catch (ex) {
await Database.close();
@@ -471,7 +471,7 @@ class Database {
*/
static async patchSqlite2() {
log.debug("db", "Database Patch 2.0 Process");
let databasePatchedFiles = await setting("databasePatchedFiles");
let databasePatchedFiles = await Settings.get("databasePatchedFiles");
if (! databasePatchedFiles) {
databasePatchedFiles = {};
@@ -499,7 +499,7 @@ class Database {
process.exit(1);
}
await setSetting("databasePatchedFiles", databasePatchedFiles);
await Settings.set("databasePatchedFiles", databasePatchedFiles);
}
/**
@@ -512,27 +512,27 @@ class Database {
// 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");
let title = await Settings.get("title");
if (title) {
console.log("Migrating Status Page");
log.info("database", "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");
log.info("database", "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.description = await Settings.get("description");
statusPage.icon = await Settings.get("icon");
statusPage.theme = await Settings.get("statusPageTheme");
statusPage.published = !!await Settings.get("statusPagePublished");
statusPage.search_engine_index = !!await Settings.get("searchEngineIndex");
statusPage.show_tags = !!await Settings.get("statusPageTags");
statusPage.password = null;
if (!statusPage.title) {
@@ -560,13 +560,13 @@ class Database {
await R.exec("DELETE FROM setting WHERE type = 'statusPage'");
// Migrate Entry Page if it is status page
let entryPage = await setting("entryPage");
let entryPage = await Settings.get("entryPage");
if (entryPage === "statusPage") {
await setSetting("entryPage", "statusPage-default", "general");
await Settings.set("entryPage", "statusPage-default", "general");
}
console.log("Migrating Status Page - Done");
log.info("database", "Migrating Status Page - Done");
}
}

View File

@@ -1,7 +1,7 @@
const { R } = require("redbean-node");
const { log } = require("../../src/util");
const { setSetting, setting } = require("../util-server");
const Database = require("../database");
const { Settings } = require("../settings");
const DEFAULT_KEEP_PERIOD = 180;
@@ -11,11 +11,11 @@ const DEFAULT_KEEP_PERIOD = 180;
*/
const clearOldData = async () => {
let period = await setting("keepDataPeriodDays");
let period = await Settings.get("keepDataPeriodDays");
// Set Default Period
if (period == null) {
await setSetting("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general");
await Settings.set("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general");
period = DEFAULT_KEEP_PERIOD;
}
@@ -25,7 +25,7 @@ const clearOldData = async () => {
parsedPeriod = parseInt(period);
} catch (_) {
log.warn("clearOldData", "Failed to parse setting, resetting to default..");
await setSetting("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general");
await Settings.set("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general");
parsedPeriod = DEFAULT_KEEP_PERIOD;
}

View File

@@ -4,7 +4,7 @@ const { Prometheus } = require("../prometheus");
const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND,
SQL_DATETIME_FORMAT, evaluateJsonQuery
} = require("../../src/util");
const { tcping, ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, setSetting, httpNtlm, radius, grpcQuery,
const { tcping, ping, checkCertificate, checkStatusCode, getTotalClientInRoom, mssqlQuery, postgresQuery, mysqlQuery, httpNtlm, radius, grpcQuery,
redisPingAsync, kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal
} = require("../util-server");
const { R } = require("redbean-node");
@@ -24,6 +24,7 @@ const { CookieJar } = require("tough-cookie");
const { HttpsCookieAgent } = require("http-cookie-agent/http");
const https = require("https");
const http = require("http");
const { Settings } = require("../settings");
const rootCertificates = rootCertificatesFingerprints();
@@ -153,7 +154,6 @@ class Monitor extends BeanModel {
snmpOid: this.snmpOid,
jsonPathOperator: this.jsonPathOperator,
snmpVersion: this.snmpVersion,
rabbitmqNodes: JSON.parse(this.rabbitmqNodes),
conditions: JSON.parse(this.conditions),
};
@@ -184,8 +184,6 @@ class Monitor extends BeanModel {
tlsCert: this.tlsCert,
tlsKey: this.tlsKey,
kafkaProducerSaslOptions: JSON.parse(this.kafkaProducerSaslOptions),
rabbitmqUsername: this.rabbitmqUsername,
rabbitmqPassword: this.rabbitmqPassword,
};
}
@@ -654,7 +652,7 @@ class Monitor extends BeanModel {
} else if (this.type === "steam") {
const steamApiUrl = "https://api.steampowered.com/IGameServersService/GetServerList/v1/";
const steamAPIKey = await setting("steamAPIKey");
const steamAPIKey = await Settings.get("steamAPIKey");
const filter = `addr\\${this.hostname}:${this.port}`;
if (!steamAPIKey) {
@@ -1376,11 +1374,12 @@ class Monitor extends BeanModel {
return;
}
let notifyDays = await setting("tlsExpiryNotifyDays");
let notifyDays = await Settings.get("tlsExpiryNotifyDays");
if (notifyDays == null || !Array.isArray(notifyDays)) {
// Reset Default
await setSetting("tlsExpiryNotifyDays", [ 7, 14, 21 ], "general");
await Settings.set("tlsExpiryNotifyDays", [ 7, 14, 21 ], "general");
notifyDays = [ 7, 14, 21 ];
await Settings.set("tlsExpiryNotifyDays", notifyDays, "general");
}
if (Array.isArray(notifyDays)) {
@@ -1511,8 +1510,10 @@ class Monitor extends BeanModel {
return await R.getAll(`
SELECT monitor_notification.monitor_id, monitor_notification.notification_id
FROM monitor_notification
WHERE monitor_notification.monitor_id IN (${monitorIDs.map((_) => "?").join(",")})
`, monitorIDs);
WHERE monitor_notification.monitor_id IN (?)
`, [
monitorIDs,
]);
}
/**
@@ -1522,11 +1523,13 @@ class Monitor extends BeanModel {
*/
static async getMonitorTag(monitorIDs) {
return await R.getAll(`
SELECT monitor_tag.monitor_id, monitor_tag.tag_id, tag.name, tag.color
SELECT monitor_tag.monitor_id, tag.name, tag.color
FROM monitor_tag
JOIN tag ON monitor_tag.tag_id = tag.id
WHERE monitor_tag.monitor_id IN (${monitorIDs.map((_) => "?").join(",")})
`, monitorIDs);
WHERE monitor_tag.monitor_id IN (?)
`, [
monitorIDs,
]);
}
/**
@@ -1566,7 +1569,6 @@ class Monitor extends BeanModel {
tagsMap.set(row.monitor_id, []);
}
tagsMap.get(row.monitor_id).push({
tag_id: row.tag_id,
name: row.name,
color: row.color
});

View File

@@ -1,5 +1,6 @@
let url = require("url");
let MemoryCache = require("./memory-cache");
const { log } = require("../../../src/util");
let t = {
ms: 1,
@@ -90,24 +91,6 @@ function ApiCache() {
instances.push(this);
this.id = instances.length;
/**
* Logs a message to the console if the `DEBUG` environment variable is set.
* @param {string} a The first argument to log.
* @param {string} b The second argument to log.
* @param {string} c The third argument to log.
* @param {string} d The fourth argument to log, and so on... (optional)
*
* Generated by Trelent
*/
function debug(a, b, c, d) {
let arr = ["\x1b[36m[apicache]\x1b[0m", a, b, c, d].filter(function (arg) {
return arg !== undefined;
});
let debugEnv = process.env.DEBUG && process.env.DEBUG.split(",").indexOf("apicache") !== -1;
return (globalOptions.debug || debugEnv) && console.log.apply(null, arr);
}
/**
* Returns true if the given request and response should be logged.
* @param {Object} request The HTTP request object.
@@ -146,7 +129,7 @@ function ApiCache() {
let groupName = req.apicacheGroup;
if (groupName) {
debug("group detected \"" + groupName + "\"");
log.debug("apicache", `group detected "${groupName}"`);
let group = (index.groups[groupName] = index.groups[groupName] || []);
group.unshift(key);
}
@@ -212,7 +195,7 @@ function ApiCache() {
redis.hset(key, "duration", duration);
redis.expire(key, duration / 1000, expireCallback || function () {});
} catch (err) {
debug("[apicache] error in redis.hset()");
log.debug("apicache", `error in redis.hset(): ${err}`);
}
} else {
memCache.add(key, value, duration, expireCallback);
@@ -320,10 +303,10 @@ function ApiCache() {
// display log entry
let elapsed = new Date() - req.apicacheTimer;
debug("adding cache entry for \"" + key + "\" @ " + strDuration, logDuration(elapsed));
debug("_apicache.headers: ", res._apicache.headers);
debug("res.getHeaders(): ", getSafeHeaders(res));
debug("cacheObject: ", cacheObject);
log.debug("apicache", `adding cache entry for "${key}" @ ${strDuration} ${logDuration(elapsed)}`);
log.debug("apicache", `_apicache.headers: ${JSON.stringify(res._apicache.headers)}`);
log.debug("apicache", `res.getHeaders(): ${JSON.stringify(getSafeHeaders(res))}`);
log.debug("apicache", `cacheObject: ${JSON.stringify(cacheObject)}`);
}
}
@@ -402,10 +385,10 @@ function ApiCache() {
let redis = globalOptions.redisClient;
if (group) {
debug("clearing group \"" + target + "\"");
log.debug("apicache", `clearing group "${target}"`);
group.forEach(function (key) {
debug("clearing cached entry for \"" + key + "\"");
log.debug("apicache", `clearing cached entry for "${key}"`);
clearTimeout(timers[key]);
delete timers[key];
if (!globalOptions.redisClient) {
@@ -414,7 +397,7 @@ function ApiCache() {
try {
redis.del(key);
} catch (err) {
console.log("[apicache] error in redis.del(\"" + key + "\")");
log.info("apicache", "error in redis.del(\"" + key + "\")");
}
}
index.all = index.all.filter(doesntMatch(key));
@@ -422,7 +405,7 @@ function ApiCache() {
delete index.groups[target];
} else if (target) {
debug("clearing " + (isAutomatic ? "expired" : "cached") + " entry for \"" + target + "\"");
log.debug("apicache", `clearing ${isAutomatic ? "expired" : "cached"} entry for "${target}"`);
clearTimeout(timers[target]);
delete timers[target];
// clear actual cached entry
@@ -432,7 +415,7 @@ function ApiCache() {
try {
redis.del(target);
} catch (err) {
console.log("[apicache] error in redis.del(\"" + target + "\")");
log.error("apicache", "error in redis.del(\"" + target + "\")");
}
}
@@ -449,7 +432,7 @@ function ApiCache() {
}
});
} else {
debug("clearing entire index");
log.debug("apicache", "clearing entire index");
if (!redis) {
memCache.clear();
@@ -461,7 +444,7 @@ function ApiCache() {
try {
redis.del(key);
} catch (err) {
console.log("[apicache] error in redis.del(\"" + key + "\")");
log.error("apicache", `error in redis.del("${key}"): ${err}`);
}
});
}
@@ -528,7 +511,7 @@ function ApiCache() {
/**
* Get index of a group
* @param {string} group
* @param {string} group
* @returns {number}
*/
this.getIndex = function (group) {
@@ -543,9 +526,9 @@ function ApiCache() {
* Express middleware
* @param {(string|number)} strDuration Duration to cache responses
* for.
* @param {function(Object, Object):boolean} middlewareToggle
* @param {function(Object, Object):boolean} middlewareToggle
* @param {Object} localOptions Options for APICache
* @returns
* @returns
*/
this.middleware = function cache(strDuration, middlewareToggle, localOptions) {
let duration = instance.getDuration(strDuration);
@@ -752,7 +735,7 @@ function ApiCache() {
*/
let cache = function (req, res, next) {
function bypass() {
debug("bypass detected, skipping cache.");
log.debug("apicache", "bypass detected, skipping cache.");
return next();
}
@@ -805,7 +788,7 @@ function ApiCache() {
// send if cache hit from memory-cache
if (cached) {
let elapsed = new Date() - req.apicacheTimer;
debug("sending cached (memory-cache) version of", key, logDuration(elapsed));
log.debug("apicache", `sending cached (memory-cache) version of ${key} ${logDuration(elapsed)}`);
perf.hit(key);
return sendCachedResponse(req, res, cached, middlewareToggle, next, duration);
@@ -817,7 +800,7 @@ function ApiCache() {
redis.hgetall(key, function (err, obj) {
if (!err && obj && obj.response) {
let elapsed = new Date() - req.apicacheTimer;
debug("sending cached (redis) version of", key, logDuration(elapsed));
log.debug("apicache", "sending cached (redis) version of "+ key+" "+ logDuration(elapsed));
perf.hit(key);
return sendCachedResponse(
@@ -859,7 +842,7 @@ function ApiCache() {
/**
* Process options
* @param {Object} options
* @param {Object} options
* @returns {Object}
*/
this.options = function (options) {
@@ -873,7 +856,7 @@ function ApiCache() {
}
if (globalOptions.trackPerformance) {
debug("WARNING: using trackPerformance flag can cause high memory usage!");
log.debug("apicache", "WARNING: using trackPerformance flag can cause high memory usage!");
}
return this;

View File

@@ -1,67 +0,0 @@
const { MonitorType } = require("./monitor-type");
const { log, UP, DOWN } = require("../../src/util");
const { axiosAbortSignal } = require("../util-server");
const axios = require("axios");
class RabbitMqMonitorType extends MonitorType {
name = "rabbitmq";
/**
* @inheritdoc
*/
async check(monitor, heartbeat, server) {
let baseUrls = [];
try {
baseUrls = JSON.parse(monitor.rabbitmqNodes);
} catch (error) {
throw new Error("Invalid RabbitMQ Nodes");
}
heartbeat.status = DOWN;
for (let baseUrl of baseUrls) {
try {
// Without a trailing slash, path in baseUrl will be removed. https://example.com/api -> https://example.com
if ( !baseUrl.endsWith("/") ) {
baseUrl += "/";
}
const options = {
// Do not start with slash, it will strip the trailing slash from baseUrl
url: new URL("api/health/checks/alarms/", baseUrl).href,
method: "get",
timeout: monitor.timeout * 1000,
headers: {
"Accept": "application/json",
"Authorization": "Basic " + Buffer.from(`${monitor.rabbitmqUsername || ""}:${monitor.rabbitmqPassword || ""}`).toString("base64"),
},
signal: axiosAbortSignal((monitor.timeout + 10) * 1000),
// Capture reason for 503 status
validateStatus: (status) => status === 200 || status === 503,
};
log.debug("monitor", `[${monitor.name}] Axios Request: ${JSON.stringify(options)}`);
const res = await axios.request(options);
log.debug("monitor", `[${monitor.name}] Axios Response: status=${res.status} body=${JSON.stringify(res.data)}`);
if (res.status === 200) {
heartbeat.status = UP;
heartbeat.msg = "OK";
break;
} else if (res.status === 503) {
heartbeat.msg = res.data.reason;
} else {
heartbeat.msg = `${res.status} - ${res.statusText}`;
}
} catch (error) {
if (axios.isCancel(error)) {
heartbeat.msg = "Request timed out";
log.debug("monitor", `[${monitor.name}] Request timed out`);
} else {
log.debug("monitor", `[${monitor.name}] Axios Error: ${JSON.stringify(error.message)}`);
heartbeat.msg = error.message;
}
}
}
}
}
module.exports = {
RabbitMqMonitorType,
};

View File

@@ -63,7 +63,7 @@ if (process.platform === "win32") {
* @returns {Promise<boolean>} The executable is allowed?
*/
async function isAllowedChromeExecutable(executablePath) {
console.log(config.args);
log.info("Chromium", config.args);
if (config.args["allow-all-chrome-exec"] || process.env.UPTIME_KUMA_ALLOW_ALL_CHROME_EXEC === "1") {
return true;
}
@@ -102,7 +102,8 @@ async function getBrowser() {
*/
async function getRemoteBrowser(remoteBrowserID, userId) {
let remoteBrowser = await RemoteBrowser.get(remoteBrowserID, userId);
log.debug("MONITOR", `Using remote browser: ${remoteBrowser.name} (${remoteBrowser.id})`);
log.debug("Chromium", `Using remote browser: ${remoteBrowser.name} (${remoteBrowser.id})`);
browser = chromium.connect(remoteBrowser.url);
browser = await chromium.connect(remoteBrowser.url);
return browser;
}

View File

@@ -1,35 +0,0 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
class Elks extends NotificationProvider {
name = "Elks";
/**
* @inheritdoc
*/
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
const okMsg = "Sent Successfully.";
const url = "https://api.46elks.com/a1/sms";
try {
let data = new URLSearchParams();
data.append("from", notification.elksFromNumber);
data.append("to", notification.elksToNumber );
data.append("message", msg);
const config = {
headers: {
"Authorization": "Basic " + Buffer.from(`${notification.elksUsername}:${notification.elksAuthToken}`).toString("base64")
}
};
await axios.post(url, data, config);
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
}
module.exports = Elks;

View File

@@ -1,7 +1,7 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { setting } = require("../util-server");
const { getMonitorRelativeURL, UP, DOWN } = require("../../src/util");
const { Settings } = require("../settings");
class AlertNow extends NotificationProvider {
name = "AlertNow";
@@ -29,7 +29,7 @@ class AlertNow extends NotificationProvider {
textMsg += ` - ${msg}`;
const baseURL = await setting("primaryBaseURL");
const baseURL = await Settings.get("primaryBaseURL");
if (baseURL && monitorJSON) {
textMsg += ` >> ${baseURL + getMonitorRelativeURL(monitorJSON.id)}`;
}

View File

@@ -87,6 +87,7 @@ class DingDing extends NotificationProvider {
* @returns {string} Status
*/
statusToString(status) {
// TODO: Move to notification-provider.js to avoid repetition in classes
switch (status) {
case DOWN:
return "DOWN";

View File

@@ -48,7 +48,7 @@ class Discord extends NotificationProvider {
},
{
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
value: this.extractAddress(monitorJSON),
value: this.extractAdress(monitorJSON),
},
{
name: `Time (${heartbeatJSON["timezone"]})`,
@@ -85,7 +85,7 @@ class Discord extends NotificationProvider {
},
{
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
value: this.extractAddress(monitorJSON),
value: this.extractAdress(monitorJSON),
},
{
name: `Time (${heartbeatJSON["timezone"]})`,

View File

@@ -1,7 +1,7 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
const { setting } = require("../util-server");
const { Settings } = require("../settings");
const successMessage = "Sent Successfully.";
class FlashDuty extends NotificationProvider {
@@ -84,7 +84,7 @@ class FlashDuty extends NotificationProvider {
}
};
const baseURL = await setting("primaryBaseURL");
const baseURL = await Settings.get("primaryBaseURL");
if (baseURL && monitorInfo) {
options.client = "Uptime Kuma";
options.client_url = baseURL + getMonitorRelativeURL(monitorInfo.id);

View File

@@ -1,7 +1,7 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { setting } = require("../util-server");
const { getMonitorRelativeURL, UP } = require("../../src/util");
const { Settings } = require("../settings");
class GoogleChat extends NotificationProvider {
name = "GoogleChat";
@@ -45,7 +45,7 @@ class GoogleChat extends NotificationProvider {
}
// add button for monitor link if available
const baseURL = await setting("primaryBaseURL");
const baseURL = await Settings.get("primaryBaseURL");
if (baseURL) {
const urlPath = monitorJSON ? getMonitorRelativeURL(monitorJSON.id) : "/";
sectionWidgets.push({

View File

@@ -24,7 +24,7 @@ class NotificationProvider {
* @param {?object} monitorJSON Monitor details (For Up/Down only)
* @returns {string} The extracted address based on the monitor type.
*/
extractAddress(monitorJSON) {
extractAdress(monitorJSON) {
if (!monitorJSON) {
return "";
}

View File

@@ -1,7 +1,7 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
const { setting } = require("../util-server");
const { Settings } = require("../settings");
let successMessage = "Sent Successfully.";
class PagerDuty extends NotificationProvider {
@@ -95,7 +95,7 @@ class PagerDuty extends NotificationProvider {
}
};
const baseURL = await setting("primaryBaseURL");
const baseURL = await Settings.get("primaryBaseURL");
if (baseURL && monitorInfo) {
options.client = "Uptime Kuma";
options.client_url = baseURL + getMonitorRelativeURL(monitorInfo.id);

View File

@@ -1,7 +1,7 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
const { setting } = require("../util-server");
const { Settings } = require("../settings");
let successMessage = "Sent Successfully.";
class PagerTree extends NotificationProvider {
@@ -74,7 +74,7 @@ class PagerTree extends NotificationProvider {
}
};
const baseURL = await setting("primaryBaseURL");
const baseURL = await Settings.get("primaryBaseURL");
if (baseURL && monitorJSON) {
options.client = "Uptime Kuma";
options.client_url = baseURL + getMonitorRelativeURL(monitorJSON.id);

View File

@@ -1,8 +1,8 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const Slack = require("./slack");
const { setting } = require("../util-server");
const { getMonitorRelativeURL, DOWN } = require("../../src/util");
const { Settings } = require("../settings");
class RocketChat extends NotificationProvider {
name = "rocket.chat";
@@ -49,7 +49,7 @@ class RocketChat extends NotificationProvider {
await Slack.deprecateURL(notification.rocketbutton);
}
const baseURL = await setting("primaryBaseURL");
const baseURL = await Settings.get("primaryBaseURL");
if (baseURL) {
data.attachments[0].title_link = baseURL + getMonitorRelativeURL(monitorJSON.id);

View File

@@ -1,65 +0,0 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
class SendGrid extends NotificationProvider {
name = "SendGrid";
/**
* @inheritdoc
*/
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
const okMsg = "Sent Successfully.";
try {
let config = {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${notification.sendgridApiKey}`,
},
};
let personalizations = {
to: [{ email: notification.sendgridToEmail }],
};
// Add CC recipients if provided
if (notification.sendgridCcEmail) {
personalizations.cc = notification.sendgridCcEmail
.split(",")
.map((email) => ({ email: email.trim() }));
}
// Add BCC recipients if provided
if (notification.sendgridBccEmail) {
personalizations.bcc = notification.sendgridBccEmail
.split(",")
.map((email) => ({ email: email.trim() }));
}
let data = {
personalizations: [ personalizations ],
from: { email: notification.sendgridFromEmail.trim() },
subject:
notification.sendgridSubject ||
"Notification from Your Uptime Kuma",
content: [
{
type: "text/plain",
value: msg,
},
],
};
await axios.post(
"https://api.sendgrid.com/v3/mail/send",
data,
config
);
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
}
module.exports = SendGrid;

View File

@@ -12,9 +12,8 @@ class ServerChan extends NotificationProvider {
const okMsg = "Sent Successfully.";
// serverchan3 requires sending via ft07.com
const matchResult = String(notification.serverChanSendKey).match(/^sctp(\d+)t/i);
const url = matchResult && matchResult[1]
? `https://${matchResult[1]}.push.ft07.com/send/${notification.serverChanSendKey}.send`
const url = String(notification.serverChanSendKey).startsWith("sctp")
? `https://${notification.serverChanSendKey}.push.ft07.com/send`
: `https://sctapi.ftqq.com/${notification.serverChanSendKey}.send`;
try {

View File

@@ -32,7 +32,7 @@ class SevenIO extends NotificationProvider {
return okMsg;
}
let address = this.extractAddress(monitorJSON);
let address = this.extractAdress(monitorJSON);
if (address !== "") {
address = `(${address}) `;
}

View File

@@ -18,7 +18,7 @@ class SIGNL4 extends NotificationProvider {
msg,
// Source system
"X-S4-SourceSystem": "UptimeKuma",
monitorUrl: this.extractAddress(monitorJSON),
monitorUrl: this.extractAdress(monitorJSON),
};
const config = {

View File

@@ -1,7 +1,8 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { setSettings, setting } = require("../util-server");
const { getMonitorRelativeURL, UP } = require("../../src/util");
const { Settings } = require("../settings");
const { log } = require("../../src/util");
class Slack extends NotificationProvider {
name = "slack";
@@ -14,15 +15,13 @@ class Slack extends NotificationProvider {
* @returns {Promise<void>}
*/
static async deprecateURL(url) {
let currentPrimaryBaseURL = await setting("primaryBaseURL");
let currentPrimaryBaseURL = await Settings.get("primaryBaseURL");
if (!currentPrimaryBaseURL) {
console.log("Move the url to be the primary base URL");
await setSettings("general", {
primaryBaseURL: url,
});
log.error("notification", "Move the url to be the primary base URL");
await Settings.set("primaryBaseURL", url, "general");
} else {
console.log("Already there, no need to move the primary base URL");
log.debug("notification", "Already there, no need to move the primary base URL");
}
}
@@ -32,7 +31,7 @@ class Slack extends NotificationProvider {
* @param {object} monitorJSON The monitor config
* @returns {Array} The relevant action objects
*/
buildActions(baseURL, monitorJSON) {
static buildActions(baseURL, monitorJSON) {
const actions = [];
if (baseURL) {
@@ -48,7 +47,7 @@ class Slack extends NotificationProvider {
}
const address = this.extractAddress(monitorJSON);
const address = this.extractAdress(monitorJSON);
if (address) {
actions.push({
"type": "button",
@@ -73,7 +72,7 @@ class Slack extends NotificationProvider {
* @param {string} msg The message body
* @returns {Array<object>} The rich content blocks for the Slack message
*/
buildBlocks(baseURL, monitorJSON, heartbeatJSON, title, msg) {
static buildBlocks(baseURL, monitorJSON, heartbeatJSON, title, msg) {
//create an array to dynamically add blocks
const blocks = [];
@@ -136,7 +135,7 @@ class Slack extends NotificationProvider {
return okMsg;
}
const baseURL = await setting("primaryBaseURL");
const baseURL = await Settings.get("primaryBaseURL");
const title = "Uptime Kuma Alert";
let data = {
@@ -150,7 +149,7 @@ class Slack extends NotificationProvider {
data.attachments.push(
{
"color": (heartbeatJSON["status"] === UP) ? "#2eb886" : "#e01e5a",
"blocks": this.buildBlocks(baseURL, monitorJSON, heartbeatJSON, title, msg),
"blocks": Slack.buildBlocks(baseURL, monitorJSON, heartbeatJSON, title, msg),
}
);
} else {

View File

@@ -93,7 +93,7 @@ class SMTP extends NotificationProvider {
if (monitorJSON !== null) {
monitorName = monitorJSON["name"];
monitorHostnameOrURL = this.extractAddress(monitorJSON);
monitorHostnameOrURL = this.extractAdress(monitorJSON);
}
let serviceStatus = "⚠️ Test";

View File

@@ -1,7 +1,7 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
const { setting } = require("../util-server");
const { Settings } = require("../settings");
let successMessage = "Sent Successfully.";
class Splunk extends NotificationProvider {
@@ -95,7 +95,7 @@ class Splunk extends NotificationProvider {
}
};
const baseURL = await setting("primaryBaseURL");
const baseURL = await Settings.get("primaryBaseURL");
if (baseURL && monitorInfo) {
options.client = "Uptime Kuma";
options.client_url = baseURL + getMonitorRelativeURL(monitorInfo.id);

View File

@@ -34,7 +34,7 @@ class Squadcast extends NotificationProvider {
data.status = "resolve";
}
data.tags["AlertAddress"] = this.extractAddress(monitorJSON);
data.tags["AlertAddress"] = this.extractAdress(monitorJSON);
monitorJSON["tags"].forEach(tag => {
data.tags[tag["name"]] = {

View File

@@ -1,7 +1,7 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { setting } = require("../util-server");
const { getMonitorRelativeURL } = require("../../src/util");
const { Settings } = require("../settings");
class Stackfield extends NotificationProvider {
name = "stackfield";
@@ -23,7 +23,7 @@ class Stackfield extends NotificationProvider {
textMsg += `\n${msg}`;
const baseURL = await setting("primaryBaseURL");
const baseURL = await Settings.get("primaryBaseURL");
if (baseURL) {
textMsg += `\n${baseURL + getMonitorRelativeURL(monitorJSON.id)}`;
}

View File

@@ -225,7 +225,7 @@ class Teams extends NotificationProvider {
const payload = this._notificationPayloadFactory({
heartbeatJSON: heartbeatJSON,
monitorName: monitorJSON.name,
monitorUrl: this.extractAddress(monitorJSON),
monitorUrl: this.extractAdress(monitorJSON),
dashboardUrl: dashboardUrl,
});

View File

@@ -10,22 +10,11 @@ class TechulusPush extends NotificationProvider {
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
const okMsg = "Sent Successfully.";
let data = {
"title": notification?.pushTitle?.length ? notification.pushTitle : "Uptime-Kuma",
"body": msg,
"timeSensitive": notification.pushTimeSensitive ?? true,
};
if (notification.pushChannel) {
data.channel = notification.pushChannel;
}
if (notification.pushSound) {
data.sound = notification.pushSound;
}
try {
await axios.post(`https://push.techulus.com/api/v1/notify/${notification.pushAPIKey}`, data);
await axios.post(`https://push.techulus.com/api/v1/notify/${notification.pushAPIKey}`, {
"title": "Uptime-Kuma",
"body": msg,
});
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error);

View File

@@ -85,7 +85,7 @@ class ZohoCliq extends NotificationProvider {
const payload = this._notificationPayloadFactory({
monitorMessage: heartbeatJSON.msg,
monitorName: monitorJSON.name,
monitorUrl: this.extractAddress(monitorJSON),
monitorUrl: this.extractAdress(monitorJSON),
status: heartbeatJSON.status
});

View File

@@ -11,7 +11,6 @@ const CallMeBot = require("./notification-providers/call-me-bot");
const SMSC = require("./notification-providers/smsc");
const DingDing = require("./notification-providers/dingding");
const Discord = require("./notification-providers/discord");
const Elks = require("./notification-providers/46elks");
const Feishu = require("./notification-providers/feishu");
const FreeMobile = require("./notification-providers/freemobile");
const GoogleChat = require("./notification-providers/google-chat");
@@ -68,7 +67,6 @@ const GtxMessaging = require("./notification-providers/gtx-messaging");
const Cellsynt = require("./notification-providers/cellsynt");
const Onesender = require("./notification-providers/onesender");
const Wpush = require("./notification-providers/wpush");
const SendGrid = require("./notification-providers/send-grid");
class Notification {
@@ -97,7 +95,6 @@ class Notification {
new SMSC(),
new DingDing(),
new Discord(),
new Elks(),
new Feishu(),
new FreeMobile(),
new GoogleChat(),
@@ -154,7 +151,6 @@ class Notification {
new GtxMessaging(),
new Cellsynt(),
new Wpush(),
new SendGrid()
];
for (let item of list) {
if (! item.name) {

View File

@@ -2,7 +2,7 @@ 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 { log } = require("../src/util");
const { UptimeKumaServer } = require("./uptime-kuma-server");
const { CookieJar } = require("tough-cookie");
const { createCookieAgent } = require("http-cookie-agent/http");
@@ -110,9 +110,9 @@ class Proxy {
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)}`);
log.debug("update-proxy", `Proxy Options: ${JSON.stringify(proxyOptions)}`);
log.debug("update-proxy", `HTTP Agent Options: ${JSON.stringify(httpAgentOptions)}`);
log.debug("update-proxy", `HTTPS Agent Options: ${JSON.stringify(httpsAgentOptions)}`);
switch (proxy.protocol) {
case "http":

View File

@@ -1,6 +1,5 @@
let express = require("express");
const {
setting,
allowDevAllOrigin,
allowAllOrigin,
percentageToColor,
@@ -18,6 +17,7 @@ const { makeBadge } = require("badge-maker");
const { Prometheus } = require("../prometheus");
const Database = require("../database");
const { UptimeCalculator } = require("../uptime-calculator");
const { Settings } = require("../settings");
let router = express.Router();
@@ -30,7 +30,7 @@ router.get("/api/entry-page", async (request, response) => {
let result = { };
let hostname = request.hostname;
if ((await setting("trustProxy")) && request.headers["x-forwarded-host"]) {
if ((await Settings.get("trustProxy")) && request.headers["x-forwarded-host"]) {
hostname = request.headers["x-forwarded-host"];
}

View File

@@ -90,8 +90,7 @@ const Monitor = require("./model/monitor");
const User = require("./model/user");
log.debug("server", "Importing Settings");
const { getSettings, setSettings, setting, initJWTSecret, checkLogin, doubleCheckPassword, shake256, SHAKE256_LENGTH, allowDevAllOrigin,
} = require("./util-server");
const { initJWTSecret, checkLogin, doubleCheckPassword, shake256, SHAKE256_LENGTH, allowDevAllOrigin } = require("./util-server");
log.debug("server", "Importing Notification");
const { Notification } = require("./notification");
@@ -201,7 +200,7 @@ let needSetup = false;
// Entry Page
app.get("/", async (request, response) => {
let hostname = request.hostname;
if (await setting("trustProxy")) {
if (await Settings.get("trustProxy")) {
const proxy = request.headers["x-forwarded-host"];
if (proxy) {
hostname = proxy;
@@ -281,7 +280,7 @@ let needSetup = false;
// Robots.txt
app.get("/robots.txt", async (_request, response) => {
let txt = "User-agent: *\nDisallow:";
if (!await setting("searchEngineIndex")) {
if (!await Settings.get("searchEngineIndex")) {
txt += " /";
}
response.setHeader("Content-Type", "text/plain");
@@ -718,8 +717,6 @@ let needSetup = false;
monitor.conditions = JSON.stringify(monitor.conditions);
monitor.rabbitmqNodes = JSON.stringify(monitor.rabbitmqNodes);
bean.import(monitor);
bean.user_id = socket.userID;
@@ -870,9 +867,6 @@ let needSetup = false;
bean.snmpOid = monitor.snmpOid;
bean.jsonPathOperator = monitor.jsonPathOperator;
bean.timeout = monitor.timeout;
bean.rabbitmqNodes = JSON.stringify(monitor.rabbitmqNodes);
bean.rabbitmqUsername = monitor.rabbitmqUsername;
bean.rabbitmqPassword = monitor.rabbitmqPassword;
bean.conditions = JSON.stringify(monitor.conditions);
bean.validate();
@@ -1332,7 +1326,7 @@ let needSetup = false;
socket.on("getSettings", async (callback) => {
try {
checkLogin(socket);
const data = await getSettings("general");
const data = await Settings.getSettings("general");
if (!data.serverTimezone) {
data.serverTimezone = await server.getTimezone();
@@ -1360,7 +1354,7 @@ let needSetup = false;
// Disabled Auth + Want to Enable Auth => No Check
// Enabled Auth + Want to Disable Auth => Check!!
// Enabled Auth + Want to Enable Auth => No Check
const currentDisabledAuth = await setting("disableAuth");
const currentDisabledAuth = await Settings.get("disableAuth");
if (!currentDisabledAuth && data.disableAuth) {
await doubleCheckPassword(socket, currentPassword);
}
@@ -1374,7 +1368,7 @@ let needSetup = false;
const previousChromeExecutable = await Settings.get("chromeExecutable");
const previousNSCDStatus = await Settings.get("nscd");
await setSettings("general", data);
await Settings.setSettings("general", data);
server.entryPage = data.entryPage;
// Also need to apply timezone globally
@@ -1470,7 +1464,7 @@ let needSetup = false;
});
} catch (e) {
console.error(e);
log.error("server", e);
callback({
ok: false,
@@ -1583,7 +1577,7 @@ let needSetup = false;
// ***************************
log.debug("auth", "check auto login");
if (await setting("disableAuth")) {
if (await Settings.get("disableAuth")) {
log.info("auth", "Disabled Auth: auto login to admin");
await afterLogin(socket, await R.findOne("user"));
socket.emit("autoLogin");

View File

@@ -60,7 +60,7 @@ module.exports.apiKeySocketHandler = (socket) => {
ok: true,
});
} catch (e) {
console.error(e);
log.error("apikeys", e);
callback({
ok: false,
msg: e.message,

View File

@@ -1,7 +1,8 @@
const { checkLogin, setSetting, setting, doubleCheckPassword } = require("../util-server");
const { checkLogin, doubleCheckPassword } = require("../util-server");
const { CloudflaredTunnel } = require("node-cloudflared-tunnel");
const { UptimeKumaServer } = require("../uptime-kuma-server");
const { log } = require("../../src/util");
const { Settings } = require("../settings");
const io = UptimeKumaServer.getInstance().io;
const prefix = "cloudflared_";
@@ -40,7 +41,7 @@ module.exports.cloudflaredSocketHandler = (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"));
io.to(socket.userID).emit(prefix + "token", await Settings.get("cloudflaredTunnelToken"));
} catch (error) { }
});
@@ -55,7 +56,7 @@ module.exports.cloudflaredSocketHandler = (socket) => {
try {
checkLogin(socket);
if (token && typeof token === "string") {
await setSetting("cloudflaredTunnelToken", token);
await Settings.set("cloudflaredTunnelToken", token);
cloudflared.token = token;
} else {
cloudflared.token = null;
@@ -67,7 +68,7 @@ module.exports.cloudflaredSocketHandler = (socket) => {
socket.on(prefix + "stop", async (currentPassword, callback) => {
try {
checkLogin(socket);
const disabledAuth = await setting("disableAuth");
const disabledAuth = await Settings.get("disableAuth");
if (!disabledAuth) {
await doubleCheckPassword(socket, currentPassword);
}
@@ -83,7 +84,7 @@ module.exports.cloudflaredSocketHandler = (socket) => {
socket.on(prefix + "removeToken", async () => {
try {
checkLogin(socket);
await setSetting("cloudflaredTunnelToken", "");
await Settings.set("cloudflaredTunnelToken", "");
} catch (error) { }
});
@@ -96,15 +97,15 @@ module.exports.cloudflaredSocketHandler = (socket) => {
*/
module.exports.autoStart = async (token) => {
if (!token) {
token = await setting("cloudflaredTunnelToken");
token = await Settings.get("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");
await Settings.set("cloudflaredTunnelToken", token);
log.info("cloudflare", "Use cloudflared token from args or env var");
}
if (token) {
console.log("Start cloudflared");
log.info("cloudflare", "Start cloudflared");
cloudflared.token = token;
cloudflared.start();
}

View File

@@ -67,7 +67,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
});
} catch (e) {
console.error(e);
log.error("maintenance", e);
callback({
ok: false,
msg: e.message,
@@ -177,7 +177,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
ok: true,
});
} catch (e) {
console.error(e);
log.error("maintenance", e);
callback({
ok: false,
msg: e.message,
@@ -201,7 +201,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
});
} catch (e) {
console.error(e);
log.error("maintenance", e);
callback({
ok: false,
msg: e.message,
@@ -225,7 +225,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
});
} catch (e) {
console.error(e);
log.error("maintenance", e);
callback({
ok: false,
msg: e.message,

View File

@@ -1,5 +1,5 @@
const { R } = require("redbean-node");
const { checkLogin, setSetting } = require("../util-server");
const { checkLogin } = require("../util-server");
const dayjs = require("dayjs");
const { log } = require("../../src/util");
const ImageDataURI = require("../image-data-uri");
@@ -7,6 +7,7 @@ const Database = require("../database");
const apicache = require("../modules/apicache");
const StatusPage = require("../model/status_page");
const { UptimeKumaServer } = require("../uptime-kuma-server");
const { Settings } = require("../settings");
/**
* Socket handlers for status page
@@ -233,7 +234,7 @@ module.exports.statusPageSocketHandler = (socket) => {
// 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");
await Settings.set("entryPage", server.entryPage, "general");
}
apicache.clear();
@@ -291,7 +292,7 @@ module.exports.statusPageSocketHandler = (socket) => {
});
} catch (error) {
console.error(error);
log.error("socket", error);
callback({
ok: false,
msg: error.message,
@@ -313,7 +314,7 @@ module.exports.statusPageSocketHandler = (socket) => {
// Reset entry page if it is the default one.
if (server.entryPage === "statusPage-" + slug) {
server.entryPage = "dashboard";
await setSetting("entryPage", server.entryPage, "general");
await Settings.set("entryPage", server.entryPage, "general");
}
// No need to delete records from `status_page_cname`, because it has cascade foreign key.

View File

@@ -115,7 +115,6 @@ class UptimeKumaServer {
UptimeKumaServer.monitorTypeList["mqtt"] = new MqttMonitorType();
UptimeKumaServer.monitorTypeList["snmp"] = new SNMPMonitorType();
UptimeKumaServer.monitorTypeList["mongodb"] = new MongodbMonitorType();
UptimeKumaServer.monitorTypeList["rabbitmq"] = new RabbitMqMonitorType();
// Allow all CORS origins (polling) in development
let cors = undefined;
@@ -553,5 +552,4 @@ const { DnsMonitorType } = require("./monitor-types/dns");
const { MqttMonitorType } = require("./monitor-types/mqtt");
const { SNMPMonitorType } = require("./monitor-types/snmp");
const { MongodbMonitorType } = require("./monitor-types/mongodb");
const { RabbitMqMonitorType } = require("./monitor-types/rabbitmq");
const Monitor = require("./model/monitor");

View File

@@ -12,7 +12,6 @@ const { Client } = require("pg");
const postgresConParse = require("pg-connection-string").parse;
const mysql = require("mysql2");
const { NtlmClient } = require("./modules/axios-ntlm/lib/ntlmClient.js");
const { Settings } = require("./settings");
const grpc = require("@grpc/grpc-js");
const protojs = require("protobufjs");
const radiusClient = require("node-radius-client");
@@ -521,46 +520,6 @@ exports.redisPingAsync = function (dsn, rejectUnauthorized) {
});
};
/**
* Retrieve value of setting based on key
* @param {string} key Key of setting to retrieve
* @returns {Promise<any>} Value
* @deprecated Use await Settings.get(key)
*/
exports.setting = async function (key) {
return await Settings.get(key);
};
/**
* Sets the specified setting to specified value
* @param {string} key Key of setting to set
* @param {any} value Value to set to
* @param {?string} type Type of setting
* @returns {Promise<void>}
*/
exports.setSetting = async function (key, value, type = null) {
await Settings.set(key, value, type);
};
/**
* Get settings based on type
* @param {string} type The type of setting
* @returns {Promise<Bean>} Settings of requested type
*/
exports.getSettings = async function (type) {
return await Settings.getSettings(type);
};
/**
* Set settings based on type
* @param {string} type Type of settings to set
* @param {object} data Values of settings
* @returns {Promise<void>}
*/
exports.setSettings = async function (type, data) {
await Settings.setSettings(type, data);
};
// ssl-checker by @dyaa
//https://github.com/dyaa/ssl-checker/blob/master/src/index.ts

View File

@@ -4,17 +4,11 @@
<div
v-for="(beat, index) in shortBeatList"
:key="index"
class="beat-hover-area"
:class="{ 'empty': (beat === 0) }"
:style="beatHoverAreaStyle"
class="beat"
:class="{ 'empty': (beat === 0), 'down': (beat.status === 0), 'pending': (beat.status === 2), 'maintenance': (beat.status === 3) }"
:style="beatStyle"
:title="getBeatTitle(beat)"
>
<div
class="beat"
:class="{ 'empty': (beat === 0), 'down': (beat.status === 0), 'pending': (beat.status === 2), 'maintenance': (beat.status === 3) }"
:style="beatStyle"
/>
</div>
/>
</div>
<div
v-if="!$root.isMobile && size !== 'small' && beatList.length > 4 && $root.styleElapsedTime !== 'none'"
@@ -53,7 +47,7 @@ export default {
beatWidth: 10,
beatHeight: 30,
hoverScale: 1.5,
beatHoverAreaPadding: 4,
beatMargin: 4,
move: false,
maxBeat: -1,
};
@@ -129,7 +123,7 @@ export default {
barStyle() {
if (this.move && this.shortBeatList.length > this.maxBeat) {
let width = -(this.beatWidth + this.beatHoverAreaPadding * 2);
let width = -(this.beatWidth + this.beatMargin * 2);
return {
transition: "all ease-in-out 0.25s",
@@ -143,17 +137,12 @@ export default {
},
beatHoverAreaStyle() {
return {
padding: this.beatHoverAreaPadding + "px",
"--hover-scale": this.hoverScale,
};
},
beatStyle() {
return {
width: this.beatWidth + "px",
height: this.beatHeight + "px",
margin: this.beatMargin + "px",
"--hover-scale": this.hoverScale,
};
},
@@ -163,7 +152,7 @@ export default {
*/
timeStyle() {
return {
"margin-left": this.numPadding * (this.beatWidth + this.beatHoverAreaPadding * 2) + "px",
"margin-left": this.numPadding * (this.beatWidth + this.beatMargin * 2) + "px",
};
},
@@ -230,20 +219,20 @@ export default {
if (this.size !== "big") {
this.beatWidth = 5;
this.beatHeight = 16;
this.beatHoverAreaPadding = 2;
this.beatMargin = 2;
}
// Suddenly, have an idea how to handle it universally.
// If the pixel * ratio != Integer, then it causes render issue, round it to solve it!!
const actualWidth = this.beatWidth * window.devicePixelRatio;
const actualHoverAreaPadding = this.beatHoverAreaPadding * window.devicePixelRatio;
const actualMargin = this.beatMargin * window.devicePixelRatio;
if (!Number.isInteger(actualWidth)) {
this.beatWidth = Math.round(actualWidth) / window.devicePixelRatio;
}
if (!Number.isInteger(actualHoverAreaPadding)) {
this.beatHoverAreaPadding = Math.round(actualHoverAreaPadding) / window.devicePixelRatio;
if (!Number.isInteger(actualMargin)) {
this.beatMargin = Math.round(actualMargin) / window.devicePixelRatio;
}
window.addEventListener("resize", this.resize);
@@ -256,7 +245,7 @@ export default {
*/
resize() {
if (this.$refs.wrap) {
this.maxBeat = Math.floor(this.$refs.wrap.clientWidth / (this.beatWidth + this.beatHoverAreaPadding * 2));
this.maxBeat = Math.floor(this.$refs.wrap.clientWidth / (this.beatWidth + this.beatMargin * 2));
}
},
@@ -284,41 +273,32 @@ export default {
}
.hp-bar-big {
.beat-hover-area {
.beat {
display: inline-block;
background-color: $primary;
border-radius: $border-radius;
&.empty {
background-color: aliceblue;
}
&.down {
background-color: $danger;
}
&.pending {
background-color: $warning;
}
&.maintenance {
background-color: $maintenance;
}
&:not(.empty):hover {
transition: all ease-in-out 0.15s;
opacity: 0.8;
transform: scale(var(--hover-scale));
}
.beat {
background-color: $primary;
border-radius: $border-radius;
/*
pointer-events needs to be changed because
tooltip momentarily disappears when crossing between .beat-hover-area and .beat
*/
pointer-events: none;
&.empty {
background-color: aliceblue;
}
&.down {
background-color: $danger;
}
&.pending {
background-color: $warning;
}
&.maintenance {
background-color: $maintenance;
}
}
}
}

View File

@@ -118,7 +118,6 @@ export default {
"clicksendsms": "ClickSend SMS",
"CallMeBot": "CallMeBot (WhatsApp, Telegram Call, Facebook Messanger)",
"discord": "Discord",
"Elks": "46elks",
"GoogleChat": "Google Chat (Google Workspace)",
"gorush": "Gorush",
"gotify": "Gotify",
@@ -165,7 +164,6 @@ export default {
"whapi": "WhatsApp (Whapi)",
"gtxmessaging": "GtxMessaging",
"Cellsynt": "Cellsynt",
"SendGrid": "SendGrid"
};
// Put notifications here if it's not supported in most regions or its documentation is not in English

View File

@@ -33,7 +33,7 @@
<template #item="monitor">
<div class="item" data-testid="monitor">
<div class="row">
<div class="col-9 col-md-8 small-padding">
<div class="col-6 col-md-4 small-padding">
<div class="info">
<font-awesome-icon v-if="editMode" icon="arrows-alt-v" class="action drag me-3" />
<font-awesome-icon v-if="editMode" icon="times" class="action remove me-3" @click="removeMonitor(group.index, monitor.index)" />
@@ -71,7 +71,7 @@
</div>
</div>
</div>
<div :key="$root.userHeartbeatBar" class="col-3 col-md-4">
<div :key="$root.userHeartbeatBar" class="col-6 col-md-8">
<HeartbeatBar size="mid" :monitor-id="monitor.element.id" />
</div>
</div>

View File

@@ -1,48 +0,0 @@
<template>
<div class="mb-3">
<label for="ElksUsername" class="form-label">{{ $t("Username") }}</label>
<input id="ElksUsername" v-model="$parent.notification.elksUsername" type="text" class="form-control" required>
<label for="ElksPassword" class="form-label">{{ $t("Password") }}</label>
</div>
<div class="form-text">
<HiddenInput id="ElksPassword" v-model="$parent.notification.elksAuthToken" :required="true" autocomplete="new-password"></HiddenInput>
<i18n-t tag="p" keypath="Can be found on:">
<a href="https://46elks.com/account" target="_blank">https://46elks.com/account</a>
</i18n-t>
</div>
<div class="mb-3">
<label for="Elks-from-number" class="form-label">{{ $t("From") }}</label>
<input id="Elks-from-number" v-model="$parent.notification.elksFromNumber" type="text" class="form-control" required>
<div class="form-text">
{{ $t("Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.") }}
<i18n-t tag="p" keypath="More info on:">
<a href="https://46elks.se/kb/text-sender-id" target="_blank">https://46elks.se/kb/text-sender-id</a>
</i18n-t>
</div>
</div>
<div class="mb-3">
<label for="Elks-to-number" class="form-label">{{ $t("To Number") }}</label>
<input id="Elks-to-number" v-model="$parent.notification.elksToNumber" type="text" class="form-control" required>
<div class="form-text">
{{ $t("The phone number of the recipient in E.164 format.") }}
<i18n-t tag="p" keypath="More info on:">
<a href="https://46elks.se/kb/e164" target="_blank">https://46elks.se/kb/e164</a>
</i18n-t>
</div>
</div>
<i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;">
<a href="https://46elks.com/docs/send-sms" target="_blank">https://46elks.com/docs/send-sms</a>
</i18n-t>
</template>
<script>
import HiddenInput from "../HiddenInput.vue";
export default {
components: {
HiddenInput,
},
};
</script>

View File

@@ -1,47 +0,0 @@
<template>
<div class="mb-3">
<label for="sendgrid-api-key" class="form-label">{{ $t("SendGrid API Key") }}</label>
<HiddenInput id="push-api-key" v-model="$parent.notification.sendgridApiKey" :required="true" autocomplete="new-password"></HiddenInput>
</div>
<div class="mb-3">
<label for="sendgrid-from-email" class="form-label">{{ $t("From Email") }}</label>
<input id="sendgrid-from-email" v-model="$parent.notification.sendgridFromEmail" type="email" class="form-control" required>
</div>
<div class="mb-3">
<label for="sendgrid-to-email" class="form-label">{{ $t("To Email") }}</label>
<input id="sendgrid-to-email" v-model="$parent.notification.sendgridToEmail" type="email" class="form-control" required>
</div>
<div class="mb-3">
<label for="sendgrid-cc-email" class="form-label">{{ $t("smtpCC") }}</label>
<input id="sendgrid-cc-email" v-model="$parent.notification.sendgridCcEmail" type="email" class="form-control">
<div class="form-text">{{ $t("Separate multiple email addresses with commas") }}</div>
</div>
<div class="mb-3">
<label for="sendgrid-bcc-email" class="form-label">{{ $t("smtpBCC") }}</label>
<input id="sendgrid-bcc-email" v-model="$parent.notification.sendgridBccEmail" type="email" class="form-control">
<small class="form-text text-muted">{{ $t("Separate multiple email addresses with commas") }}</small>
</div>
<div class="mb-3">
<label for="sendgrid-subject" class="form-label">{{ $t("Subject:") }}</label>
<input id="sendgrid-subject" v-model="$parent.notification.sendgridSubject" type="text" class="form-control">
<small class="form-text text-muted">{{ $t("leave blank for default subject") }}</small>
</div>
<i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;">
<a href="https://docs.sendgrid.com/api-reference/mail-send/mail-send" target="_blank">https://docs.sendgrid.com/api-reference/mail-send/mail-send</a>
</i18n-t>
</template>
<script>
import HiddenInput from "../HiddenInput.vue";
export default {
components: {
HiddenInput,
},
mounted() {
if (typeof this.$parent.notification.sendgridSubject === "undefined") {
this.$parent.notification.sendgridSubject = "Notification from Your Uptime Kuma";
}
},
};
</script>

View File

@@ -4,53 +4,6 @@
<HiddenInput id="push-api-key" v-model="$parent.notification.pushAPIKey" :required="true" autocomplete="new-password"></HiddenInput>
</div>
<div class="mb-3">
<label for="push-api-title" class="form-label">{{ $t("Title") }}</label>
<input id="push-api-title" v-model="$parent.notification.pushTitle" type="text" class="form-control">
</div>
<div class="mb-3">
<label for="push-api-channel" class="form-label">{{ $t("Notification Channel") }}</label>
<input id="push-api-channel" v-model="$parent.notification.pushChannel" type="text" class="form-control" patttern="[A-Za-z0-9-]+">
<div class="form-text">
{{ $t("Alphanumerical string and hyphens only") }}
</div>
</div>
<div class="mb-3">
<label for="push-api-sound" class="form-label">{{ $t("Sound") }}</label>
<select id="push-api-sound" v-model="$parent.notification.pushSound" class="form-select">
<option value="default">{{ $t("Default") }}</option>
<option value="arcade">{{ $t("Arcade") }}</option>
<option value="correct">{{ $t("Correct") }}</option>
<option value="fail">{{ $t("Fail") }}</option>
<option value="harp">{{ $t("Harp") }}</option>
<option value="reveal">{{ $t("Reveal") }}</option>
<option value="bubble">{{ $t("Bubble") }}</option>
<option value="doorbell">{{ $t("Doorbell") }}</option>
<option value="flute">{{ $t("Flute") }}</option>
<option value="money">{{ $t("Money") }}</option>
<option value="scifi">{{ $t("Scifi") }}</option>
<option value="clear">{{ $t("Clear") }}</option>
<option value="elevator">{{ $t("Elevator") }}</option>
<option value="guitar">{{ $t("Guitar") }}</option>
<option value="pop">{{ $t("Pop") }}</option>
</select>
<div class="form-text">
{{ $t("Custom sound to override default notification sound") }}
</div>
</div>
<div class="mb-3">
<div class="form-check form-switch">
<input v-model="$parent.notification.pushTimeSensitive" class="form-check-input" type="checkbox">
<label class="form-check-label">{{ $t("Time Sensitive (iOS Only)") }}</label>
</div>
<div class="form-text">
{{ $t("Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.") }}
</div>
</div>
<i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;">
<a href="https://docs.push.techulus.com" target="_blank">https://docs.push.techulus.com</a>
</i18n-t>
@@ -63,19 +16,5 @@ export default {
components: {
HiddenInput,
},
mounted() {
if (typeof this.$parent.notification.pushTitle === "undefined") {
this.$parent.notification.pushTitle = "Uptime-Kuma";
}
if (typeof this.$parent.notification.pushChannel === "undefined") {
this.$parent.notification.pushChannel = "uptime-kuma";
}
if (typeof this.$parent.notification.pushSound === "undefined") {
this.$parent.notification.pushSound = "default";
}
if (typeof this.$parent.notification.pushTimeSensitive === "undefined") {
this.$parent.notification.pushTimeSensitive = true;
}
},
};
</script>

View File

@@ -9,7 +9,6 @@ import CallMeBot from "./CallMeBot.vue";
import SMSC from "./SMSC.vue";
import DingDing from "./DingDing.vue";
import Discord from "./Discord.vue";
import Elks from "./46elks.vue";
import Feishu from "./Feishu.vue";
import FreeMobile from "./FreeMobile.vue";
import GoogleChat from "./GoogleChat.vue";
@@ -66,7 +65,6 @@ import Whapi from "./Whapi.vue";
import Cellsynt from "./Cellsynt.vue";
import WPush from "./WPush.vue";
import SIGNL4 from "./SIGNL4.vue";
import SendGrid from "./SendGrid.vue";
/**
* Manage all notification form.
@@ -84,7 +82,6 @@ const NotificationFormList = {
"smsc": SMSC,
"DingDing": DingDing,
"discord": Discord,
"Elks": Elks,
"Feishu": Feishu,
"FreeMobile": FreeMobile,
"GoogleChat": GoogleChat,
@@ -140,8 +137,7 @@ const NotificationFormList = {
"whapi": Whapi,
"gtxmessaging": GtxMessaging,
"Cellsynt": Cellsynt,
"WPush": WPush,
"SendGrid": SendGrid,
"WPush": WPush
};
export default NotificationFormList;

View File

@@ -1 +0,0 @@
{}

View File

@@ -1050,43 +1050,5 @@
"less than": "по-малко от",
"greater than": "по-голямо от",
"greater than or equal to": "по-голямо или равно на",
"record": "запис",
"CurlDebugInfo": "За да отстраните грешки в монитора, можете или да поставите това в терминала на Вашата машина, или в терминала на машината, на която работи \"Uptime Kuma\" и да видите заявката.{newiline}Моля, вземете под внимание мрежовите разлики като {firewalls}, {dns_resolvers} или {docker_networks}.",
"shrinkDatabaseDescriptionSqlite": "Инициира {vacuum} за база данни тип SQLite. Функцията {auto_vacuum} вече е активирана, но това не дефрагментира базата данни, нито препакетира отделните страници на базата данни по начина, по който го прави командата {vacuum}.",
"ignoredTLSError": "TLS/SSL грешките са игнорирани",
"Debug": "Отстраняване на грешки",
"Copy": "Копирай",
"CopyToClipboardError": "Неуспешно копиране в клипборда: {error}",
"CopyToClipboardSuccess": "Копирано!",
"firewalls": "защитни стени",
"dns resolvers": "DNS преобразуватели",
"docker networks": "docker мрежи",
"CurlDebugInfoOAuth2CCUnsupported": "Пълният oauth клиентски идентификационен поток не се поддържа в {curl}.{newline}Моля, вземете токен на носител и го предайте чрез опцията {oauth2_bearer}.",
"CurlDebugInfoProxiesUnsupported": "Поддръжката на прокси в горната команда {curl} в момента не е внедрена.",
"Message format": "Формат на съобщението",
"Send rich messages": "Изпращай съобщения в \"rich\" формат",
"Sound": "Звук",
"Notification Channel": "Канал за известяване",
"Alphanumerical string and hyphens only": "Само буквено-цифров низ и тирета",
"Arcade": "Arcade",
"Correct": "Правилно",
"Fail": "Грешка",
"Harp": "Арфа",
"Reveal": "Разкрий",
"Bubble": "Балон",
"Doorbell": "Звънец на врата",
"Flute": "Флейта",
"Money": "Пари",
"Scifi": "Nаучна фантастика",
"Clear": "Премахни",
"Elevator": "Асансьор",
"Guitar": "Китара",
"Pop": "Поп",
"Custom sound to override default notification sound": "Персонализиран звук, заменящ звука за известяване по подразбиране",
"Time Sensitive (iOS Only)": "Зависещи от часа (само за iOS)",
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Известията от типа \"Зависещи от часа\" ще бъдат доставени незабавно, дори ако устройството е в режим „Не безпокойте“.",
"From": "От",
"Can be found on:": "Можте да се откриете на: {0}",
"The phone number of the recipient in E.164 format.": "Телефонният номер на получателя във формат E.164.",
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.": "Идентификационен номер на подателя на текста или телефонен номер във формат E.164, в случай, че желаете да получавате отговори."
"record": "запис"
}

View File

@@ -1011,45 +1011,5 @@
"OAuth2: Client Credentials": "OAuth2: přihlašovací údaje klienta",
"Authentication Method": "Metoda ověřování",
"Authorization Header": "Hlavička autorizace",
"Form Data Body": "Tělo formuláře s daty",
"threemaRecipientTypePhoneFormat": "E.164, bez počátečního +",
"jsonQueryDescription": "Pro zpracování a získání konkrétních dat z JSON odpovědi serveru použijte JSON dotaz - případně \"$\" pro zdrojovou (raw) odpověď, pokud neočekáváte JSON výstup. Výsledek bude následně porovnán jako řetězec vůči očekávaní hodnotě. Dokumentaci naleznete na {0} a pro testování dotazů můžete využít {1}.",
"shrinkDatabaseDescriptionSqlite": "Podmínka spuštění příkazu {vacuum} nad SQLite databází. Příkaz {auto_vacuum} je již zapnutý, ale nedochází k defragmentaci databáze ani k přebalení jednotlivých stránek databáze tak, jak to dělá příkaz {vacuum}.",
"Community String": "Řetězec komunity",
"Host Onesender": "Onesender hostitel",
"Token Onesender": "Onesender token",
"snmpOIDHelptext": "Zadejte OID senzoru nebo stavu, který chcete monitorovat. Pokud si nejste jisti identifikátorem OID, použijte nástroje pro správu sítě, jako jsou prohlížeče MIB nebo SNMP software.",
"snmpCommunityStringHelptext": "Tento řetězec slouží jako heslo pro ověřování a řízení přístupu k zařízením podporujícím protokol SNMP. Shodujte se s konfigurací zařízení SNMP.",
"record": "záznam",
"Go back to home page.": "Vrátit se domovskou stránku.",
"No tags found.": "Nenalezeny žádné štítky.",
"Lost connection to the socket server.": "Ztraceno socketové spojení se serverem.",
"Cannot connect to the socket server.": "Nelze navázat socketové spojení se serverem.",
"SIGNL4": "SIGNL4",
"SIGNL4 Webhook URL": "URL adresa webhooku SIGNL4",
"signl4Docs": "Další informace související s konfigurací SIGNL4 a postup jak získat URL webhooku SIGNL4 naleznete na {0}.",
"Conditions": "Podmínky",
"conditionAdd": "Přidat podmínku",
"conditionDelete": "Vymazat podmínku",
"conditionAddGroup": "Přidat skupinu",
"conditionDeleteGroup": "Smazat skupinu",
"conditionValuePlaceholder": "Hodnota",
"equals": "rovná se",
"not equals": "nerovná se",
"contains": "obsahuje",
"not contains": "neobsahuje",
"starts with": "začíná na",
"not starts with": "nezačíná na",
"ends with": "končí na",
"not ends with": "nekončí na",
"less than": "menší než",
"greater than": "větší než",
"less than or equal to": "menší nebo rovno",
"greater than or equal to": "větší nebo rovno",
"groupOnesenderDesc": "Ujistěte se, že jste zadali platné GroupID. Pro odeslání zprávy do skupiny zadejte například 628123456789-342345",
"OAuth Token URL": "URL OAuth tokenu",
"Client ID": "ID klienta",
"Client Secret": "Tajemství klienta",
"OAuth Scope": "OAuth rozsah",
"Optional: Space separated list of scopes": "Volitelné: seznam rozsahů oddělte mezerami"
"Form Data Body": "Tělo formuláře s daty"
}

View File

@@ -1,7 +1,7 @@
{
"languageName": "Deutsch (Schweiz)",
"Settings": "Einstellungen",
"Dashboard": "Überblick",
"Dashboard": "Dashboard",
"New Update": "Update verfügbar",
"Language": "Sprache",
"Appearance": "Erscheinungsbild",
@@ -1047,43 +1047,5 @@
"greater than": "mehr als",
"less than or equal to": "kleiner als oder gleich",
"greater than or equal to": "grösser als oder gleich",
"record": "Eintrag",
"shrinkDatabaseDescriptionSqlite": "Datenbank {vacuum} für SQLite auslösen. {auto_vacuum} ist bereits aktiviert, aber dies defragmentiert die Datenbank nicht und packt auch nicht einzelne Datenbankseiten neu, wie es der Befehl {vacuum} tut.",
"ignoredTLSError": "TLS/SSL-Fehler wurden ignoriert",
"Debug": "Debug",
"Copy": "Kopieren",
"CurlDebugInfoOAuth2CCUnsupported": "Der vollständige OAuth-Client-Credential-Flow wird in {curl} nicht unterstützt.{newline}Bitte besorge dir ein Bearer-Token und übergebe ihn über die {oauth2_bearer}-Option.",
"Money": "Geld",
"Custom sound to override default notification sound": "Benutzerdefinierter Ton, um den standardmässigen Benachrichtigungston zu ersetzen",
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.": "Entweder eine Text-Absender-ID oder eine Telefonnummer im E.164-Format, wenn du Antworten erhalten möchtest.",
"CopyToClipboardError": "Konnte nicht in die Zwischenablage kopiert werden: {error}",
"CopyToClipboardSuccess": "Kopiert!",
"CurlDebugInfo": "Um den Monitor zu debuggen, kannst du dies entweder in das Terminal deines eigenen Rechners oder in das Terminal der Maschine, auf der Uptime Kuma läuft, einfügen und überprüfen, was du anforderst.{newiline}Bitte beachte Netzwerkunterschiede wie {firewalls}, {dns_resolvers} oder {docker_networks}.",
"firewalls": "Firewalls",
"dns resolvers": "DNS-Resolver",
"docker networks": "Docker-Netzwerke",
"CurlDebugInfoProxiesUnsupported": "Die Unterstützung von Proxys im oben genannten {curl}-Befehl ist derzeit nicht implementiert.",
"Message format": "Nachrichtenformat",
"Send rich messages": "Sende Rich-Text-Nachrichten",
"Notification Channel": "Benachrichtigungskanal",
"Sound": "Benachrichtigungston",
"Alphanumerical string and hyphens only": "Nur alphanumerische Zeichen und Bindestriche",
"Correct": "Korrekt",
"Fail": "Fehlgeschlagen",
"Harp": "Harfe",
"Reveal": "Enthüllen",
"Bubble": "Blase",
"Doorbell": "Türklingel",
"Flute": "Flöte",
"Scifi": "Science Fiction",
"Clear": "Klar",
"Elevator": "Lift",
"Guitar": "Gitarre",
"Pop": "Pop",
"Time Sensitive (iOS Only)": "Zeitkritisch (nur iOS)",
"The phone number of the recipient in E.164 format.": "Die Telefonnummer des Empfängers im E.164-Format.",
"Can be found on:": "Ist zu finden auf: {0}",
"From": "Von",
"Arcade": "Spielhalle",
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Zeitkritische Benachrichtigungen werden sofort zugestellt, auch wenn sich das Gerät im Nicht stören-Modus befindet."
"record": "Eintrag"
}

View File

@@ -1,7 +1,7 @@
{
"languageName": "Deutsch",
"Settings": "Einstellungen",
"Dashboard": "Überblick",
"Dashboard": "Dashboard",
"New Update": "Aktualisierung verfügbar",
"Language": "Sprache",
"Appearance": "Erscheinungsbild",
@@ -1050,43 +1050,5 @@
"less than": "weniger als",
"less than or equal to": "kleiner als oder gleich",
"greater than or equal to": "größer als oder gleich",
"record": "Eintrag",
"shrinkDatabaseDescriptionSqlite": "Datenbank {vacuum} für SQLite auslösen. {auto_vacuum} ist bereits aktiviert, aber dies defragmentiert die Datenbank nicht und packt auch nicht einzelne Datenbankseiten neu, wie es der Befehl {vacuum} tut.",
"ignoredTLSError": "TLS/SSL-Fehler wurden ignoriert",
"Message format": "Nachrichtenformat",
"Notification Channel": "Benachrichtigungskanal",
"Custom sound to override default notification sound": "Benutzerdefinierter Ton, um den standardmäßigen Benachrichtigungston zu ersetzen",
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Zeitkritische Benachrichtigungen werden sofort zugestellt, auch wenn sich das Gerät im Nicht stören-Modus befindet.",
"Debug": "Debug",
"Copy": "Kopieren",
"CopyToClipboardError": "Konnte nicht in die Zwischenablage kopiert werden: {error}",
"CopyToClipboardSuccess": "Kopiert!",
"CurlDebugInfo": "Um den Monitor zu debuggen, kannst du dies entweder in das Terminal deines eigenen Rechners oder in das Terminal der Maschine, auf der Uptime Kuma läuft, einfügen und überprüfen, was du anforderst.{newiline}Bitte beachte Netzwerkunterschiede wie {firewalls}, {dns_resolvers} oder {docker_networks}.",
"firewalls": "Firewalls",
"dns resolvers": "DNS-Resolver",
"docker networks": "Docker-Netzwerke",
"CurlDebugInfoOAuth2CCUnsupported": "Der vollständige OAuth-Client-Credential-Flow wird in {curl} nicht unterstützt.{newline}Bitte besorge dir ein Bearer-Token und übergebe ihn über die {oauth2_bearer}-Option.",
"CurlDebugInfoProxiesUnsupported": "Die Unterstützung von Proxys im oben genannten {curl}-Befehl ist derzeit nicht implementiert.",
"Alphanumerical string and hyphens only": "Nur alphanumerische Zeichen und Bindestriche",
"Correct": "Korrekt",
"Harp": "Harfe",
"Doorbell": "Türklingel",
"Flute": "Flöte",
"Money": "Geld",
"Scifi": "Science Fiction",
"Elevator": "Aufzug",
"Guitar": "Gitarre",
"Sound": "Benachrichtigungston",
"Time Sensitive (iOS Only)": "Zeitkritisch (nur iOS)",
"From": "Von",
"Can be found on:": "Ist zu finden auf: {0}",
"The phone number of the recipient in E.164 format.": "Die Telefonnummer des Empfängers im E.164-Format.",
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.": "Entweder eine Text-Absender-ID oder eine Telefonnummer im E.164-Format, wenn du Antworten erhalten möchtest.",
"Send rich messages": "Sende Rich-Text-Nachrichten",
"Fail": "Fehlgeschlagen",
"Reveal": "Enthüllen",
"Bubble": "Blase",
"Clear": "Klar",
"Pop": "Pop",
"Arcade": "Spielhalle"
"record": "Eintrag"
}

View File

@@ -251,7 +251,7 @@
"PushUrl": "Push URL",
"HeadersInvalidFormat": "The request headers are not valid JSON: ",
"BodyInvalidFormat": "The request body is not valid JSON: ",
"CopyToClipboardError": "Couldn't copy to clipboard: {error}",
"CopyToClipboardError": "Couldn't copy to clipbard: {error}",
"CopyToClipboardSuccess": "Copied!",
"CurlDebugInfo": "To debug the monitor, you can either paste this into your own machines terminal or into the machines terminal which uptime kuma is running on and see what you are requesting.{newiline}Please be aware of networking differences like {firewalls}, {dns_resolvers} or {docker_networks}.",
"firewalls": "firewalls",
@@ -1027,38 +1027,5 @@
"greater than": "greater than",
"less than or equal to": "less than or equal to",
"greater than or equal to": "greater than or equal to",
"record": "record",
"Notification Channel": "Notification Channel",
"Sound": "Sound",
"Alphanumerical string and hyphens only": "Alphanumerical string and hyphens only",
"Arcade": "Arcade",
"Correct": "Correct",
"Fail": "Fail",
"Harp": "Harp",
"Reveal": "Reveal",
"Bubble": "Bubble",
"Doorbell": "Doorbell",
"Flute": "Flute",
"Money": "Money",
"Scifi": "Scifi",
"Clear": "Clear",
"Elevator": "Elevator",
"Guitar": "Guitar",
"Pop": "Pop",
"Custom sound to override default notification sound": "Custom sound to override default notification sound",
"Time Sensitive (iOS Only)": "Time Sensitive (iOS Only)",
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.",
"From": "From",
"Can be found on:": "Can be found on: {0}",
"The phone number of the recipient in E.164 format.": "The phone number of the recipient in E.164 format.",
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.": "Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.",
"RabbitMQ Nodes": "RabbitMQ Management Nodes",
"rabbitmqNodesDescription": "Enter the URL for the RabbitMQ management nodes including protocol and port. Example: {0}",
"rabbitmqNodesRequired": "Please set the nodes for this monitor.",
"rabbitmqNodesInvalid": "Please use a fully qualified (starting with 'http') URL for RabbitMQ nodes.",
"RabbitMQ Username": "RabbitMQ Username",
"RabbitMQ Password": "RabbitMQ Password",
"rabbitmqHelpText": "To use the monitor, you will need to enable the Management Plugin in your RabbitMQ setup. For more information, please consult the {rabitmq_documentation}.",
"SendGrid API Key": "SendGrid API Key",
"Separate multiple email addresses with commas": "Separate multiple email addresses with commas"
"record": "record"
}

View File

@@ -1 +0,0 @@
{}

View File

@@ -242,6 +242,7 @@
"Running": "Töötab",
"resendEveryXTimes": "Saada uuesti {0} korda",
"statusMaintenance": "Hooldus",
"Webhook URL": "",
"Server URL": "Serveri URL",
"Priority": "Tähtsus",
"emojiCheatSheet": "Emotikoni spikker: {0}",
@@ -253,6 +254,7 @@
"PushUrl": "Lükka URL",
"Monitor History": "Monitori ajalugu",
"PasswordsDoNotMatch": "Paroolid ei ühti.",
"records": "",
"Current User": "Praegune kasutaja",
"topic": "Teema",
"successMessage": "Edukas sõnum",
@@ -306,6 +308,7 @@
"General Monitor Type": "Üldine monitori tüüp",
"webhookAdditionalHeadersDesc": "Lisab täiendavad päised saadetud webhookiga.",
"Read more": "Loe rohkem",
"HeadersInvalidFormat": "",
"clearDataOlderThan": "Hoia monitori ajalugu alles {0} päeva.",
"steamApiKeyDescription": "Steam Game Serveri monitoorimiseks on vaja sul Steam Web-API võtit. Sa saad registreerida enda API võtme siin: ",
"Done": "Tehtud",

View File

@@ -106,7 +106,7 @@
"disableauth.message2": "Egoera jakin batzuetarako diseinatuta dago, Uptime Kumaren {intendThirdPartyAuth} (Cloudflare Access, Authelia edo beste autentifikazio-mekanismo batzuk).",
"where you intend to implement third-party authentication": "aurrean hirugarrengo autentifikazio batzuek jartzeko",
"Please use this option carefully!": "Mesedez, kontuz erabili aukera hau!",
"Logout": "Itxi saioa",
"Logout": "Saioa amaitu",
"Leave": "Utzi",
"I understand, please disable": "Ulertzen dut, mesedez desgaitu",
"Confirm": "Baieztatu",
@@ -115,7 +115,7 @@
"Username": "Erabiltzailea",
"Password": "Pasahitza",
"Remember me": "Gogora nazazu",
"Login": "Hasi saioa",
"Login": "Saioa hasi",
"No Monitors, please": "Monitorizaziorik ez, mesedez",
"add one": "gehitu bat",
"Notification Type": "Jakinarazpen mota",
@@ -164,11 +164,11 @@
"Add New below or Select...": "Gehitu beste bat behean edo hautatu…",
"Tag with this name already exist.": "Izen hau duen etiketa dagoeneko badago.",
"Tag with this value already exist.": "Balio hau duen etiketa dagoeneko badago.",
"color": "Kolorea",
"color": "kolorea",
"value (optional)": "balioa (hautazkoa)",
"Gray": "Grisa",
"Red": "Gorria",
"Orange": "Laranja",
"Orange": "Naranja",
"Green": "Berdea",
"Blue": "Urdina",
"Indigo": "Indigo",
@@ -190,7 +190,7 @@
"Status Page": "Egoera orria",
"Status Pages": "Egoera orriak",
"defaultNotificationName": "Nire {notification} Alerta ({number})",
"here": "hemen",
"here": "Hemen",
"Required": "Beharrezkoa",
"telegram": "Telegram",
"ZohoCliq": "ZohoCliq",
@@ -582,10 +582,6 @@
"Mechanism": "Mekanismoa",
"Home": "Hasiera",
"filterActive": "Aktibo",
"filterActivePaused": "Pausatua",
"Expected Value": "Esperotako balioa",
"statusPageRefreshIn": "{0} barru freskatuko da.",
"now": "orain",
"time ago": "duela {0}",
"-year": "-urte"
"filterActivePaused": "Geldituta",
"Expected Value": "Esperotako balioa"
}

View File

@@ -1048,43 +1048,5 @@
"greater than": "enemmän kuin",
"less than or equal to": "vähemmän tai yhtä paljon kuin",
"greater than or equal to": "enemmän tai yhtä paljon kuin",
"record": "tietue",
"Notification Channel": "Ilmoituskanava",
"Custom sound to override default notification sound": "Mukautettu ääni oletusäänen sijaan",
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Aikaherkät ilmoitukset toimitetaan välittömästi vaikka laite olisi Älä Häiritse-tilassa.",
"ignoredTLSError": "TLS/SSL-virheitä ei huomioida",
"Debug": "Debug",
"Copy": "Kopioi",
"CopyToClipboardError": "Leikepöydälle kopiointi ei onnistunut: {error}",
"CopyToClipboardSuccess": "Kopioitu!",
"CurlDebugInfo": "Debugataksesi seurainta, voit joko kopioida tämän oman tietokoneesi komentoriville tai sen tietokoneen komentoriville jossa uptime kuma on käynissä nähdäksesi pyyntösi.{newiline}Otathan huomioon verkkoympäristöjen erot kuten {firewalls}, {dns_resolvers} tai {docker_networks}.",
"firewalls": "palomuurit",
"dns resolvers": "DNS-palvelimet",
"docker networks": "Docker-verkot",
"CurlDebugInfoOAuth2CCUnsupported": "{curl} ei tue täydellistä oauth-asiakkaan kirjautumistietojen välittämistä.{newline}Hanki bearer token -tunnus ja välitä se {oauth2_bearer}-vaihtoehdon kautta.",
"CurlDebugInfoProxiesUnsupported": "Välityspalvelimen tukea yllä olevassa {curl}-komennossa ei ole tällä hetkellä toteutettu.",
"shrinkDatabaseDescriptionSqlite": "Käynnistä tietokannan {vacuum} SQLitelle. {auto_vacuum} on jo käytössä, mutta se ei eheytä tietokantaa tai uudelleenpakkaa yksittäisiä tietokantasivuja samoin kuin {vacuum}.",
"Message format": "Viestin muoto",
"Send rich messages": "Lähetä monipuolisia viestejä (rich messages)",
"Sound": "Ääni",
"Alphanumerical string and hyphens only": "Vain aakkosnumeerinen merkkijono ja yhdysmerkit",
"Arcade": "Pelihalli",
"Harp": "Harppu",
"Fail": "Virhe",
"Correct": "Onnistuminen",
"Reveal": "Ilmestys",
"Bubble": "Kupla",
"Doorbell": "Ovikello",
"Flute": "Huilu",
"Money": "Raha",
"Clear": "Kirkas",
"Elevator": "Hissi",
"Guitar": "Kitara",
"Time Sensitive (iOS Only)": "Aikaherkkä (vain iOS)",
"From": "Lähettäjä",
"Can be found on:": "Löydettävissä: {0}",
"The phone number of the recipient in E.164 format.": "Vastaanottajan puhelinnumero E.164-muodossa.",
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.": "Joko lähettäjätunnus (sender ID) tai puhelinnumero E-164-muodossa jos haluat pystyä vastaanottamaan vastausviestejä.",
"Scifi": "Tieteisseikkailu",
"Pop": "Poksahdus"
"record": "tietue"
}

View File

@@ -1050,43 +1050,5 @@
"greater than": "supérieur à",
"less than or equal to": "inférieur ou égal à",
"greater than or equal to": "supérieur ou égal à",
"record": "enregistrer",
"shrinkDatabaseDescriptionSqlite": "Déclencher la commande {vacuum} pour la base de données SQLite. {auto_vacuum} est déjà activé, mais cela ne défragmente pas la base de données ni ne réorganise les pages individuelles de la base de données de la même manière que la commande {vacuum}.",
"ignoredTLSError": "Les erreurs TLS/SSL ont été ignorées",
"CopyToClipboardError": "Impossible de copier dans le presse-papiers : {error}",
"CurlDebugInfo": "Pour déboguer la sonde, vous pouvez soit le coller dans le terminal de votre propre machine, soit dans le terminal de la machine sur lequel kuma de disponibilité s'exécute et voir ce qui vous est demandé. {newiline} Veuillez être conscient des différences de réseau telles que {firewalls}, {dns_resolvers}. ou {docker_networks}.",
"Notification Channel": "Canal de notification",
"Alphanumerical string and hyphens only": "Chaîne alphanumérique et tirets uniquement",
"Custom sound to override default notification sound": "Son personnalisé pour remplacer le son de notification par défaut",
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Les notifications urgentes seront envoyées immédiatement, même si l'appareil est en mode Ne pas déranger.",
"Debug": "Déboguer",
"Copy": "Copie",
"CopyToClipboardSuccess": "Copié !",
"firewalls": "pare-feu",
"dns resolvers": "résolveurs DNS",
"docker networks": "réseaux dockers",
"CurlDebugInfoOAuth2CCUnsupported": "Le flux complet des informations d'identification du client OAuth n'est pas pris en charge dans {curl}. {newline}Veuillez obtenir un jeton d'accès bearer token et le transmettre via l'option {oauth2_bearer}.",
"CurlDebugInfoProxiesUnsupported": "La prise en charge du proxy dans la commande {curl} ci-dessus n'est actuellement pas implémentée.",
"Message format": "Format des messages",
"Send rich messages": "Envoyer des messages enrichis",
"Sound": "Son",
"Arcade": "Arcade",
"Correct": "Correcte",
"Fail": "Échouer",
"Harp": "Harpe",
"Reveal": "Révéler",
"Bubble": "Bulle",
"Doorbell": "Sonnette",
"Flute": "Flûte",
"Money": "Argent",
"Scifi": "Science-fiction",
"Clear": "Effacer",
"Elevator": "Ascenseur",
"Guitar": "Guitare",
"Pop": "Pop",
"Time Sensitive (iOS Only)": "Sensible au temps (iOS uniquement)",
"From": "De",
"Can be found on:": "Disponible sur : {0}",
"The phone number of the recipient in E.164 format.": "Le numéro de téléphone du destinataire au format E.164.",
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.": "Soit un identifiant d'expéditeur de texte, soit un numéro de téléphone au format E.164 si vous souhaitez pouvoir recevoir des réponses."
"record": "enregistrer"
}

View File

@@ -1015,6 +1015,5 @@
"less than": "níos lú ná",
"greater than": "níos mó ná",
"less than or equal to": "níos lú ná nó cothrom le",
"record": "taifead",
"shrinkDatabaseDescriptionSqlite": "Bunachar sonraí truicear {vacuum} le haghaidh SQLite. Tá {auto_vacuum} cumasaithe cheana féin ach ní dhéanann sé seo scoilt ar an mbunachar sonraí ná athphacáil leathanaigh aonair an bhunachair sonraí mar a dhéanann an t-ordú {vacuum}."
"record": "taifead"
}

View File

@@ -983,104 +983,5 @@
"threemaRecipientTypePhone": "Telefonski broj",
"threemaRecipientTypePhoneFormat": "E.164, bez vodećeg znaka +",
"threemaBasicModeInfo": "Napomena: Ova integracija koristi Threema Gateway u osnovnom načinu rada (enkripcija temeljena na poslužitelju). Dodatne pojedinosti možete pronaći na {0}.",
"smspartnerSenderNameInfo": "Mora biti između 3 i 11 znakova",
"shrinkDatabaseDescriptionSqlite": "Pokreni {vacuum} baze podataka za SQLite. Opcija {auto_vacuum} je već omogućena, ali to ne defragmentira bazu podataka niti ponovno pakira pojedinačne stranice baze podataka na način na koji to radi naredba {vacuum}.",
"ignoredTLSError": "TLS/SSL greške se ignoriraju",
"cacheBusterParam": "Dodaj parametar {0}",
"cacheBusterParamDescription": "Nasumično generirani parametar, za preskakanje predmemorije.",
"snmpCommunityStringHelptext": "Ovaj niz funkcionira kao lozinka za provjeru autentičnosti i kontrolu pristupa uređajima s omogućenim SNMP-om. Uskladite ga sa svojom konfiguracijom SNMP uređaja.",
"privateOnesenderDesc": "Provjerite je li broj telefona valjan. Za slanje poruke na privatni telefonski broj, npr. 628123456789",
"Go back to home page.": "Vratite se na početnu stranicu.",
"signl4Docs": "Više informacija o tome kako konfigurirati SIGNL4 i kako dobiti SIGNL4 URL webhooka možete pronaći na {0}.",
"not starts with": "ne počinje s",
"less than or equal to": "manje od ili jednako",
"Doorbell": "Zvono na vratima",
"Custom sound to override default notification sound": "Prilagođeni zvuk za zamjenu zadanog zvuka obavijesti",
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Vremenski osjetljive obavijesti bit će isporučene odmah, čak i ako je uređaj u načinu rada bez ometanja.",
"now": "sada",
"time ago": "prije {0}",
"-year": "-godišnje",
"Json Query Expression": "Upit u JSON obliku",
"Community String": "Zajednički niz teksta",
"conditionAddGroup": "Dodaj grupu",
"conditionDeleteGroup": "Obriši grupu",
"Debug": "Otklanjanje grešaka",
"Copy": "Kopirati",
"CopyToClipboardError": "Greška pri kopiranju u međuspremnik: {error}",
"CopyToClipboardSuccess": "Kopirano!",
"dns resolvers": "DNS razrješivači",
"firewalls": "vatrozidi",
"CurlDebugInfo": "Za otklanjanje grešaka u Monitoru, možete zalijepiti ovo u terminal na vlastitom računalu ili računalu na kojem se Uptime Kuma pokreće, kako biste isprobali Vaš mrežni zahtjev.{newiline}Budite svjesni mrežnih razlika koje mogu činiti {firewalls}, {dns_resolvers} ili {docker_networks}.",
"docker networks": "Dockerove mreže",
"CurlDebugInfoOAuth2CCUnsupported": "Potpuni tok vjerodajnica klijenta OAuth nije podržan u {curl}.{newline}Nabavite token nositelja i proslijedite ga koristeći opciju {oauth2_bearer}.",
"CurlDebugInfoProxiesUnsupported": "Proxy podrška u gornjoj naredbi {curl} trenutno nije implementirana.",
"and": "i",
"Message format": "Format poruke",
"Send rich messages": "Slanje poruka s obogaćenim tekstom",
"OID (Object Identifier)": "OID (Identifikator objekta)",
"snmpOIDHelptext": "Unesite OID za senzor ili status kojeg želite monitorirati. Koristite alate za upravljanje mrežom poput MIB preglednika ili SNMP programa ako niste sigurni koja je vrijednost OID-a.",
"Condition": "Uvjet",
"SNMP Version": "Inačica SNMP-a",
"Please enter a valid OID.": "Unesite važeći OID.",
"Recipient Type": "Tip primatelja",
"Private Number": "Privatni broj",
"wayToGetOnesenderUrlandToken": "URL i token možete dobiti odlaskom na OneSender web stranicu. Više informacija na {0}",
"Token Onesender": "OneSender Token",
"Host Onesender": "Adresa OneSender domaćina",
"Group ID": "Identifikator Grupe",
"groupOnesenderDesc": "Provjerite je li Identifikator Grupe valjan. Za slanje poruke na Grupu, npr. 628123456789-342345",
"Add Remote Browser": "Dodaj udaljeni preglednik",
"New Group": "Nova grupa",
"Group Name": "Naziv grupe",
"OAuth2: Client Credentials": "OAuth2: vjerodajnice klijenta",
"Authentication Method": "Metoda provjere autentičnosti",
"Authorization Header": "Zaglavlje autorizacije",
"Form Data Body": "Tijelo podataka obrasca",
"OAuth Token URL": "URL OAuth tokena",
"Client ID": "Klijentski identifikator",
"Client Secret": "Klijentska tajna",
"OAuth Scope": "OAuth opseg",
"Optional: Space separated list of scopes": "Neobavezno: popis opsega odvojen razmakom",
"No tags found.": "Nema pronađenih oznaka.",
"Lost connection to the socket server.": "Izgubljena veza sa socket poslužiteljem.",
"Cannot connect to the socket server.": "Nije moguće spojiti se na socket poslužitelj.",
"SIGNL4": "SIGNL4",
"SIGNL4 Webhook URL": "SIGNL4 URL webhooka",
"Conditions": "Uvjeti",
"conditionAdd": "Dodaj uvjet",
"conditionDelete": "Obriši uvjet",
"conditionValuePlaceholder": "Vrijednost",
"equals": "je jednako",
"not equals": "nije jednako",
"contains": "sadržava",
"not contains": "ne sadržava",
"starts with": "počinje s",
"ends with": "završava s",
"not ends with": "ne završava s",
"less than": "manje od",
"greater than": "veće od",
"greater than or equal to": "veće od ili jednako",
"record": "zapis",
"Notification Channel": "Kanal obavijesti",
"Sound": "Zvuk",
"Alphanumerical string and hyphens only": "Samo alfanumerički niz i crtice",
"Arcade": "Arkadno",
"Fail": "Neuspjeh",
"Correct": "Ispravno",
"Harp": "Harfa",
"Reveal": "Otkrivanje",
"Bubble": "Mjehurić",
"Flute": "Flauta",
"Money": "Novac",
"Scifi": "Znanstvena fantastika",
"Clear": "Čisto",
"Elevator": "Dizalo",
"Guitar": "Gitara",
"Pop": "Pop",
"Time Sensitive (iOS Only)": "Vremenski osjetljivo (samo iOS)",
"From": "Od",
"Can be found on:": "Može se pronaći na: {0}",
"The phone number of the recipient in E.164 format.": "Telefonski broj primatelja u formatu E.164.",
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.": "Ili identifikator pošiljatelja tekstualne poruke ili telefonski broj u formatu E.164 ako želite primati odgovore.",
"jsonQueryDescription": "Izvršite JSON upit nad primljenim odgovorom i provjerite očekivanu povratnu vrijednost. Koristite \"$\" za zahtjeve u kojima ne očekujete JSON odgovor. Povratna vrijednost će se za usporedbu pretvoriti u niz znakova (string). Pogledajte stranicu {0} za dokumentaciju o jeziku upita. Testno okruženje možete pronaći na {1}."
"smspartnerSenderNameInfo": "Mora biti između 3 i 11 znakova"
}

View File

@@ -1045,12 +1045,5 @@
"New Group": "Grup Baru",
"Group Name": "Nama Grup",
"OAuth2: Client Credentials": "OAuth2: Kredensial Klien",
"Authentication Method": "Metode Autentikasi",
"shrinkDatabaseDescriptionSqlite": "Memicu pangkalan data {vacuum} untuk SQLite. {auto_vacuum} sudah diaktifkan, tetapi tidak mendefragmentasi pangkalan data atau mengemas ulang halaman individual dari pangkalan data seperti yang dilakukan oleh perintah {vacuum}.",
"ignoredTLSError": "Galat TLS/SSL sudah diabaikan",
"Debug": "Awakutu",
"Copy": "Salin",
"CopyToClipboardError": "Tidak bisa menyalin ke papan klip: {galat}",
"CopyToClipboardSuccess": "Tersalin!",
"CurlDebugInfo": "Untuk pengawakutuan monitor, Anda bisa menempelkan ini ke terminal mesin Anda sendiri atau ke terminal mesin di mana uptime kuma sedang berjalan dan melihat apa yang Anda harapkan. Mohon perhatikan perbedaan jaringan seperti {firewalls}, {dns_resolvers}, atau {docker_networks}."
"Authentication Method": "Metode Autentikasi"
}

View File

@@ -672,35 +672,5 @@
"emailCustomBody": "カスタム本文",
"emailTemplateServiceName": "サービス名",
"smtpLiquidIntroduction": "次の 2 つのテンプレート・フィールドは Liquid テンプレート言語で記述できます。これらの使い方は {0} にあります。以下は利用可能な変数です:",
"emailTemplateStatus": "ステータス",
"now": "現在",
"time ago": "{0}前",
"-year": "年",
"Json Query Expression": "Jsonクエリ表現",
"ignoredTLSError": "TLS/SSLエラーは無視されました",
"locally configured mail transfer agent": "独自設定されたメール転送エージェント",
"ignoreTLSErrorGeneral": "接続時のTLS/SSLエラーを無視する",
"successKeyword": "成功時のキーワード",
"pushViewCode": "Push モニターの使い方(コードを見る)",
"Reset Token": "トークンのリセット",
"templateLimitedToUpDownCertNotifications": "監視対象の UP/DOWN と証明書の有効期限通知でのみ利用可能",
"templateLimitedToUpDownNotifications": "監視対象の UP/DOWN の通知でのみ利用可能",
"webhookBodyPresetOption": "プリセット - {0}",
"Optional": "オプション",
"and": "かつ",
"From Email": "Emailより",
"CurlDebugInfoProxiesUnsupported": "上記コマンド {curl} のProxyサポートは現在、実装されていません。",
"Your User ID": "あなたのユーザーID",
"programmingLanguages": "プログラミング言語",
"Debug": "デバッグ",
"Copy": "コピー",
"CopyToClipboardError": "クリップボードにコピーできません: {error}",
"CopyToClipboardSuccess": "コピーしました!",
"firewalls": "ファイアウォール",
"dns resolvers": "DNSリゾルバ",
"docker networks": "Dockerネットワーク",
"sameAsServerTimezone": "サーバータイムゾーンと同じ",
"cronExpression": "Cron表記",
"invalidCronExpression": "不正なCron表記です: {0}",
"Single Maintenance Window": "シングルメンテナンスウィンドウ"
"emailTemplateStatus": "ステータス"
}

View File

@@ -294,7 +294,7 @@
"emailCustomSubject": "Niestandardowy temat",
"checkPrice": "Sprawdź ceny {0}:",
"octopushLegacyHint": "Czy używasz starszej wersji Octopush (2011-2020) czy nowej wersji?",
"Feishu WebHookUrl": "Adres webhooka Feishu",
"Feishu WebHookUrl": "Feishu WebHookURL",
"matrixHomeserverURL": "Adres URL serwera domowego (z http(s):// i opcjonalnie port)",
"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.",
@@ -410,7 +410,7 @@
"SignName": "Podpis",
"Sms template must contain parameters: ": "Szablon sms musi posiadać parametry: ",
"Bark Endpoint": "Punkt końcowy Bark",
"WebHookUrl": "Adres webhooka",
"WebHookUrl": "WebHookUrl",
"SecretKey": "Tajny klucz",
"For safety, must use secret key": "Ze względów bezpieczeństwa musisz użyć tajnego klucza",
"Device Token": "Token urządzenia",
@@ -989,104 +989,5 @@
"wayToGetThreemaGateway": "Możesz zarejestrować się w Threema Gateway {0}.",
"threemaSenderIdentityFormat": "8 znaków, zwykle zaczyna się od *",
"threemaBasicModeInfo": "Uwaga: Ta integracja korzysta z Threema Gateway w trybie podstawowym (szyfrowanie po stronie serwera). Więcej szczegółów można znaleźć {0}.",
"apiKeysDisabledMsg": "Klucze API są wyłączone, ponieważ wyłączone jest uwierzytelnianie.",
"-year": "-rok",
"and": "i",
"now": "teraz",
"cacheBusterParam": "Dodaj parametr {0}",
"CopyToClipboardError": "Błąd podczas kopiowania do schowka: {error}",
"CurlDebugInfo": "Aby zdebugować monitor, możesz wkleić ten ciąg do terminala na swojej maszynie lub na maszynie gdzie uptime kuma jest uruchomina aby zobaczyć co żądasz.{newiline} Miej na uwadzę różnice sieciowe takich jak {firewalls}, {dns_resolvers} lub {docker_networks}.",
"shrinkDatabaseDescriptionSqlite": "Zażądaj {vacuum} bazy dla SQLite. {auto_vacuum} jest już włączone jednak nie defragmentuje to bazy ani nie przepakowuje indywidualnych stron bazy w taki sam sposób jak robi to polecenie {vacuum}.",
"cacheBusterParamDescription": "Losowo wygenerowany parametr w celu pominięcia pamięci podręcznej.",
"Group Name": "Nazwa grupy",
"OAuth2: Client Credentials": "OAuth2: Poświadczenia klienta",
"signl4Docs": "Aby zdobyć więcej informacji jak skonfigurować SIGNL4 i jak zdobyć odnośnik webhooka SIGNL4 udaj się do {0}.",
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Czasowo wrażliwe powiadomienia zostaną dostarczone natychmiastowo, nawet jeśli urzadzenie jest w trybie nie przeszkadzać.",
"time ago": "{0} temu",
"Json Query Expression": "Wyrażenie zapytania JSON",
"Condition": "Warunek",
"snmpCommunityStringHelptext": "Ten ciąg funkcjonuje jako hasło do uwierzytelnienia i kontroli dostęp do urządzeń SNMP. Dopasuj go do konfiguracji urządzenia SNMP.",
"SNMP Version": "Wersja SNMP",
"OID (Object Identifier)": "OID (Identyfikator obiektu)",
"snmpOIDHelptext": "Wprowadź OID sensora lub statusu który chcesz monitorować. Użyj narzędzia do zarządzania siecią jak przeglądarki MIB lub oprogramowanie SNMP jeśli nie masz pewności co do OID.",
"Please enter a valid OID.": "Wprowadź poprawny OID.",
"Host Onesender": "Serwer Onesender",
"Token Onesender": "Token Onesender",
"Recipient Type": "Typ odbiorcy",
"Go back to home page.": "Powróć do strony domowej.",
"No tags found.": "Nie znaleziono etykiet.",
"Authorization Header": "Nagłówek autoryzacji",
"Form Data Body": "Zawartość formularza danych",
"OAuth Token URL": "Odnośnik tokena OAuth",
"Client ID": "Identyfikator klienta",
"Client Secret": "Sekret klienta",
"OAuth Scope": "Zakres OAuth",
"Optional: Space separated list of scopes": "Opcjonalne: Oddzielona spacją lista zakresów",
"SIGNL4 Webhook URL": "Odnośnik webhooka SIGNL4",
"Lost connection to the socket server.": "Utracono połączenie do serwera.",
"Cannot connect to the socket server.": "Nie można połączyć z serwerem.",
"SIGNL4": "SIGNL4",
"less than": "mniej niż",
"Conditions": "Warunek",
"conditionAdd": "Dodaj warunek",
"conditionDelete": "Usuń warunek",
"conditionAddGroup": "Dodaj grupę",
"conditionDeleteGroup": "Usuń grupę",
"conditionValuePlaceholder": "Wartość",
"equals": "równa się",
"not equals": "nie jest równe",
"contains": "zawiera",
"not contains": "nie zawiera",
"starts with": "zaczyna się od",
"not starts with": "nie zaczyna się od",
"ends with": "kończy się",
"ignoredTLSError": "Błędy TLS/SSL zostały zignorowane",
"Debug": "Debuguj",
"Copy": "Kopiuj",
"CopyToClipboardSuccess": "Skopiowano!",
"firewalls": "zapory sieciowe",
"dns resolvers": "serwery rozwiązywania nazw domenowych",
"docker networks": "sieci docker",
"CurlDebugInfoOAuth2CCUnsupported": "Pełen ciąg poświadczeń klienta oauth nie jest obsługiwany w {curl}.{newline}Zdobądź bearer token i przekaż go przez opcję {oauth2_bearer}.",
"CurlDebugInfoProxiesUnsupported": "Obsługa proxy w powyższym poleceniu {curl} nie jest aktualnie zaimplementowana.",
"Message format": "Format wiadomości",
"Send rich messages": "Wysyłaj bogate wiadomości",
"Community String": "Ciąg community",
"Private Number": "Numer prywatny",
"groupOnesenderDesc": "Upewnij się, że GroupID jest poprawne. Aby wysłać wiadomość do grupy, np.: 628123456789-342345",
"privateOnesenderDesc": "Upewnij się, że numer telefonu jest poprawny. Aby wysłać wiadomość do prywatnego numeru, np.: 628123456789",
"Group ID": "ID grupy",
"wayToGetOnesenderUrlandToken": "Aby zdobyć odnośnik i token udaj się na stronę Onesender. Więcej informacji {0}",
"Add Remote Browser": "Dodaj zdalną przeglądarkę",
"New Group": "Nowa grupa",
"Authentication Method": "Metoda uwierzytelnienia",
"not ends with": "nie kończy się",
"greater than": "więcej niż",
"less than or equal to": "mniej lub równe",
"greater than or equal to": "więcej lub równe",
"record": "rekord",
"Notification Channel": "Kanał powiadomień",
"Sound": "Dźwięk",
"Alphanumerical string and hyphens only": "Tylko ciąg alfanumeryczny i dywiz (kreska)",
"Time Sensitive (iOS Only)": "Czasowo wrażliwe (tylko iOS)",
"From": "Od",
"Can be found on:": "Może być znalezione w: {0}",
"The phone number of the recipient in E.164 format.": "Numer telefonu odbiorcy w formacie E.164.",
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.": "Identyfikator wysyłającego wiadomość lub numer telefonu w formacie E.164 jeśli chcesz otrzymywać odpowiedzi.",
"jsonQueryDescription": "Przetwórz i wyciągnij specyficzne dane z odpowiedzi JSON serwera używając zapytania JSON lub użyj \"$\" dla nieprzetworzonej odpowiedzi, jeśli nie spodziewasz się formatu JSON. Wynik jest następnie porównywany, jako ciągi, do spodziewanej wartości. Po dokumetnację zobacz {0} lub użyj {1} aby poeksperymentować z zapytaniami.",
"Custom sound to override default notification sound": "Własny dzwięk nadpisujący domyślny dzwięk powiadomień",
"Arcade": "Arcade",
"Correct": "Correct",
"Fail": "Fail",
"Harp": "Harp",
"Reveal": "Reveal",
"Bubble": "Bubble",
"Doorbell": "Doorbell",
"Flute": "Flute",
"Money": "Money",
"Scifi": "Scifi",
"Clear": "Clear",
"Elevator": "Elevator",
"Guitar": "Guitar",
"Pop": "Pop"
"apiKeysDisabledMsg": "Klucze API są wyłączone, ponieważ wyłączone jest uwierzytelnianie."
}

View File

@@ -980,12 +980,5 @@
"Group Name": "Nome do Grupo",
"OAuth2: Client Credentials": "OAuth2: Client Credentials",
"Authentication Method": "Método de Autenticação",
"Authorization Header": "Header de Autorização",
"ignoredTLSError": "Erros TLS/SSL foram ignorados",
"Debug": "Depurar",
"Copy": "Copiar",
"CopyToClipboardError": "Não foi possível copiar para a área de transferência: {error}",
"CopyToClipboardSuccess": "Copiado!",
"firewalls": "firewalls",
"docker networks": "redes docker"
"Authorization Header": "Header de Autorização"
}

View File

@@ -971,20 +971,5 @@
"Select message type": "Выберите тип сообщения",
"Send to channel": "Отправить в канал",
"Refresh Interval": "Интервал обновления",
"ignoreTLSErrorGeneral": "Игнорировать ошибки TLS/SSL для подключения",
"CurlDebugInfoOAuth2CCUnsupported": "Полная поддержка потока клиентских учетных данных OAuth отсутствует в {curl}.{newline}Пожалуйста, получите токен доступа и передайте его через параметр {oauth2_bearer}.",
"now": "сейчас",
"time ago": "{0} назад",
"Refresh Interval Description": "Страница статуса будет полностью обновляться каждые {0} секунд",
"and": "и",
"e.g. {discordThreadID}": "например {discordThreadID}",
"ignoredTLSError": "Ошибки TLS/SSL были проигнорированы",
"Debug": "Отладка",
"Copy": "Скопировать",
"CopyToClipboardError": "Не удалось скопировать: {error}",
"CopyToClipboardSuccess": "Скопировано!",
"firewalls": "файрволы",
"dns resolvers": "dns резолверы",
"docker networks": "докер-сети",
"CurlDebugInfoProxiesUnsupported": "Поддержка прокси в верхней {curl} команде в настоящее время не реализована."
"ignoreTLSErrorGeneral": "Игнорировать ошибки TLS/SSL для подключения"
}

View File

@@ -94,7 +94,7 @@
"Password": "Lösenord",
"Remember me": "Kom ihåg mig",
"Login": "Logga in",
"No Monitors, please": "Inga övervakare",
"No Monitors, please": "Inga övervakare, tack",
"add one": "lägg till en",
"Notification Type": "Notifieringstyp",
"Email": "Epost",
@@ -957,38 +957,5 @@
"threemaSenderIdentityFormat": "8 tecken, startar oftast med *",
"threemaApiAuthenticationSecret": "Gateway-ID hemlighet",
"threemaBasicModeInfo": "Notera: Denna integration använder Threema Gateway i standardläge (server-baserad kryptering). Mer detaljer kan hittas {0}.",
"apiKeysDisabledMsg": "API-nycklar är inaktiverade för att autentisering är inaktiverad.",
"CopyToClipboardError": "Kunde inte kopiera till urklipp: {fel}",
"Message format": "Meddelandeformat",
"Condition": "Villkor",
"SNMP Version": "SNMP-version",
"No tags found.": "Inga taggar hittades.",
"now": "nu",
"time ago": "{0} sedan",
"-year": "-år",
"Json Query Expression": "Json-frågeutryck",
"and": "och",
"Recipient Type": "Mottagartyp",
"Private Number": "Privat nummer",
"New Group": "Ny grupp",
"Group Name": "Gruppnamn",
"OAuth2: Client Credentials": "OAuth2: Klientuppgifter",
"Authentication Method": "Autentiseringsmetod",
"Authorization Header": "Autentiseringshuvud",
"Client ID": "Klient ID",
"Client Secret": "Klienthemlighet",
"Go back to home page.": "Gå tillbaka till hemsidan.",
"ignoredTLSError": "TLS/SSL-fel har ignorerats",
"Debug": "Felsöka",
"Copy": "Kopiera",
"CopyToClipboardSuccess": "Kopierat!",
"firewalls": "brandväggar",
"dns resolvers": "dns-upplösare",
"docker networks": "docker-nätverk",
"cacheBusterParam": "Lägg till {0} parameter",
"Community String": "Gruppsträng",
"OID (Object Identifier)": "OID (Objektsidentifierare)",
"Please enter a valid OID.": "Ange ett giltigt OID.",
"Group ID": "GruppID",
"Add Remote Browser": "Lägg till fjärrbläddrare"
"apiKeysDisabledMsg": "API-nycklar är inaktiverade för att autentisering är inaktiverad."
}

View File

@@ -1050,43 +1050,5 @@
"less than": "daha küçük",
"greater than or equal to": "büyük veya eşit",
"record": "kayıt",
"jsonQueryDescription": "JSON sorgusunu kullanarak sunucunun JSON yanıtından belirli verileri ayrıştırın ve çıkarın. JSON beklemiyorsanız ham yanıt için \"$\" sembolünü kullanın. Sonuç daha sonra metin olarak beklenen değerle karşılaştırılır. Belgeler için {0}'a bakın ve sorgularla denemeler yapmak için {1}'i kullanın.",
"shrinkDatabaseDescriptionSqlite": "SQLite için {vacuum} veritabanını tetikle. {auto_vacuum} zaten etkin ancak bu, {vacuum} komutunun yaptığı gibi veritabanını birleştirmez veya tek tek veritabanı sayfalarını yeniden paketlemez.",
"Debug": "Hata ayıklama",
"Copy": "Kopyala",
"CopyToClipboardError": "Panoya kopyalanamadı: {hata}",
"CopyToClipboardSuccess": "Kopyalandı!",
"firewalls": "güvenlik duvarları",
"dns resolvers": "dns çözücüler",
"docker networks": "Docker ağları",
"CurlDebugInfoOAuth2CCUnsupported": "Tam Oauth istemci kimlik bilgisi akışı {curl}'de desteklenmiyor.{newline}Lütfen bir taşıyıcı belirteci alın ve bunu {oauth2_bearer} seçeneği aracılığıyla iletin.",
"CurlDebugInfoProxiesUnsupported": "Yukarıdaki {curl} komutunda proxy desteği şu anda uygulanmamıştır.",
"Message format": "Mesaj biçimi",
"Send rich messages": "Zengin mesajlar gönder",
"Notification Channel": "Bildirim Kanalı",
"Sound": "Ses",
"Alphanumerical string and hyphens only": "Yalnızca alfanümerik dize ve tireler",
"Arcade": "Çarşı",
"Correct": "Doğru",
"Fail": "Hata",
"Harp": "Harp",
"Reveal": "Ortaya çıkarmak",
"Bubble": "Kabarcık",
"Doorbell": "Kapı zili",
"Flute": "Flüt",
"Money": "Para",
"Scifi": "Bilimkurgu",
"Clear": "Temizlemek",
"Elevator": "Asansör",
"Guitar": "Gitar",
"Pop": "Pop",
"Custom sound to override default notification sound": "Varsayılan bildirim sesini geçersiz kılmak için özel ses",
"Time Sensitive (iOS Only)": "Zaman Duyarlı (Yalnızca iOS)",
"From": "Kimden",
"Can be found on:": "Şurada bulunabilir: {0}",
"The phone number of the recipient in E.164 format.": "Alıcının E.164 formatındaki telefon numarası.",
"ignoredTLSError": "TLS/SSL hataları göz ardı edildi",
"CurlDebugInfo": "Monitörü hata ayıklamak için, bunu kendi makinenizin terminaline veya uptime kuma'nın çalıştığı makinenin terminaline yapıştırabilir ve ne istediğinizi görebilirsiniz.{newiline}Lütfen {firewalls}, {dns_resolvers} veya {docker_networks} gibi ağ farklılıklarına dikkat edin.",
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Cihaz rahatsız etmeyin modunda olsa bile, zaman açısından hassas bildirimler anında iletilecek.",
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.": "Cevap alabilmek istiyorsanız E.164 formatında bir kısa mesaj gönderici kimliği veya bir telefon numarası."
"jsonQueryDescription": "JSON sorgusunu kullanarak sunucunun JSON yanıtından belirli verileri ayrıştırın ve çıkarın. JSON beklemiyorsanız ham yanıt için \"$\" sembolünü kullanın. Sonuç daha sonra metin olarak beklenen değerle karşılaştırılır. Belgeler için {0}'a bakın ve sorgularla denemeler yapmak için {1}'i kullanın."
}

View File

@@ -1 +0,0 @@
{}

View File

@@ -1056,43 +1056,5 @@
"greater than": "більше, ніж",
"less than or equal to": "менше або дорівнює",
"greater than or equal to": "більше або дорівнює",
"record": "запис",
"shrinkDatabaseDescriptionSqlite": "Запускає команду {vacuum} для бази даних SQLite. Команда {auto_vacuum} вже увімкнена, але вона не дефрагментує базу даних і не перепаковує окремі сторінки бази даних так, як це робить команда {vacuum}.",
"ignoredTLSError": "Помилки TLS/SSL проігноровано",
"Debug": "Налагодження",
"Copy": "Копіювати",
"CopyToClipboardError": "Не вдалося скопіювати в буфер обміну: {error}",
"CopyToClipboardSuccess": "Скопійовано!",
"firewalls": "фаєрволи",
"dns resolvers": "dns сервери",
"docker networks": "docker-мережі",
"CurlDebugInfoProxiesUnsupported": "Підтримка проксі у наведеній вище команді {curl} наразі не реалізована.",
"Message format": "Формат повідомлення",
"Send rich messages": "Надіслати розгорнуті повідомлення",
"Notification Channel": "Канал сповіщення",
"Sound": "Звук",
"Alphanumerical string and hyphens only": "Тільки алфавітно-цифровий рядок і дефіси",
"Arcade": "Аркада",
"Correct": "Вірно",
"Fail": "Невдача",
"Harp": "Арфа",
"Reveal": "Розкриття",
"Bubble": "Бульбашка",
"Doorbell": "Дзвінок",
"Flute": "Флейта",
"Money": "Гроші",
"Scifi": "Наукова фантастика",
"Clear": "Чисто",
"Elevator": "Ліфт",
"Guitar": "Гітара",
"Pop": "Поп",
"Custom sound to override default notification sound": "Користувацький звук для заміни звуку сповіщень за замовчуванням",
"Time Sensitive (iOS Only)": "Чутливий до часу (лише для iOS)",
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Сповіщення, що залежать від часу, будуть доставлені негайно, навіть якщо пристрій перебуває в режимі «Не турбувати».",
"From": "Від",
"The phone number of the recipient in E.164 format.": "Номер телефону одержувача у форматі E.164.",
"CurlDebugInfo": "Для налагодження монітора ви можете вставити цей файл у термінал вашої власної машини або у термінал машини, на якій запущено uptime kuma, і подивитися, що ви запитуєте.{newline}Зверніть увагу на мережеві відмінності, такі як {firewalls}, {dns_resolvers} або {docker_networks}.",
"CurlDebugInfoOAuth2CCUnsupported": "Повний потік облікових даних клієнта oauth не підтримується в {curl}.{newline}Потрібно отримати bearer-токен і передати його за допомогою опції {oauth2_bearer}.",
"Can be found on:": "Можна знайти на: {0}",
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.": "Або ідентифікатор відправника тексту, або номер телефону у форматі E.164, якщо ви хочете отримувати відповіді."
"record": "запис"
}

View File

@@ -1,5 +1,5 @@
{
"languageName": "简体中文",
"languageName": "English",
"checkEverySecond": "检测频率 {0} 秒",
"retryCheckEverySecond": "重试间隔 {0} 秒",
"resendEveryXTimes": "每 {0} 次失败则重复发送一次",
@@ -1052,43 +1052,5 @@
"less than or equal to": "不多于",
"greater than or equal to": "不少于",
"record": "记录",
"jsonQueryDescription": "使用 JSON 查询解析并提取服务器 JSON 响应中的特定数据,或者,如果不期望得到 JSON 响应,则可使用 \"$\" 获取原始响应。然后将结果转为字符串并与期望值进行字符串比较。有关更多文档,请参阅 {0},亦可使用 {1} 来尝试查询。",
"shrinkDatabaseDescriptionSqlite": "触发 SQLite 数据库的 {vacuum} 命令。{auto_vacuum} 已经启用,但它不会像 {vacuum} 命令那样对数据库进行碎片整理,也不会重新打包各个数据库页面。",
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.": "如需可被回复,请输入发送者 ID 或 E.164 格式的手机号码。",
"Copy": "复制",
"Debug": "Debug",
"CopyToClipboardSuccess": "已复制!",
"CopyToClipboardError": "无法复制到剪贴板:{error}",
"CurlDebugInfoProxiesUnsupported": "上述 {curl} 命令中的代理支持目前尚未实现。",
"docker networks": "Docker 网络",
"dns resolvers": "DNS 解析器",
"firewalls": "防火墙",
"Message format": "消息格式",
"Send rich messages": "发送富文本消息",
"Sound": "声音",
"Notification Channel": "通知频道",
"Bubble": "Bubble气泡",
"Reveal": "Reveal揭示",
"Harp": "Harp竖琴",
"Correct": "Correct成功音",
"Fail": "Fail失败音",
"Arcade": "Arcade拱廊",
"Alphanumerical string and hyphens only": "仅限字母、数字和连字符(-",
"Custom sound to override default notification sound": "自定义声音,用以覆盖默认通知声音",
"Pop": "Pop流行音乐",
"Guitar": "Guitar吉他",
"Elevator": "Elevator电梯",
"Clear": "Clear清除声",
"Scifi": "Scifi科幻",
"Flute": "Flute长笛",
"Doorbell": "Doorbell门铃",
"The phone number of the recipient in E.164 format.": "收件人的 E.164 格式电话号码。",
"Can be found on:": "可在此找到:{0}",
"From": "发件人",
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "即使设备处于专注模式,即时通知也会立即发送。",
"Time Sensitive (iOS Only)": "即时通知(仅 iOS 可用)",
"Money": "Money",
"CurlDebugInfoOAuth2CCUnsupported": "{curl} 不支持完整的 oauth 客户端凭证流。{newline}请获取承载令牌bearer token并通过 {oauth2_bearer} 选项传递。",
"CurlDebugInfo": "要调试监控项,您可以将该命令粘贴到您自己的或 uptime kuma 正在运行的机器的命令行终端中,并查看结果。{newiline}请注意网络差异,例如 {firewalls}、{dns_resolvers} 或 {docker_networks}。",
"ignoredTLSError": "TLS/SSL 错误已被忽略"
"jsonQueryDescription": "使用 JSON 查询解析并提取服务器 JSON 响应中的特定数据,或者,如果不期望得到 JSON 响应,则可使用 \"$\" 获取原始响应。然后将结果转为字符串并与期望值进行字符串比较。有关更多文档,请参阅 {0},亦可使用 {1} 来尝试查询。"
}

View File

@@ -64,9 +64,6 @@
<option value="mqtt">
MQTT
</option>
<option value="rabbitmq">
RabbitMQ
</option>
<option value="kafka-producer">
Kafka Producer
</option>
@@ -93,13 +90,6 @@
</option>
</optgroup>
</select>
<i18n-t v-if="monitor.type === 'rabbitmq'" keypath="rabbitmqHelpText" tag="div" class="form-text">
<template #rabitmq_documentation>
<a href="https://www.rabbitmq.com/management" target="_blank" rel="noopener noreferrer">
RabbitMQ documentation
</a>
</template>
</i18n-t>
</div>
<div v-if="monitor.type === 'tailscale-ping'" class="alert alert-warning" role="alert">
@@ -243,43 +233,6 @@
</div>
</template>
<template v-if="monitor.type === 'rabbitmq'">
<!-- RabbitMQ Nodes List -->
<div class="my-3">
<label for="rabbitmqNodes" class="form-label">{{ $t("RabbitMQ Nodes") }}</label>
<VueMultiselect
id="rabbitmqNodes"
v-model="monitor.rabbitmqNodes"
:required="true"
:multiple="true"
:options="[]"
:placeholder="$t('Enter the list of nodes')"
:tag-placeholder="$t('Press Enter to add node')"
:max-height="500"
:taggable="true"
:show-no-options="false"
:close-on-select="false"
:clear-on-select="false"
:preserve-search="false"
:preselect-first="false"
@tag="addRabbitmqNode"
></VueMultiselect>
<div class="form-text">
{{ $t("rabbitmqNodesDescription", ["https://node1.rabbitmq.com:15672"]) }}
</div>
</div>
<div class="my-3">
<label for="rabbitmqUsername" class="form-label">RabbitMQ {{ $t("RabbitMQ Username") }}</label>
<input id="rabbitmqUsername" v-model="monitor.rabbitmqUsername" type="text" required class="form-control">
</div>
<div class="my-3">
<label for="rabbitmqPassword" class="form-label">{{ $t("RabbitMQ Password") }}</label>
<HiddenInput id="rabbitmqPassword" v-model="monitor.rabbitmqPassword" autocomplete="false" required="true"></HiddenInput>
</div>
</template>
<!-- Hostname -->
<!-- TCP Port / Ping / DNS / Steam / MQTT / Radius / Tailscale Ping / SNMP only -->
<div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' || monitor.type === 'steam' || monitor.type === 'gamedig' || monitor.type === 'mqtt' || monitor.type === 'radius' || monitor.type === 'tailscale-ping' || monitor.type === 'snmp'" class="my-3">
@@ -596,7 +549,7 @@
</div>
<!-- Timeout: HTTP / Keyword / SNMP only -->
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query' || monitor.type === 'snmp' || monitor.type === 'rabbitmq'" class="my-3">
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query' || monitor.type === 'snmp'" class="my-3">
<label for="timeout" class="form-label">{{ $t("Request Timeout") }} ({{ $t("timeoutAfter", [ monitor.timeout || clampTimeout(monitor.interval) ]) }})</label>
<input id="timeout" v-model="monitor.timeout" type="number" class="form-control" required min="0" step="0.1">
</div>
@@ -1169,9 +1122,6 @@ const monitorDefaults = {
kafkaProducerAllowAutoTopicCreation: false,
gamedigGivenPortOnly: true,
remote_browser: null,
rabbitmqNodes: [],
rabbitmqUsername: "",
rabbitmqPassword: "",
conditions: []
};
@@ -1759,10 +1709,6 @@ message HealthCheckResponse {
this.monitor.kafkaProducerBrokers.push(newBroker);
},
addRabbitmqNode(newNode) {
this.monitor.rabbitmqNodes.push(newNode);
},
/**
* Validate form input
* @returns {boolean} Is the form input valid?
@@ -1790,17 +1736,6 @@ message HealthCheckResponse {
return false;
}
}
if (this.monitor.type === "rabbitmq") {
if (this.monitor.rabbitmqNodes.length === 0) {
toast.error(this.$t("rabbitmqNodesRequired"));
return false;
}
if (!this.monitor.rabbitmqNodes.every(node => node.startsWith("http://") || node.startsWith("https://"))) {
toast.error(this.$t("rabbitmqNodesInvalid"));
return false;
}
}
return true;
},

View File

@@ -8,17 +8,34 @@
// Backend uses the compiled file util.js
// Frontend uses util.ts
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.sleep = exports.flipStatus = exports.badgeConstants = exports.CONSOLE_STYLE_BgGray = exports.CONSOLE_STYLE_BgWhite = exports.CONSOLE_STYLE_BgCyan = exports.CONSOLE_STYLE_BgMagenta = exports.CONSOLE_STYLE_BgBlue = exports.CONSOLE_STYLE_BgYellow = exports.CONSOLE_STYLE_BgGreen = exports.CONSOLE_STYLE_BgRed = exports.CONSOLE_STYLE_BgBlack = exports.CONSOLE_STYLE_FgPink = exports.CONSOLE_STYLE_FgBrown = exports.CONSOLE_STYLE_FgViolet = exports.CONSOLE_STYLE_FgLightBlue = exports.CONSOLE_STYLE_FgLightGreen = exports.CONSOLE_STYLE_FgOrange = exports.CONSOLE_STYLE_FgGray = exports.CONSOLE_STYLE_FgWhite = exports.CONSOLE_STYLE_FgCyan = exports.CONSOLE_STYLE_FgMagenta = exports.CONSOLE_STYLE_FgBlue = exports.CONSOLE_STYLE_FgYellow = exports.CONSOLE_STYLE_FgGreen = exports.CONSOLE_STYLE_FgRed = exports.CONSOLE_STYLE_FgBlack = exports.CONSOLE_STYLE_Hidden = exports.CONSOLE_STYLE_Reverse = exports.CONSOLE_STYLE_Blink = exports.CONSOLE_STYLE_Underscore = exports.CONSOLE_STYLE_Dim = exports.CONSOLE_STYLE_Bright = exports.CONSOLE_STYLE_Reset = exports.MIN_INTERVAL_SECOND = exports.MAX_INTERVAL_SECOND = exports.SQL_DATETIME_FORMAT_WITHOUT_SECOND = exports.SQL_DATETIME_FORMAT = exports.SQL_DATE_FORMAT = exports.STATUS_PAGE_MAINTENANCE = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.MAINTENANCE = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isNode = exports.isDev = void 0;
exports.evaluateJsonQuery = exports.intHash = exports.localToUTC = exports.utcToLocal = exports.utcToISODateTime = exports.isoToUTCDateTime = exports.parseTimeFromTimeObject = exports.parseTimeObject = exports.getMaintenanceRelativeURL = exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.log = exports.debug = exports.ucfirst = void 0;
exports.intHash = exports.localToUTC = exports.utcToLocal = exports.utcToISODateTime = exports.isoToUTCDateTime = exports.parseTimeFromTimeObject = exports.parseTimeObject = exports.getMaintenanceRelativeURL = exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.log = exports.debug = exports.ucfirst = void 0;
exports.evaluateJsonQuery = exports.intHash = exports.localToUTC = exports.utcToLocal = exports.utcToISODateTime = exports.isoToUTCDateTime = exports.parseTimeFromTimeObject = exports.parseTimeObject = exports.getMaintenanceRelativeURL = exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.log = exports.ucfirst = void 0;
const dayjs_1 = __importDefault(require("dayjs"));
const dayjs = require("dayjs");
const jsonata = require("jsonata");
const jsonata = __importStar(require("jsonata"));
exports.isDev = process.env.NODE_ENV === "development";
exports.isNode = typeof process !== "undefined" && ((_a = process === null || process === void 0 ? void 0 : process.versions) === null || _a === void 0 ? void 0 : _a.node);
exports.appName = "Uptime Kuma";
@@ -125,10 +142,6 @@ function ucfirst(str) {
return firstLetter.toUpperCase() + str.substr(1);
}
exports.ucfirst = ucfirst;
function debug(msg) {
exports.log.log("", msg, "debug");
}
exports.debug = debug;
class Logger {
constructor() {
this.hideLog = {
@@ -156,8 +169,6 @@ class Logger {
if (this.hideLog[level] && this.hideLog[level].includes(module.toLowerCase())) {
return;
}
module = module.toUpperCase();
level = level.toUpperCase();
let now;
if (dayjs_1.default.tz) {
now = dayjs_1.default.tz(new Date()).format();
@@ -167,10 +178,20 @@ class Logger {
}
const levelColor = consoleLevelColors[level];
const moduleColor = consoleModuleColors[intHash(module, consoleModuleColors.length)];
let timePart;
let modulePart;
let levelPart;
let msgPart;
let timePart = now;
let modulePart = module;
let levelPart = level;
let msgPart = msg;
if (process.env.UPTIME_KUMA_LOG_FORMAT === "json") {
console.log(JSON.stringify({
time: timePart,
module: modulePart,
level: levelPart,
msg: typeof msg === "string" ? msg : JSON.stringify(msg),
}));
return;
}
module = module.toUpperCase();
if (exports.isNode) {
switch (level) {
case "DEBUG":
@@ -187,28 +208,17 @@ class Logger {
if (typeof msg === "string") {
msgPart = exports.CONSOLE_STYLE_FgRed + msg + exports.CONSOLE_STYLE_Reset;
}
else {
msgPart = msg;
}
break;
case "DEBUG":
if (typeof msg === "string") {
msgPart = exports.CONSOLE_STYLE_FgGray + msg + exports.CONSOLE_STYLE_Reset;
}
else {
msgPart = msg;
}
break;
default:
msgPart = msg;
break;
}
}
else {
timePart = now;
modulePart = `[${module}]`;
levelPart = `${level}:`;
msgPart = msg;
}
switch (level) {
case "ERROR":
@@ -231,23 +241,23 @@ class Logger {
}
}
info(module, msg) {
this.log(module, msg, "info");
this.log(module, msg, "INFO");
}
warn(module, msg) {
this.log(module, msg, "warn");
this.log(module, msg, "WARN");
}
error(module, msg) {
this.log(module, msg, "error");
this.log(module, msg, "ERROR");
}
debug(module, msg) {
this.log(module, msg, "debug");
this.log(module, msg, "DEBUG");
}
exception(module, exception, msg) {
let finalMessage = exception;
if (msg) {
finalMessage = `${msg}: ${exception}`;
}
this.log(module, finalMessage, "error");
this.log(module, finalMessage, "ERROR");
}
}
exports.log = new Logger();
@@ -458,4 +468,4 @@ async function evaluateJsonQuery(data, jsonPath, jsonPathOperator, expectedValue
throw new Error(`Error evaluating JSON query: ${err.message}. Response from server was: ${response}`);
}
}
exports.evaluateJsonQuery = evaluateJsonQuery;
exports.evaluateJsonQuery = evaluateJsonQuery;

View File

@@ -120,11 +120,7 @@ export const badgeConstants = {
defaultCertExpireDownDays: "7"
};
/**
* Flip the status of s between UP and DOWN if this is possible
* @param s {number} status
* @returns {number} flipped status
*/
/** Flip the status of s */
export function flipStatus(s: number) {
if (s === UP) {
return DOWN;
@@ -160,15 +156,6 @@ export function ucfirst(str: string) {
return firstLetter.toUpperCase() + str.substr(1);
}
/**
* @deprecated Use log.debug (https://github.com/louislam/uptime-kuma/pull/910)
* @param msg Message to write
* @returns {void}
*/
export function debug(msg: unknown) {
log.log("", msg, "debug");
}
class Logger {
/**
@@ -210,12 +197,13 @@ class Logger {
/**
* Write a message to the log
* @private
* @param module The module the log comes from
* @param msg Message to write
* @param level Log level. One of INFO, WARN, ERROR, DEBUG or can be customized.
* @param level {"INFO"|"WARN"|"ERROR"|"DEBUG"} Log level
* @returns {void}
*/
log(module: string, msg: any, level: string) {
log(module: string, msg: unknown, level: "INFO"|"WARN"|"ERROR"|"DEBUG"): void {
if (level === "DEBUG" && !isDev) {
return;
}
@@ -224,9 +212,6 @@ class Logger {
return;
}
module = module.toUpperCase();
level = level.toUpperCase();
let now;
if (dayjs.tz) {
now = dayjs.tz(new Date()).format();
@@ -237,10 +222,23 @@ class Logger {
const levelColor = consoleLevelColors[level];
const moduleColor = consoleModuleColors[intHash(module, consoleModuleColors.length)];
let timePart: string;
let modulePart: string;
let levelPart: string;
let msgPart: string;
let timePart: string = now;
let modulePart: string = module;
let levelPart: string = level;
let msgPart: unknown = msg;
if (process.env.UPTIME_KUMA_LOG_FORMAT === "json") {
console.log(JSON.stringify({
time: timePart,
module: modulePart,
level: levelPart,
msg: typeof msg === "string" ? msg : JSON.stringify(msg),
}));
return;
}
// Console rendering:
module = module.toUpperCase();
if (isNode) {
// Add console colors
@@ -261,27 +259,18 @@ class Logger {
case "ERROR":
if (typeof msg === "string") {
msgPart = CONSOLE_STYLE_FgRed + msg + CONSOLE_STYLE_Reset;
} else {
msgPart = msg;
}
break;
case "DEBUG":
if (typeof msg === "string") {
msgPart = CONSOLE_STYLE_FgGray + msg + CONSOLE_STYLE_Reset;
} else {
msgPart = msg;
}
break;
default:
msgPart = msg;
break;
}
} else {
// No console colors
timePart = now;
modulePart = `[${module}]`;
levelPart = `${level}:`;
msgPart = msg;
}
// Write to console
@@ -312,8 +301,8 @@ class Logger {
* @param msg Message to write
* @returns {void}
*/
info(module: string, msg: unknown) {
this.log(module, msg, "info");
info(module: string, msg: string): void {
this.log(module, msg, "INFO");
}
/**
@@ -322,8 +311,8 @@ class Logger {
* @param msg Message to write
* @returns {void}
*/
warn(module: string, msg: unknown) {
this.log(module, msg, "warn");
warn(module: string, msg: string): void {
this.log(module, msg, "WARN");
}
/**
@@ -332,8 +321,8 @@ class Logger {
* @param msg Message to write
* @returns {void}
*/
error(module: string, msg: unknown) {
this.log(module, msg, "error");
error(module: string, msg: string): void {
this.log(module, msg, "ERROR");
}
/**
@@ -342,8 +331,8 @@ class Logger {
* @param msg Message to write
* @returns {void}
*/
debug(module: string, msg: unknown) {
this.log(module, msg, "debug");
debug(module: string, msg: string): void {
this.log(module, msg, "DEBUG");
}
/**
@@ -360,7 +349,7 @@ class Logger {
finalMessage = `${msg}: ${exception}`;
}
this.log(module, finalMessage, "error");
this.log(module, finalMessage, "ERROR");
}
}
@@ -404,7 +393,7 @@ export class TimeLogger {
* @param name Name of monitor
* @returns {void}
*/
print(name: string) {
print(name: string): void {
if (isDev && process.env.TIMELOGGER === "1") {
console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms");
}

View File

@@ -1,102 +0,0 @@
const { describe, test } = require("node:test");
const assert = require("node:assert");
const { HiveMQContainer } = require("@testcontainers/hivemq");
const mqtt = require("mqtt");
const { MqttMonitorType } = require("../../server/monitor-types/mqtt");
const { UP, PENDING } = require("../../src/util");
/**
* Runs an MQTT test with the
* @param {string} mqttSuccessMessage the message that the monitor expects
* @param {null|"keyword"|"json-query"} mqttCheckType the type of check we perform
* @param {string} receivedMessage what message is recieved from the mqtt channel
* @returns {Promise<Heartbeat>} the heartbeat produced by the check
*/
async function testMqtt(mqttSuccessMessage, mqttCheckType, receivedMessage) {
const hiveMQContainer = await new HiveMQContainer().start();
const connectionString = hiveMQContainer.getConnectionString();
const mqttMonitorType = new MqttMonitorType();
const monitor = {
jsonPath: "firstProp", // always return firstProp for the json-query monitor
hostname: connectionString.split(":", 2).join(":"),
mqttTopic: "test",
port: connectionString.split(":")[2],
mqttUsername: null,
mqttPassword: null,
interval: 20, // controls the timeout
mqttSuccessMessage: mqttSuccessMessage, // for keywords
expectedValue: mqttSuccessMessage, // for json-query
mqttCheckType: mqttCheckType,
};
const heartbeat = {
msg: "",
status: PENDING,
};
const testMqttClient = mqtt.connect(hiveMQContainer.getConnectionString());
testMqttClient.on("connect", () => {
testMqttClient.subscribe("test", (error) => {
if (!error) {
testMqttClient.publish("test", receivedMessage);
}
});
});
try {
await mqttMonitorType.check(monitor, heartbeat, {});
} finally {
testMqttClient.end();
hiveMQContainer.stop();
}
return heartbeat;
}
describe("MqttMonitorType", {
concurrency: true,
skip: !!process.env.CI && (process.platform !== "linux" || process.arch !== "x64")
}, () => {
test("valid keywords (type=default)", async () => {
const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-");
assert.strictEqual(heartbeat.status, UP);
assert.strictEqual(heartbeat.msg, "Topic: test; Message: -> KEYWORD <-");
});
test("valid keywords (type=keyword)", async () => {
const heartbeat = await testMqtt("KEYWORD", "keyword", "-> KEYWORD <-");
assert.strictEqual(heartbeat.status, UP);
assert.strictEqual(heartbeat.msg, "Topic: test; Message: -> KEYWORD <-");
});
test("invalid keywords (type=default)", async () => {
await assert.rejects(
testMqtt("NOT_PRESENT", null, "-> KEYWORD <-"),
new Error("Message Mismatch - Topic: test; Message: -> KEYWORD <-"),
);
});
test("invalid keyword (type=keyword)", async () => {
await assert.rejects(
testMqtt("NOT_PRESENT", "keyword", "-> KEYWORD <-"),
new Error("Message Mismatch - Topic: test; Message: -> KEYWORD <-"),
);
});
test("valid json-query", async () => {
// works because the monitors' jsonPath is hard-coded to "firstProp"
const heartbeat = await testMqtt("present", "json-query", "{\"firstProp\":\"present\"}");
assert.strictEqual(heartbeat.status, UP);
assert.strictEqual(heartbeat.msg, "Message received, expected value is found");
});
test("invalid (because query fails) json-query", async () => {
// works because the monitors' jsonPath is hard-coded to "firstProp"
await assert.rejects(
testMqtt("[not_relevant]", "json-query", "{}"),
new Error("Message received but value is not equal to expected value, value was: [undefined]"),
);
});
test("invalid (because successMessage fails) json-query", async () => {
// works because the monitors' jsonPath is hard-coded to "firstProp"
await assert.rejects(
testMqtt("[wrong_success_messsage]", "json-query", "{\"firstProp\":\"present\"}"),
new Error("Message received but value is not equal to expected value, value was: [present]")
);
});
});

View File

@@ -1,53 +0,0 @@
const { describe, test } = require("node:test");
const assert = require("node:assert");
const { RabbitMQContainer } = require("@testcontainers/rabbitmq");
const { RabbitMqMonitorType } = require("../../server/monitor-types/rabbitmq");
const { UP, DOWN, PENDING } = require("../../src/util");
describe("RabbitMQ Single Node", {
skip: !!process.env.CI && (process.platform !== "linux" || process.arch !== "x64"),
}, () => {
test("RabbitMQ is running", async () => {
// The default timeout of 30 seconds might not be enough for the container to start
const rabbitMQContainer = await new RabbitMQContainer().withStartupTimeout(60000).start();
const rabbitMQMonitor = new RabbitMqMonitorType();
const connectionString = `http://${rabbitMQContainer.getHost()}:${rabbitMQContainer.getMappedPort(15672)}`;
const monitor = {
rabbitmqNodes: JSON.stringify([ connectionString ]),
rabbitmqUsername: "guest",
rabbitmqPassword: "guest",
};
const heartbeat = {
msg: "",
status: PENDING,
};
try {
await rabbitMQMonitor.check(monitor, heartbeat, {});
assert.strictEqual(heartbeat.status, UP);
assert.strictEqual(heartbeat.msg, "OK");
} finally {
rabbitMQContainer.stop();
}
});
test("RabbitMQ is not running", async () => {
const rabbitMQMonitor = new RabbitMqMonitorType();
const monitor = {
rabbitmqNodes: JSON.stringify([ "http://localhost:15672" ]),
rabbitmqUsername: "rabbitmqUser",
rabbitmqPassword: "rabbitmqPass",
};
const heartbeat = {
msg: "",
status: PENDING,
};
await rabbitMQMonitor.check(monitor, heartbeat, {});
assert.strictEqual(heartbeat.status, DOWN);
});
});