mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-11-23 23:04:04 +00:00
Merge branch 'master' into unified-logging-infrastructure
This commit is contained in:
commit
75c4c35da2
138 changed files with 10133 additions and 3031 deletions
|
@ -1,28 +0,0 @@
|
|||
# Codespaces
|
||||
|
||||
You can modifiy Uptime Kuma in your browser without setting up a local development.
|
||||
|
||||
![image](https://github.com/louislam/uptime-kuma/assets/1336778/31d9f06d-dd0b-4405-8e0d-a96586ee4595)
|
||||
|
||||
1. Click `Code` -> `Create codespace on master`
|
||||
2. Wait a few minutes until you see there are two exposed ports
|
||||
3. Go to the `3000` url, see if it is working
|
||||
|
||||
![image](https://github.com/louislam/uptime-kuma/assets/1336778/909b2eb4-4c5e-44e4-ac26-6d20ed856e7f)
|
||||
|
||||
## Frontend
|
||||
|
||||
Since the frontend is using [Vite.js](https://vitejs.dev/), all changes in this area will be hot-reloaded.
|
||||
You don't need to restart the frontend, unless you try to add a new frontend dependency.
|
||||
|
||||
## Backend
|
||||
|
||||
The backend does not automatically hot-reload.
|
||||
You will need to restart the backend after changing something using these steps:
|
||||
|
||||
1. Click `Terminal`
|
||||
2. Click `Codespaces: server-dev` in the right panel
|
||||
3. Press `Ctrl + C` to stop the server
|
||||
4. Press `Up` to run `npm run start-server-dev`
|
||||
|
||||
![image](https://github.com/louislam/uptime-kuma/assets/1336778/e0c0a350-fe46-4588-9f37-e053c85834d1)
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"image": "mcr.microsoft.com/devcontainers/javascript-node:dev-18-bookworm",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/github-cli:1": {}
|
||||
},
|
||||
"updateContentCommand": "npm ci",
|
||||
"postCreateCommand": "",
|
||||
"postAttachCommand": {
|
||||
"frontend-dev": "npm run start-frontend-devcontainer",
|
||||
"server-dev": "npm run start-server-dev",
|
||||
"open-port": "gh codespace ports visibility 3001:public -c $CODESPACE_NAME"
|
||||
},
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"GitHub.copilot-chat"
|
||||
]
|
||||
}
|
||||
},
|
||||
"forwardPorts": [3000, 3001]
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
/.idea
|
||||
/node_modules
|
||||
/data*
|
||||
/cypress
|
||||
/out
|
||||
/test
|
||||
/kubernetes
|
||||
|
@ -18,7 +17,6 @@ README.md
|
|||
.vscode
|
||||
.eslint*
|
||||
.stylelint*
|
||||
/.devcontainer
|
||||
/.github
|
||||
yarn.lock
|
||||
app.json
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
module.exports = {
|
||||
ignorePatterns: [
|
||||
"test/*.js",
|
||||
"test/cypress",
|
||||
"server/modules/apicache/*",
|
||||
"server/modules/*",
|
||||
"src/util.js"
|
||||
],
|
||||
root: true,
|
||||
|
|
4
.github/workflows/stale-bot.yml
vendored
4
.github/workflows/stale-bot.yml
vendored
|
@ -9,7 +9,7 @@ jobs:
|
|||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v8
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
stale-issue-message: |-
|
||||
We are clearing up our old `help`-issues and your issue has been open for 60 days with no activity.
|
||||
|
@ -21,7 +21,7 @@ jobs:
|
|||
exempt-issue-labels: 'News,Medium,High,discussion,bug,doc,feature-request'
|
||||
exempt-issue-assignees: 'louislam'
|
||||
operations-per-run: 200
|
||||
- uses: actions/stale@v8
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
stale-issue-message: |-
|
||||
This issue was marked as `cannot-reproduce` by a maintainer.
|
||||
|
|
|
@ -127,7 +127,7 @@ Different guidelines exist for different types of pull requests (PRs):
|
|||
- `server/monitor-types/MONITORING_TYPE.js` is the core of each monitor.
|
||||
the `async check(...)`-function should:
|
||||
- throw an error for each fault that is detected with an actionable error message
|
||||
- in the happy-path, you should set `heartbeat.msg` to a successfull message and set `heartbeat.status = UP`
|
||||
- in the happy-path, you should set `heartbeat.msg` to a successful message and set `heartbeat.status = UP`
|
||||
- `server/uptime-kuma-server.js` is where the monitoring backend needs to be registered.
|
||||
*If you have an idea how we can skip this step, we would love to hear about it ^^*
|
||||
- `src/pages/EditMonitor.vue` is the shared frontend users interact with.
|
||||
|
@ -236,12 +236,6 @@ The goal is to make the Uptime Kuma installation as easy as installing a mobile
|
|||
- IDE that supports [`ESLint`](https://eslint.org/) and EditorConfig (I am using [`IntelliJ IDEA`](https://www.jetbrains.com/idea/))
|
||||
- A SQLite GUI tool (f.ex. [`SQLite Expert Personal`](https://www.sqliteexpert.com/download.html) or [`DBeaver Community`](https://dbeaver.io/download/))
|
||||
|
||||
### GitHub Codespaces
|
||||
|
||||
If you don't want to setup an local environment, you can now develop on GitHub Codespaces, read more:
|
||||
|
||||
https://github.com/louislam/uptime-kuma/tree/master/.devcontainer
|
||||
|
||||
## Git Branches
|
||||
|
||||
- `master`: 2.X.X development. If you want to add a new feature, your pull request should base on this.
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
const { defineConfig } = require("cypress");
|
||||
|
||||
module.exports = defineConfig({
|
||||
projectId: "vyjuem",
|
||||
e2e: {
|
||||
experimentalStudio: true,
|
||||
setupNodeEvents(on, config) {
|
||||
|
||||
},
|
||||
fixturesFolder: "test/cypress/fixtures",
|
||||
screenshotsFolder: "test/cypress/screenshots",
|
||||
videosFolder: "test/cypress/videos",
|
||||
downloadsFolder: "test/cypress/downloads",
|
||||
supportFile: "test/cypress/support/e2e.js",
|
||||
baseUrl: "http://localhost:3002",
|
||||
defaultCommandTimeout: 10000,
|
||||
pageLoadTimeout: 60000,
|
||||
viewportWidth: 1920,
|
||||
viewportHeight: 1080,
|
||||
specPattern: [
|
||||
"test/cypress/e2e/setup.cy.js",
|
||||
"test/cypress/e2e/**/*.js"
|
||||
],
|
||||
},
|
||||
env: {
|
||||
baseUrl: "http://localhost:3002",
|
||||
},
|
||||
});
|
|
@ -1,10 +0,0 @@
|
|||
const { defineConfig } = require("cypress");
|
||||
|
||||
module.exports = defineConfig({
|
||||
e2e: {
|
||||
supportFile: false,
|
||||
specPattern: [
|
||||
"test/cypress/unit/**/*.js"
|
||||
],
|
||||
}
|
||||
});
|
|
@ -1,11 +1,11 @@
|
|||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
const port = 30001;
|
||||
const url = `http://localhost:${port}`;
|
||||
export const url = `http://localhost:${port}`;
|
||||
|
||||
export default defineConfig({
|
||||
// Look for test files in the "tests" directory, relative to this configuration file.
|
||||
testDir: "../test/e2e",
|
||||
testDir: "../test/e2e/specs",
|
||||
outputDir: "../private/playwright-test-results",
|
||||
fullyParallel: false,
|
||||
locale: "en-US",
|
||||
|
@ -40,9 +40,15 @@ export default defineConfig({
|
|||
// Configure projects for major browsers.
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
name: "run-once setup",
|
||||
testMatch: /setup-process\.once\.js/,
|
||||
use: { ...devices["Desktop Chrome"] },
|
||||
},
|
||||
{
|
||||
name: "specs",
|
||||
use: { ...devices["Desktop Chrome"] },
|
||||
dependencies: [ "run-once setup" ],
|
||||
},
|
||||
/*
|
||||
{
|
||||
name: "firefox",
|
||||
|
@ -52,7 +58,7 @@ export default defineConfig({
|
|||
|
||||
// Run your local dev server before starting the tests.
|
||||
webServer: {
|
||||
command: `node extra/remove-playwright-test-data.js && node server/server.js --port=${port} --data-dir=./data/playwright-test`,
|
||||
command: `node extra/remove-playwright-test-data.js && cross-env NODE_ENV=development node server/server.js --port=${port} --data-dir=./data/playwright-test`,
|
||||
url,
|
||||
reuseExistingServer: false,
|
||||
cwd: "../",
|
||||
|
|
16
db/knex_migrations/2024-04-26-0000-snmp-monitor.js
Normal file
16
db/knex_migrations/2024-04-26-0000-snmp-monitor.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
exports.up = function (knex) {
|
||||
return knex.schema
|
||||
.alterTable("monitor", function (table) {
|
||||
table.string("snmp_oid").defaultTo(null);
|
||||
table.enum("snmp_version", [ "1", "2c", "3" ]).defaultTo("2c");
|
||||
table.string("json_path_operator").defaultTo(null);
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function (knex) {
|
||||
return knex.schema.alterTable("monitor", function (table) {
|
||||
table.dropColumn("snmp_oid");
|
||||
table.dropColumn("snmp_version");
|
||||
table.dropColumn("json_path_operator");
|
||||
});
|
||||
};
|
13
db/knex_migrations/2024-08-24-000-add-cache-bust.js
Normal file
13
db/knex_migrations/2024-08-24-000-add-cache-bust.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
exports.up = function (knex) {
|
||||
return knex.schema
|
||||
.alterTable("monitor", function (table) {
|
||||
table.boolean("cache_bust").notNullable().defaultTo(false);
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function (knex) {
|
||||
return knex.schema
|
||||
.alterTable("monitor", function (table) {
|
||||
table.dropColumn("cache_bust");
|
||||
});
|
||||
};
|
12
db/knex_migrations/2024-08-24-0000-conditions.js
Normal file
12
db/knex_migrations/2024-08-24-0000-conditions.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
exports.up = function (knex) {
|
||||
return knex.schema
|
||||
.alterTable("monitor", function (table) {
|
||||
table.text("conditions").notNullable().defaultTo("[]");
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function (knex) {
|
||||
return knex.schema.alterTable("monitor", function (table) {
|
||||
table.dropColumn("conditions");
|
||||
});
|
||||
};
|
|
@ -3,7 +3,6 @@ FROM node:20-bookworm-slim AS base2-slim
|
|||
ARG TARGETPLATFORM
|
||||
|
||||
# Specify --no-install-recommends to skip unused dependencies, make the base much smaller!
|
||||
# apprise = for notifications (From testing repo)
|
||||
# sqlite3 = for debugging
|
||||
# iputils-ping = for ping
|
||||
# util-linux = for setpriv (Should be dropped in 2.0.0?)
|
||||
|
@ -12,10 +11,10 @@ ARG TARGETPLATFORM
|
|||
# ca-certificates = keep the cert up-to-date
|
||||
# sudo = for start service nscd with non-root user
|
||||
# nscd = for better DNS caching
|
||||
RUN echo "deb http://deb.debian.org/debian testing main" >> /etc/apt/sources.list && \
|
||||
apt update && \
|
||||
apt --yes --no-install-recommends -t testing install apprise sqlite3 ca-certificates && \
|
||||
apt --yes --no-install-recommends -t stable install \
|
||||
RUN apt update && \
|
||||
apt --yes --no-install-recommends install \
|
||||
sqlite3 \
|
||||
ca-certificates \
|
||||
iputils-ping \
|
||||
util-linux \
|
||||
dumb-init \
|
||||
|
@ -25,6 +24,15 @@ RUN echo "deb http://deb.debian.org/debian testing main" >> /etc/apt/sources.lis
|
|||
rm -rf /var/lib/apt/lists/* && \
|
||||
apt --yes autoremove
|
||||
|
||||
# apprise = for notifications (Install from the deb package, as the stable one is too old) (workaround for #4867)
|
||||
# Switching to testing repo is no longer working, as the testing repo is not bookworm anymore.
|
||||
# python3-paho-mqtt (#4859)
|
||||
RUN curl http://ftp.debian.org/debian/pool/main/a/apprise/apprise_1.8.0-2_all.deb --output apprise.deb && \
|
||||
apt update && \
|
||||
apt --yes --no-install-recommends install ./apprise.deb python3-paho-mqtt && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
rm -f apprise.deb && \
|
||||
apt --yes autoremove
|
||||
|
||||
# Install cloudflared
|
||||
RUN curl https://pkg.cloudflare.com/cloudflare-main.gpg --output /usr/share/keyrings/cloudflare-main.gpg && \
|
||||
|
@ -42,7 +50,9 @@ COPY ./docker/etc/sudoers /etc/sudoers
|
|||
|
||||
# Full Base Image
|
||||
# MariaDB, Chromium and fonts
|
||||
FROM base2-slim AS base2
|
||||
# Make sure to reuse the slim image here. Uncomment the above line if you want to build it from scratch.
|
||||
# FROM base2-slim AS base2
|
||||
FROM louislam/uptime-kuma:base2-slim AS base2
|
||||
ENV UPTIME_KUMA_ENABLE_EMBEDDED_MARIADB=1
|
||||
RUN apt update && \
|
||||
apt --yes --no-install-recommends install chromium fonts-indic fonts-noto fonts-noto-cjk mariadb-server && \
|
||||
|
|
5397
package-lock.json
generated
5397
package-lock.json
generated
File diff suppressed because it is too large
Load diff
20
package.json
20
package.json
|
@ -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 test/backend-test-entry.js",
|
||||
"test-backend:14": "cross-env TEST_BACKEND=1 NODE_OPTIONS=\"--experimental-abortcontroller --no-warnings\" node--test test/backend-test",
|
||||
"test-backend:18": "cross-env TEST_BACKEND=1 node --test test/backend-test",
|
||||
"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",
|
||||
|
@ -76,10 +74,10 @@
|
|||
"dependencies": {
|
||||
"@grpc/grpc-js": "~1.7.3",
|
||||
"@louislam/ping": "~0.4.4-mod.1",
|
||||
"@louislam/sqlite3": "15.1.6",
|
||||
"@vvo/tzdb": "^6.125.0",
|
||||
"args-parser": "~1.3.0",
|
||||
"axios": "~0.28.1",
|
||||
"axios-ntlm": "1.3.0",
|
||||
"badge-maker": "~3.3.1",
|
||||
"bcryptjs": "~2.4.3",
|
||||
"chardet": "~1.4.0",
|
||||
|
@ -89,12 +87,14 @@
|
|||
"command-exists": "~1.2.9",
|
||||
"compare-versions": "~3.6.0",
|
||||
"compression": "~1.7.4",
|
||||
"croner": "~6.0.5",
|
||||
"croner": "~8.1.0",
|
||||
"dayjs": "~1.11.5",
|
||||
"dev-null": "^0.1.1",
|
||||
"dotenv": "~16.0.3",
|
||||
"express": "~4.19.2",
|
||||
"express-basic-auth": "~1.2.1",
|
||||
"express-static-gzip": "~2.1.7",
|
||||
"feed": "^4.2.2",
|
||||
"form-data": "~4.0.0",
|
||||
"gamedig": "^4.2.0",
|
||||
"html-escaper": "^3.0.3",
|
||||
|
@ -112,12 +112,14 @@
|
|||
"knex": "^2.4.2",
|
||||
"limiter": "~2.1.0",
|
||||
"liquidjs": "^10.7.0",
|
||||
"marked": "^14.0.0",
|
||||
"mitt": "~3.0.1",
|
||||
"mongodb": "~4.17.1",
|
||||
"mqtt": "~4.3.7",
|
||||
"mssql": "~8.1.4",
|
||||
"mssql": "~11.0.0",
|
||||
"mysql2": "~3.9.6",
|
||||
"nanoid": "~3.3.4",
|
||||
"net-snmp": "^3.11.2",
|
||||
"node-cloudflared-tunnel": "~1.0.9",
|
||||
"node-radius-client": "~1.0.0",
|
||||
"nodemailer": "~6.9.13",
|
||||
|
@ -136,10 +138,9 @@
|
|||
"redbean-node": "~0.3.0",
|
||||
"redis": "~4.5.1",
|
||||
"semver": "~7.5.4",
|
||||
"socket.io": "~4.6.1",
|
||||
"socket.io-client": "~4.6.1",
|
||||
"socket.io": "~4.7.5",
|
||||
"socket.io-client": "~4.7.5",
|
||||
"socks-proxy-agent": "6.1.1",
|
||||
"sqlite3": "~5.1.7",
|
||||
"tar": "~6.2.1",
|
||||
"tcp-ping": "~0.1.1",
|
||||
"thirty-two": "~1.0.2",
|
||||
|
@ -177,7 +178,6 @@
|
|||
"eslint-plugin-vue": "~8.7.1",
|
||||
"favico.js": "~0.3.10",
|
||||
"get-port-please": "^3.1.1",
|
||||
"marked": "~4.2.5",
|
||||
"node-ssh": "~13.1.0",
|
||||
"postcss-html": "~1.5.0",
|
||||
"postcss-rtlcss": "~3.7.2",
|
||||
|
|
|
@ -213,6 +213,32 @@ async function sendRemoteBrowserList(socket) {
|
|||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send list of monitor types to client
|
||||
* @param {Socket} socket Socket.io socket instance
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function sendMonitorTypeList(socket) {
|
||||
const result = Object.entries(UptimeKumaServer.monitorTypeList).map(([ key, type ]) => {
|
||||
return [ key, {
|
||||
supportsConditions: type.supportsConditions,
|
||||
conditionVariables: type.conditionVariables.map(v => {
|
||||
return {
|
||||
id: v.id,
|
||||
operators: v.operators.map(o => {
|
||||
return {
|
||||
id: o.id,
|
||||
caption: o.caption,
|
||||
};
|
||||
}),
|
||||
};
|
||||
}),
|
||||
}];
|
||||
});
|
||||
|
||||
io.to(socket.userID).emit("monitorTypeList", Object.fromEntries(result));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sendNotificationList,
|
||||
sendImportantHeartbeatList,
|
||||
|
@ -222,4 +248,5 @@ module.exports = {
|
|||
sendInfo,
|
||||
sendDockerHostList,
|
||||
sendRemoteBrowserList,
|
||||
sendMonitorTypeList,
|
||||
};
|
||||
|
|
|
@ -223,8 +223,11 @@ class Database {
|
|||
fs.copyFileSync(Database.templatePath, Database.sqlitePath);
|
||||
}
|
||||
|
||||
const Dialect = require("knex/lib/dialects/sqlite3/index.js");
|
||||
Dialect.prototype._driver = () => require("@louislam/sqlite3");
|
||||
|
||||
config = {
|
||||
client: "sqlite3",
|
||||
client: Dialect,
|
||||
connection: {
|
||||
filename: Database.sqlitePath,
|
||||
acquireConnectionTimeout: acquireConnectionTimeout,
|
||||
|
|
|
@ -239,19 +239,7 @@ class Maintenance extends BeanModel {
|
|||
this.beanMeta.status = "under-maintenance";
|
||||
clearTimeout(this.beanMeta.durationTimeout);
|
||||
|
||||
// Check if duration is still in the window. If not, use the duration from the current time to the end of the window
|
||||
let duration;
|
||||
|
||||
if (customDuration > 0) {
|
||||
duration = customDuration;
|
||||
} else if (this.end_date) {
|
||||
let d = dayjs(this.end_date).diff(dayjs(), "second");
|
||||
if (d < this.duration) {
|
||||
duration = d * 1000;
|
||||
}
|
||||
} else {
|
||||
duration = this.duration * 1000;
|
||||
}
|
||||
let duration = this.inferDuration(customDuration);
|
||||
|
||||
UptimeKumaServer.getInstance().sendMaintenanceListByUserID(this.user_id);
|
||||
|
||||
|
@ -263,9 +251,21 @@ class Maintenance extends BeanModel {
|
|||
};
|
||||
|
||||
// Create Cron
|
||||
this.beanMeta.job = new Cron(this.cron, {
|
||||
timezone: await this.getTimezone(),
|
||||
}, startEvent);
|
||||
if (this.strategy === "recurring-interval") {
|
||||
// For recurring-interval, Croner needs to have interval and startAt
|
||||
const startDate = dayjs(this.startDate);
|
||||
const [ hour, minute ] = this.startTime.split(":");
|
||||
const startDateTime = startDate.hour(hour).minute(minute);
|
||||
this.beanMeta.job = new Cron(this.cron, {
|
||||
timezone: await this.getTimezone(),
|
||||
interval: this.interval_day * 24 * 60 * 60,
|
||||
startAt: startDateTime.toISOString(),
|
||||
}, startEvent);
|
||||
} else {
|
||||
this.beanMeta.job = new Cron(this.cron, {
|
||||
timezone: await this.getTimezone(),
|
||||
}, startEvent);
|
||||
}
|
||||
|
||||
// Continue if the maintenance is still in the window
|
||||
let runningTimeslot = this.getRunningTimeslot();
|
||||
|
@ -311,6 +311,24 @@ class Maintenance extends BeanModel {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the maintenance duration
|
||||
* @param {number} customDuration - The custom duration in milliseconds.
|
||||
* @returns {number} The inferred duration in milliseconds.
|
||||
*/
|
||||
inferDuration(customDuration) {
|
||||
// Check if duration is still in the window. If not, use the duration from the current time to the end of the window
|
||||
if (customDuration > 0) {
|
||||
return customDuration;
|
||||
} else if (this.end_date) {
|
||||
let d = dayjs(this.end_date).diff(dayjs(), "second");
|
||||
if (d < this.duration) {
|
||||
return d * 1000;
|
||||
}
|
||||
}
|
||||
return this.duration * 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the maintenance
|
||||
* @returns {void}
|
||||
|
@ -395,10 +413,8 @@ class Maintenance extends BeanModel {
|
|||
} else if (!this.strategy.startsWith("recurring-")) {
|
||||
this.cron = "";
|
||||
} else if (this.strategy === "recurring-interval") {
|
||||
let array = this.start_time.split(":");
|
||||
let hour = parseInt(array[0]);
|
||||
let minute = parseInt(array[1]);
|
||||
this.cron = minute + " " + hour + " */" + this.interval_day + " * *";
|
||||
// For intervals, the pattern is calculated in the run function as the interval-option is set
|
||||
this.cron = "* * * * *";
|
||||
this.duration = this.calcDuration();
|
||||
log.debug("maintenance", "Cron: " + this.cron);
|
||||
log.debug("maintenance", "Duration: " + this.duration);
|
||||
|
|
|
@ -2,7 +2,7 @@ const dayjs = require("dayjs");
|
|||
const axios = require("axios");
|
||||
const { Prometheus } = require("../prometheus");
|
||||
const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND,
|
||||
SQL_DATETIME_FORMAT
|
||||
SQL_DATETIME_FORMAT, evaluateJsonQuery
|
||||
} = require("../../src/util");
|
||||
const { tcping, ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, setSetting, httpNtlm, radius, grpcQuery,
|
||||
redisPingAsync, kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal
|
||||
|
@ -17,7 +17,6 @@ const apicache = require("../modules/apicache");
|
|||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||
const { DockerHost } = require("../docker");
|
||||
const Gamedig = require("gamedig");
|
||||
const jsonata = require("jsonata");
|
||||
const jwt = require("jsonwebtoken");
|
||||
const crypto = require("crypto");
|
||||
const { UptimeCalculator } = require("../uptime-calculator");
|
||||
|
@ -160,7 +159,12 @@ class Monitor extends BeanModel {
|
|||
kafkaProducerAllowAutoTopicCreation: this.getKafkaProducerAllowAutoTopicCreation(),
|
||||
kafkaProducerMessage: this.kafkaProducerMessage,
|
||||
screenshot,
|
||||
cacheBust: this.getCacheBust(),
|
||||
remote_browser: this.remote_browser,
|
||||
snmpOid: this.snmpOid,
|
||||
jsonPathOperator: this.jsonPathOperator,
|
||||
snmpVersion: this.snmpVersion,
|
||||
conditions: JSON.parse(this.conditions),
|
||||
};
|
||||
|
||||
if (includeSensitiveData) {
|
||||
|
@ -293,6 +297,14 @@ class Monitor extends BeanModel {
|
|||
return Boolean(this.grpcEnableTls);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse to boolean
|
||||
* @returns {boolean} if cachebusting is enabled
|
||||
*/
|
||||
getCacheBust() {
|
||||
return Boolean(this.cacheBust);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get accepted status codes
|
||||
* @returns {object} Accepted status codes
|
||||
|
@ -334,7 +346,7 @@ class Monitor extends BeanModel {
|
|||
let previousBeat = null;
|
||||
let retries = 0;
|
||||
|
||||
this.prometheus = new Prometheus(this);
|
||||
this.prometheus = await Prometheus.createAndInitMetrics(this);
|
||||
|
||||
const beat = async () => {
|
||||
|
||||
|
@ -498,6 +510,14 @@ class Monitor extends BeanModel {
|
|||
options.data = bodyValue;
|
||||
}
|
||||
|
||||
if (this.cacheBust) {
|
||||
const randomFloatString = Math.random().toString(36);
|
||||
const cacheBust = randomFloatString.substring(2);
|
||||
options.params = {
|
||||
uptime_kuma_cachebuster: cacheBust,
|
||||
};
|
||||
}
|
||||
|
||||
if (this.proxy_id) {
|
||||
const proxy = await R.load("proxy", this.proxy_id);
|
||||
|
||||
|
@ -598,25 +618,15 @@ class Monitor extends BeanModel {
|
|||
} else if (this.type === "json-query") {
|
||||
let data = res.data;
|
||||
|
||||
// convert data to object
|
||||
if (typeof data === "string" && res.headers["content-type"] !== "application/json") {
|
||||
try {
|
||||
data = JSON.parse(data);
|
||||
} catch (_) {
|
||||
// Failed to parse as JSON, just process it as a string
|
||||
}
|
||||
}
|
||||
const { status, response } = await evaluateJsonQuery(data, this.jsonPath, this.jsonPathOperator, this.expectedValue);
|
||||
|
||||
let expression = jsonata(this.jsonPath);
|
||||
|
||||
let result = await expression.evaluate(data);
|
||||
|
||||
if (result.toString() === this.expectedValue) {
|
||||
bean.msg += ", expected value is found";
|
||||
if (status) {
|
||||
bean.status = UP;
|
||||
bean.msg = `JSON query passes (comparing ${response} ${this.jsonPathOperator} ${this.expectedValue})`;
|
||||
} else {
|
||||
throw new Error(bean.msg + ", but value is not equal to expected value, value was: [" + result + "]");
|
||||
throw new Error(`JSON query does not pass (comparing ${response} ${this.jsonPathOperator} ${this.expectedValue})`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else if (this.type === "port") {
|
||||
|
@ -988,7 +998,7 @@ class Monitor extends BeanModel {
|
|||
await R.store(bean);
|
||||
|
||||
log.debug("monitor", `[${this.name}] prometheus.update`);
|
||||
this.prometheus?.update(bean, tlsInfo);
|
||||
await this.prometheus?.update(bean, tlsInfo);
|
||||
|
||||
previousBeat = bean;
|
||||
|
||||
|
|
|
@ -4,6 +4,11 @@ const cheerio = require("cheerio");
|
|||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||
const jsesc = require("jsesc");
|
||||
const googleAnalytics = require("../google-analytics");
|
||||
const { marked } = require("marked");
|
||||
const { Feed } = require("feed");
|
||||
const config = require("../config");
|
||||
|
||||
const { STATUS_PAGE_ALL_DOWN, STATUS_PAGE_ALL_UP, STATUS_PAGE_MAINTENANCE, STATUS_PAGE_PARTIAL_DOWN, UP, MAINTENANCE, DOWN } = require("../../src/util");
|
||||
|
||||
class StatusPage extends BeanModel {
|
||||
|
||||
|
@ -13,6 +18,24 @@ class StatusPage extends BeanModel {
|
|||
*/
|
||||
static domainMappingList = { };
|
||||
|
||||
/**
|
||||
* Handle responses to RSS pages
|
||||
* @param {Response} response Response object
|
||||
* @param {string} slug Status page slug
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async handleStatusPageRSSResponse(response, slug) {
|
||||
let statusPage = await R.findOne("status_page", " slug = ? ", [
|
||||
slug
|
||||
]);
|
||||
|
||||
if (statusPage) {
|
||||
response.send(await StatusPage.renderRSS(statusPage, slug));
|
||||
} else {
|
||||
response.status(404).send(UptimeKumaServer.getInstance().indexHTML);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle responses to status page
|
||||
* @param {Response} response Response object
|
||||
|
@ -38,6 +61,38 @@ class StatusPage extends BeanModel {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SSR for RSS feed
|
||||
* @param {statusPage} statusPage object
|
||||
* @param {slug} slug from router
|
||||
* @returns {Promise<string>} the rendered html
|
||||
*/
|
||||
static async renderRSS(statusPage, slug) {
|
||||
const { heartbeats, statusDescription } = await StatusPage.getRSSPageData(statusPage);
|
||||
|
||||
let proto = config.isSSL ? "https" : "http";
|
||||
let host = `${proto}://${config.hostname || "localhost"}:${config.port}/status/${slug}`;
|
||||
|
||||
const feed = new Feed({
|
||||
title: "uptime kuma rss feed",
|
||||
description: `current status: ${statusDescription}`,
|
||||
link: host,
|
||||
language: "en", // optional, used only in RSS 2.0, possible values: http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes
|
||||
updated: new Date(), // optional, default = today
|
||||
});
|
||||
|
||||
heartbeats.forEach(heartbeat => {
|
||||
feed.addItem({
|
||||
title: `${heartbeat.name} is down`,
|
||||
description: `${heartbeat.name} has been down since ${heartbeat.time}`,
|
||||
id: heartbeat.monitorID,
|
||||
date: new Date(heartbeat.time),
|
||||
});
|
||||
});
|
||||
|
||||
return feed.rss2();
|
||||
}
|
||||
|
||||
/**
|
||||
* SSR for status pages
|
||||
* @param {string} indexHTML HTML page to render
|
||||
|
@ -46,7 +101,11 @@ class StatusPage extends BeanModel {
|
|||
*/
|
||||
static async renderHTML(indexHTML, statusPage) {
|
||||
const $ = cheerio.load(indexHTML);
|
||||
const description155 = statusPage.description?.substring(0, 155) ?? "";
|
||||
|
||||
const description155 = marked(statusPage.description ?? "")
|
||||
.replace(/<[^>]+>/gm, "")
|
||||
.trim()
|
||||
.substring(0, 155);
|
||||
|
||||
$("title").text(statusPage.title);
|
||||
$("meta[name=description]").attr("content", description155);
|
||||
|
@ -93,6 +152,109 @@ class StatusPage extends BeanModel {
|
|||
return $.root().html();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {heartbeats} heartbeats from getRSSPageData
|
||||
* @returns {number} status_page constant from util.ts
|
||||
*/
|
||||
static overallStatus(heartbeats) {
|
||||
if (heartbeats.length === 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
let status = STATUS_PAGE_ALL_UP;
|
||||
let hasUp = false;
|
||||
|
||||
for (let beat of heartbeats) {
|
||||
if (beat.status === MAINTENANCE) {
|
||||
return STATUS_PAGE_MAINTENANCE;
|
||||
} else if (beat.status === UP) {
|
||||
hasUp = true;
|
||||
} else {
|
||||
status = STATUS_PAGE_PARTIAL_DOWN;
|
||||
}
|
||||
}
|
||||
|
||||
if (! hasUp) {
|
||||
status = STATUS_PAGE_ALL_DOWN;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} status from overallStatus
|
||||
* @returns {string} description
|
||||
*/
|
||||
static getStatusDescription(status) {
|
||||
if (status === -1) {
|
||||
return "No Services";
|
||||
}
|
||||
|
||||
if (status === STATUS_PAGE_ALL_UP) {
|
||||
return "All Systems Operational";
|
||||
}
|
||||
|
||||
if (status === STATUS_PAGE_PARTIAL_DOWN) {
|
||||
return "Partially Degraded Service";
|
||||
}
|
||||
|
||||
if (status === STATUS_PAGE_ALL_DOWN) {
|
||||
return "Degraded Service";
|
||||
}
|
||||
|
||||
// TODO: show the real maintenance information: title, description, time
|
||||
if (status === MAINTENANCE) {
|
||||
return "Under maintenance";
|
||||
}
|
||||
|
||||
return "?";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all data required for RSS
|
||||
* @param {StatusPage} statusPage Status page to get data for
|
||||
* @returns {object} Status page data
|
||||
*/
|
||||
static async getRSSPageData(statusPage) {
|
||||
// get all heartbeats that correspond to this statusPage
|
||||
const config = await statusPage.toPublicJSON();
|
||||
|
||||
// Public Group List
|
||||
const showTags = !!statusPage.show_tags;
|
||||
|
||||
const list = await R.find("group", " public = 1 AND status_page_id = ? ORDER BY weight ", [
|
||||
statusPage.id
|
||||
]);
|
||||
|
||||
let heartbeats = [];
|
||||
|
||||
for (let groupBean of list) {
|
||||
let monitorGroup = await groupBean.toPublicJSON(showTags, config?.showCertificateExpiry);
|
||||
for (const monitor of monitorGroup.monitorList) {
|
||||
const heartbeat = await R.findOne("heartbeat", "monitor_id = ? ORDER BY time DESC", [ monitor.id ]);
|
||||
if (heartbeat) {
|
||||
heartbeats.push({
|
||||
...monitor,
|
||||
status: heartbeat.status,
|
||||
time: heartbeat.time
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// calculate RSS feed description
|
||||
let status = StatusPage.overallStatus(heartbeats);
|
||||
let statusDescription = StatusPage.getStatusDescription(status);
|
||||
|
||||
// keep only DOWN heartbeats in the RSS feed
|
||||
heartbeats = heartbeats.filter(heartbeat => heartbeat.status === DOWN);
|
||||
|
||||
return {
|
||||
heartbeats,
|
||||
statusDescription
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all status page data in one call
|
||||
* @param {StatusPage} statusPage Status page to get data for
|
||||
|
|
21
server/modules/axios-ntlm/LICENSE
Normal file
21
server/modules/axios-ntlm/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 CatButtes
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
77
server/modules/axios-ntlm/lib/flags.js
Normal file
77
server/modules/axios-ntlm/lib/flags.js
Normal file
|
@ -0,0 +1,77 @@
|
|||
'use strict';
|
||||
// Original file https://raw.githubusercontent.com/elasticio/node-ntlm-client/master/lib/flags.js
|
||||
module.exports.NTLMFLAG_NEGOTIATE_UNICODE = 1 << 0;
|
||||
/* Indicates that Unicode strings are supported for use in security buffer
|
||||
data. */
|
||||
module.exports.NTLMFLAG_NEGOTIATE_OEM = 1 << 1;
|
||||
/* Indicates that OEM strings are supported for use in security buffer data. */
|
||||
module.exports.NTLMFLAG_REQUEST_TARGET = 1 << 2;
|
||||
/* Requests that the server's authentication realm be included in the Type 2
|
||||
message. */
|
||||
/* unknown (1<<3) */
|
||||
module.exports.NTLMFLAG_NEGOTIATE_SIGN = 1 << 4;
|
||||
/* Specifies that authenticated communication between the client and server
|
||||
should carry a digital signature (message integrity). */
|
||||
module.exports.NTLMFLAG_NEGOTIATE_SEAL = 1 << 5;
|
||||
/* Specifies that authenticated communication between the client and server
|
||||
should be encrypted (message confidentiality). */
|
||||
module.exports.NTLMFLAG_NEGOTIATE_DATAGRAM_STYLE = 1 << 6;
|
||||
/* Indicates that datagram authentication is being used. */
|
||||
module.exports.NTLMFLAG_NEGOTIATE_LM_KEY = 1 << 7;
|
||||
/* Indicates that the LAN Manager session key should be used for signing and
|
||||
sealing authenticated communications. */
|
||||
module.exports.NTLMFLAG_NEGOTIATE_NETWARE = 1 << 8;
|
||||
/* unknown purpose */
|
||||
module.exports.NTLMFLAG_NEGOTIATE_NTLM_KEY = 1 << 9;
|
||||
/* Indicates that NTLM authentication is being used. */
|
||||
/* unknown (1<<10) */
|
||||
module.exports.NTLMFLAG_NEGOTIATE_ANONYMOUS = 1 << 11;
|
||||
/* Sent by the client in the Type 3 message to indicate that an anonymous
|
||||
context has been established. This also affects the response fields. */
|
||||
module.exports.NTLMFLAG_NEGOTIATE_DOMAIN_SUPPLIED = 1 << 12;
|
||||
/* Sent by the client in the Type 1 message to indicate that a desired
|
||||
authentication realm is included in the message. */
|
||||
module.exports.NTLMFLAG_NEGOTIATE_WORKSTATION_SUPPLIED = 1 << 13;
|
||||
/* Sent by the client in the Type 1 message to indicate that the client
|
||||
workstation's name is included in the message. */
|
||||
module.exports.NTLMFLAG_NEGOTIATE_LOCAL_CALL = 1 << 14;
|
||||
/* Sent by the server to indicate that the server and client are on the same
|
||||
machine. Implies that the client may use a pre-established local security
|
||||
context rather than responding to the challenge. */
|
||||
module.exports.NTLMFLAG_NEGOTIATE_ALWAYS_SIGN = 1 << 15;
|
||||
/* Indicates that authenticated communication between the client and server
|
||||
should be signed with a "dummy" signature. */
|
||||
module.exports.NTLMFLAG_TARGET_TYPE_DOMAIN = 1 << 16;
|
||||
/* Sent by the server in the Type 2 message to indicate that the target
|
||||
authentication realm is a domain. */
|
||||
module.exports.NTLMFLAG_TARGET_TYPE_SERVER = 1 << 17;
|
||||
/* Sent by the server in the Type 2 message to indicate that the target
|
||||
authentication realm is a server. */
|
||||
module.exports.NTLMFLAG_TARGET_TYPE_SHARE = 1 << 18;
|
||||
/* Sent by the server in the Type 2 message to indicate that the target
|
||||
authentication realm is a share. Presumably, this is for share-level
|
||||
authentication. Usage is unclear. */
|
||||
module.exports.NTLMFLAG_NEGOTIATE_NTLM2_KEY = 1 << 19;
|
||||
/* Indicates that the NTLM2 signing and sealing scheme should be used for
|
||||
protecting authenticated communications. */
|
||||
module.exports.NTLMFLAG_REQUEST_INIT_RESPONSE = 1 << 20;
|
||||
/* unknown purpose */
|
||||
module.exports.NTLMFLAG_REQUEST_ACCEPT_RESPONSE = 1 << 21;
|
||||
/* unknown purpose */
|
||||
module.exports.NTLMFLAG_REQUEST_NONNT_SESSION_KEY = 1 << 22;
|
||||
/* unknown purpose */
|
||||
module.exports.NTLMFLAG_NEGOTIATE_TARGET_INFO = 1 << 23;
|
||||
/* Sent by the server in the Type 2 message to indicate that it is including a
|
||||
Target Information block in the message. */
|
||||
/* unknown (1<24) */
|
||||
/* unknown (1<25) */
|
||||
/* unknown (1<26) */
|
||||
/* unknown (1<27) */
|
||||
/* unknown (1<28) */
|
||||
module.exports.NTLMFLAG_NEGOTIATE_128 = 1 << 29;
|
||||
/* Indicates that 128-bit encryption is supported. */
|
||||
module.exports.NTLMFLAG_NEGOTIATE_KEY_EXCHANGE = 1 << 30;
|
||||
/* Indicates that the client will provide an encrypted master key in
|
||||
the "Session Key" field of the Type 3 message. */
|
||||
module.exports.NTLMFLAG_NEGOTIATE_56 = 1 << 31;
|
||||
//# sourceMappingURL=flags.js.map
|
122
server/modules/axios-ntlm/lib/hash.js
Normal file
122
server/modules/axios-ntlm/lib/hash.js
Normal file
|
@ -0,0 +1,122 @@
|
|||
'use strict';
|
||||
// Original source at https://github.com/elasticio/node-ntlm-client/blob/master/lib/hash.js
|
||||
var crypto = require('crypto');
|
||||
function createLMResponse(challenge, lmhash) {
|
||||
var buf = new Buffer.alloc(24), pwBuffer = new Buffer.alloc(21).fill(0);
|
||||
lmhash.copy(pwBuffer);
|
||||
calculateDES(pwBuffer.slice(0, 7), challenge).copy(buf);
|
||||
calculateDES(pwBuffer.slice(7, 14), challenge).copy(buf, 8);
|
||||
calculateDES(pwBuffer.slice(14), challenge).copy(buf, 16);
|
||||
return buf;
|
||||
}
|
||||
function createLMHash(password) {
|
||||
var buf = new Buffer.alloc(16), pwBuffer = new Buffer.alloc(14), magicKey = new Buffer.from('KGS!@#$%', 'ascii');
|
||||
if (password.length > 14) {
|
||||
buf.fill(0);
|
||||
return buf;
|
||||
}
|
||||
pwBuffer.fill(0);
|
||||
pwBuffer.write(password.toUpperCase(), 0, 'ascii');
|
||||
return Buffer.concat([
|
||||
calculateDES(pwBuffer.slice(0, 7), magicKey),
|
||||
calculateDES(pwBuffer.slice(7), magicKey)
|
||||
]);
|
||||
}
|
||||
function calculateDES(key, message) {
|
||||
var desKey = new Buffer.alloc(8);
|
||||
desKey[0] = key[0] & 0xFE;
|
||||
desKey[1] = ((key[0] << 7) & 0xFF) | (key[1] >> 1);
|
||||
desKey[2] = ((key[1] << 6) & 0xFF) | (key[2] >> 2);
|
||||
desKey[3] = ((key[2] << 5) & 0xFF) | (key[3] >> 3);
|
||||
desKey[4] = ((key[3] << 4) & 0xFF) | (key[4] >> 4);
|
||||
desKey[5] = ((key[4] << 3) & 0xFF) | (key[5] >> 5);
|
||||
desKey[6] = ((key[5] << 2) & 0xFF) | (key[6] >> 6);
|
||||
desKey[7] = (key[6] << 1) & 0xFF;
|
||||
for (var i = 0; i < 8; i++) {
|
||||
var parity = 0;
|
||||
for (var j = 1; j < 8; j++) {
|
||||
parity += (desKey[i] >> j) % 2;
|
||||
}
|
||||
desKey[i] |= (parity % 2) === 0 ? 1 : 0;
|
||||
}
|
||||
var des = crypto.createCipheriv('DES-ECB', desKey, '');
|
||||
return des.update(message);
|
||||
}
|
||||
function createNTLMResponse(challenge, ntlmhash) {
|
||||
var buf = new Buffer.alloc(24), ntlmBuffer = new Buffer.alloc(21).fill(0);
|
||||
ntlmhash.copy(ntlmBuffer);
|
||||
calculateDES(ntlmBuffer.slice(0, 7), challenge).copy(buf);
|
||||
calculateDES(ntlmBuffer.slice(7, 14), challenge).copy(buf, 8);
|
||||
calculateDES(ntlmBuffer.slice(14), challenge).copy(buf, 16);
|
||||
return buf;
|
||||
}
|
||||
function createNTLMHash(password) {
|
||||
var md4sum = crypto.createHash('md4');
|
||||
md4sum.update(new Buffer.from(password, 'ucs2'));
|
||||
return md4sum.digest();
|
||||
}
|
||||
function createNTLMv2Hash(ntlmhash, username, authTargetName) {
|
||||
var hmac = crypto.createHmac('md5', ntlmhash);
|
||||
hmac.update(new Buffer.from(username.toUpperCase() + authTargetName, 'ucs2'));
|
||||
return hmac.digest();
|
||||
}
|
||||
function createLMv2Response(type2message, username, ntlmhash, nonce, targetName) {
|
||||
var buf = new Buffer.alloc(24), ntlm2hash = createNTLMv2Hash(ntlmhash, username, targetName), hmac = crypto.createHmac('md5', ntlm2hash);
|
||||
//server challenge
|
||||
type2message.challenge.copy(buf, 8);
|
||||
//client nonce
|
||||
buf.write(nonce || createPseudoRandomValue(16), 16, 'hex');
|
||||
//create hash
|
||||
hmac.update(buf.slice(8));
|
||||
var hashedBuffer = hmac.digest();
|
||||
hashedBuffer.copy(buf);
|
||||
return buf;
|
||||
}
|
||||
function createNTLMv2Response(type2message, username, ntlmhash, nonce, targetName) {
|
||||
var buf = new Buffer.alloc(48 + type2message.targetInfo.buffer.length), ntlm2hash = createNTLMv2Hash(ntlmhash, username, targetName), hmac = crypto.createHmac('md5', ntlm2hash);
|
||||
//the first 8 bytes are spare to store the hashed value before the blob
|
||||
//server challenge
|
||||
type2message.challenge.copy(buf, 8);
|
||||
//blob signature
|
||||
buf.writeUInt32BE(0x01010000, 16);
|
||||
//reserved
|
||||
buf.writeUInt32LE(0, 20);
|
||||
//timestamp
|
||||
//TODO: we are loosing precision here since js is not able to handle those large integers
|
||||
// maybe think about a different solution here
|
||||
// 11644473600000 = diff between 1970 and 1601
|
||||
var timestamp = ((Date.now() + 11644473600000) * 10000).toString(16);
|
||||
var timestampLow = Number('0x' + timestamp.substring(Math.max(0, timestamp.length - 8)));
|
||||
var timestampHigh = Number('0x' + timestamp.substring(0, Math.max(0, timestamp.length - 8)));
|
||||
buf.writeUInt32LE(timestampLow, 24, false);
|
||||
buf.writeUInt32LE(timestampHigh, 28, false);
|
||||
//random client nonce
|
||||
buf.write(nonce || createPseudoRandomValue(16), 32, 'hex');
|
||||
//zero
|
||||
buf.writeUInt32LE(0, 40);
|
||||
//complete target information block from type 2 message
|
||||
type2message.targetInfo.buffer.copy(buf, 44);
|
||||
//zero
|
||||
buf.writeUInt32LE(0, 44 + type2message.targetInfo.buffer.length);
|
||||
hmac.update(buf.slice(8));
|
||||
var hashedBuffer = hmac.digest();
|
||||
hashedBuffer.copy(buf);
|
||||
return buf;
|
||||
}
|
||||
function createPseudoRandomValue(length) {
|
||||
var str = '';
|
||||
while (str.length < length) {
|
||||
str += Math.floor(Math.random() * 16).toString(16);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
module.exports = {
|
||||
createLMHash: createLMHash,
|
||||
createNTLMHash: createNTLMHash,
|
||||
createLMResponse: createLMResponse,
|
||||
createNTLMResponse: createNTLMResponse,
|
||||
createLMv2Response: createLMv2Response,
|
||||
createNTLMv2Response: createNTLMv2Response,
|
||||
createPseudoRandomValue: createPseudoRandomValue
|
||||
};
|
||||
//# sourceMappingURL=hash.js.map
|
220
server/modules/axios-ntlm/lib/ntlm.js
Normal file
220
server/modules/axios-ntlm/lib/ntlm.js
Normal file
|
@ -0,0 +1,220 @@
|
|||
'use strict';
|
||||
// Original file https://raw.githubusercontent.com/elasticio/node-ntlm-client/master/lib/ntlm.js
|
||||
var os = require('os'), flags = require('./flags'), hash = require('./hash');
|
||||
var NTLMSIGNATURE = "NTLMSSP\0";
|
||||
function createType1Message(workstation, target) {
|
||||
var dataPos = 32, pos = 0, buf = new Buffer.alloc(1024);
|
||||
workstation = workstation === undefined ? os.hostname() : workstation;
|
||||
target = target === undefined ? '' : target;
|
||||
//signature
|
||||
buf.write(NTLMSIGNATURE, pos, NTLMSIGNATURE.length, 'ascii');
|
||||
pos += NTLMSIGNATURE.length;
|
||||
//message type
|
||||
buf.writeUInt32LE(1, pos);
|
||||
pos += 4;
|
||||
//flags
|
||||
buf.writeUInt32LE(flags.NTLMFLAG_NEGOTIATE_OEM |
|
||||
flags.NTLMFLAG_REQUEST_TARGET |
|
||||
flags.NTLMFLAG_NEGOTIATE_NTLM_KEY |
|
||||
flags.NTLMFLAG_NEGOTIATE_NTLM2_KEY |
|
||||
flags.NTLMFLAG_NEGOTIATE_ALWAYS_SIGN, pos);
|
||||
pos += 4;
|
||||
//domain security buffer
|
||||
buf.writeUInt16LE(target.length, pos);
|
||||
pos += 2;
|
||||
buf.writeUInt16LE(target.length, pos);
|
||||
pos += 2;
|
||||
buf.writeUInt32LE(target.length === 0 ? 0 : dataPos, pos);
|
||||
pos += 4;
|
||||
if (target.length > 0) {
|
||||
dataPos += buf.write(target, dataPos, 'ascii');
|
||||
}
|
||||
//workstation security buffer
|
||||
buf.writeUInt16LE(workstation.length, pos);
|
||||
pos += 2;
|
||||
buf.writeUInt16LE(workstation.length, pos);
|
||||
pos += 2;
|
||||
buf.writeUInt32LE(workstation.length === 0 ? 0 : dataPos, pos);
|
||||
pos += 4;
|
||||
if (workstation.length > 0) {
|
||||
dataPos += buf.write(workstation, dataPos, 'ascii');
|
||||
}
|
||||
return 'NTLM ' + buf.toString('base64', 0, dataPos);
|
||||
}
|
||||
function decodeType2Message(str) {
|
||||
if (str === undefined) {
|
||||
throw new Error('Invalid argument');
|
||||
}
|
||||
//convenience
|
||||
if (Object.prototype.toString.call(str) !== '[object String]') {
|
||||
if (str.hasOwnProperty('headers') && str.headers.hasOwnProperty('www-authenticate')) {
|
||||
str = str.headers['www-authenticate'];
|
||||
}
|
||||
else {
|
||||
throw new Error('Invalid argument');
|
||||
}
|
||||
}
|
||||
var ntlmMatch = /^NTLM ([^,\s]+)/.exec(str);
|
||||
if (ntlmMatch) {
|
||||
str = ntlmMatch[1];
|
||||
}
|
||||
var buf = new Buffer.from(str, 'base64'), obj = {};
|
||||
//check signature
|
||||
if (buf.toString('ascii', 0, NTLMSIGNATURE.length) !== NTLMSIGNATURE) {
|
||||
throw new Error('Invalid message signature: ' + str);
|
||||
}
|
||||
//check message type
|
||||
if (buf.readUInt32LE(NTLMSIGNATURE.length) !== 2) {
|
||||
throw new Error('Invalid message type (no type 2)');
|
||||
}
|
||||
//read flags
|
||||
obj.flags = buf.readUInt32LE(20);
|
||||
obj.encoding = (obj.flags & flags.NTLMFLAG_NEGOTIATE_OEM) ? 'ascii' : 'ucs2';
|
||||
obj.version = (obj.flags & flags.NTLMFLAG_NEGOTIATE_NTLM2_KEY) ? 2 : 1;
|
||||
obj.challenge = buf.slice(24, 32);
|
||||
//read target name
|
||||
obj.targetName = (function () {
|
||||
var length = buf.readUInt16LE(12);
|
||||
//skipping allocated space
|
||||
var offset = buf.readUInt32LE(16);
|
||||
if (length === 0) {
|
||||
return '';
|
||||
}
|
||||
if ((offset + length) > buf.length || offset < 32) {
|
||||
throw new Error('Bad type 2 message');
|
||||
}
|
||||
return buf.toString(obj.encoding, offset, offset + length);
|
||||
})();
|
||||
//read target info
|
||||
if (obj.flags & flags.NTLMFLAG_NEGOTIATE_TARGET_INFO) {
|
||||
obj.targetInfo = (function () {
|
||||
var info = {};
|
||||
var length = buf.readUInt16LE(40);
|
||||
//skipping allocated space
|
||||
var offset = buf.readUInt32LE(44);
|
||||
var targetInfoBuffer = new Buffer.alloc(length);
|
||||
buf.copy(targetInfoBuffer, 0, offset, offset + length);
|
||||
if (length === 0) {
|
||||
return info;
|
||||
}
|
||||
if ((offset + length) > buf.length || offset < 32) {
|
||||
throw new Error('Bad type 2 message');
|
||||
}
|
||||
var pos = offset;
|
||||
while (pos < (offset + length)) {
|
||||
var blockType = buf.readUInt16LE(pos);
|
||||
pos += 2;
|
||||
var blockLength = buf.readUInt16LE(pos);
|
||||
pos += 2;
|
||||
if (blockType === 0) {
|
||||
//reached the terminator subblock
|
||||
break;
|
||||
}
|
||||
var blockTypeStr = void 0;
|
||||
switch (blockType) {
|
||||
case 1:
|
||||
blockTypeStr = 'SERVER';
|
||||
break;
|
||||
case 2:
|
||||
blockTypeStr = 'DOMAIN';
|
||||
break;
|
||||
case 3:
|
||||
blockTypeStr = 'FQDN';
|
||||
break;
|
||||
case 4:
|
||||
blockTypeStr = 'DNS';
|
||||
break;
|
||||
case 5:
|
||||
blockTypeStr = 'PARENT_DNS';
|
||||
break;
|
||||
default:
|
||||
blockTypeStr = '';
|
||||
break;
|
||||
}
|
||||
if (blockTypeStr) {
|
||||
info[blockTypeStr] = buf.toString('ucs2', pos, pos + blockLength);
|
||||
}
|
||||
pos += blockLength;
|
||||
}
|
||||
return {
|
||||
parsed: info,
|
||||
buffer: targetInfoBuffer
|
||||
};
|
||||
})();
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
function createType3Message(type2Message, username, password, workstation, target) {
|
||||
var dataPos = 52, buf = new Buffer.alloc(1024);
|
||||
if (workstation === undefined) {
|
||||
workstation = os.hostname();
|
||||
}
|
||||
if (target === undefined) {
|
||||
target = type2Message.targetName;
|
||||
}
|
||||
//signature
|
||||
buf.write(NTLMSIGNATURE, 0, NTLMSIGNATURE.length, 'ascii');
|
||||
//message type
|
||||
buf.writeUInt32LE(3, 8);
|
||||
if (type2Message.version === 2) {
|
||||
dataPos = 64;
|
||||
var ntlmHash = hash.createNTLMHash(password), nonce = hash.createPseudoRandomValue(16), lmv2 = hash.createLMv2Response(type2Message, username, ntlmHash, nonce, target), ntlmv2 = hash.createNTLMv2Response(type2Message, username, ntlmHash, nonce, target);
|
||||
//lmv2 security buffer
|
||||
buf.writeUInt16LE(lmv2.length, 12);
|
||||
buf.writeUInt16LE(lmv2.length, 14);
|
||||
buf.writeUInt32LE(dataPos, 16);
|
||||
lmv2.copy(buf, dataPos);
|
||||
dataPos += lmv2.length;
|
||||
//ntlmv2 security buffer
|
||||
buf.writeUInt16LE(ntlmv2.length, 20);
|
||||
buf.writeUInt16LE(ntlmv2.length, 22);
|
||||
buf.writeUInt32LE(dataPos, 24);
|
||||
ntlmv2.copy(buf, dataPos);
|
||||
dataPos += ntlmv2.length;
|
||||
}
|
||||
else {
|
||||
var lmHash = hash.createLMHash(password), ntlmHash = hash.createNTLMHash(password), lm = hash.createLMResponse(type2Message.challenge, lmHash), ntlm = hash.createNTLMResponse(type2Message.challenge, ntlmHash);
|
||||
//lm security buffer
|
||||
buf.writeUInt16LE(lm.length, 12);
|
||||
buf.writeUInt16LE(lm.length, 14);
|
||||
buf.writeUInt32LE(dataPos, 16);
|
||||
lm.copy(buf, dataPos);
|
||||
dataPos += lm.length;
|
||||
//ntlm security buffer
|
||||
buf.writeUInt16LE(ntlm.length, 20);
|
||||
buf.writeUInt16LE(ntlm.length, 22);
|
||||
buf.writeUInt32LE(dataPos, 24);
|
||||
ntlm.copy(buf, dataPos);
|
||||
dataPos += ntlm.length;
|
||||
}
|
||||
//target name security buffer
|
||||
buf.writeUInt16LE(type2Message.encoding === 'ascii' ? target.length : target.length * 2, 28);
|
||||
buf.writeUInt16LE(type2Message.encoding === 'ascii' ? target.length : target.length * 2, 30);
|
||||
buf.writeUInt32LE(dataPos, 32);
|
||||
dataPos += buf.write(target, dataPos, type2Message.encoding);
|
||||
//user name security buffer
|
||||
buf.writeUInt16LE(type2Message.encoding === 'ascii' ? username.length : username.length * 2, 36);
|
||||
buf.writeUInt16LE(type2Message.encoding === 'ascii' ? username.length : username.length * 2, 38);
|
||||
buf.writeUInt32LE(dataPos, 40);
|
||||
dataPos += buf.write(username, dataPos, type2Message.encoding);
|
||||
//workstation name security buffer
|
||||
buf.writeUInt16LE(type2Message.encoding === 'ascii' ? workstation.length : workstation.length * 2, 44);
|
||||
buf.writeUInt16LE(type2Message.encoding === 'ascii' ? workstation.length : workstation.length * 2, 46);
|
||||
buf.writeUInt32LE(dataPos, 48);
|
||||
dataPos += buf.write(workstation, dataPos, type2Message.encoding);
|
||||
if (type2Message.version === 2) {
|
||||
//session key security buffer
|
||||
buf.writeUInt16LE(0, 52);
|
||||
buf.writeUInt16LE(0, 54);
|
||||
buf.writeUInt32LE(0, 56);
|
||||
//flags
|
||||
buf.writeUInt32LE(type2Message.flags, 60);
|
||||
}
|
||||
return 'NTLM ' + buf.toString('base64', 0, dataPos);
|
||||
}
|
||||
module.exports = {
|
||||
createType1Message: createType1Message,
|
||||
decodeType2Message: decodeType2Message,
|
||||
createType3Message: createType3Message
|
||||
};
|
||||
//# sourceMappingURL=ntlm.js.map
|
127
server/modules/axios-ntlm/lib/ntlmClient.js
Normal file
127
server/modules/axios-ntlm/lib/ntlmClient.js
Normal file
|
@ -0,0 +1,127 @@
|
|||
"use strict";
|
||||
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 __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __generator = (this && this.__generator) || function (thisArg, body) {
|
||||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
||||
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
||||
function verb(n) { return function (v) { return step([n, v]); }; }
|
||||
function step(op) {
|
||||
if (f) throw new TypeError("Generator is already executing.");
|
||||
while (_) try {
|
||||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
||||
if (y = 0, t) op = [op[0] & 2, t.value];
|
||||
switch (op[0]) {
|
||||
case 0: case 1: t = op; break;
|
||||
case 4: _.label++; return { value: op[1], done: false };
|
||||
case 5: _.label++; y = op[1]; op = [0]; continue;
|
||||
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
||||
default:
|
||||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
||||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
||||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
||||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
||||
if (t[2]) _.ops.pop();
|
||||
_.trys.pop(); continue;
|
||||
}
|
||||
op = body.call(thisArg, _);
|
||||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
||||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
||||
}
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.NtlmClient = void 0;
|
||||
var axios_1 = __importDefault(require("axios"));
|
||||
var ntlm = __importStar(require("./ntlm"));
|
||||
var https = __importStar(require("https"));
|
||||
var http = __importStar(require("http"));
|
||||
var dev_null_1 = __importDefault(require("dev-null"));
|
||||
/**
|
||||
* @param credentials An NtlmCredentials object containing the username and password
|
||||
* @param AxiosConfig The Axios config for the instance you wish to create
|
||||
*
|
||||
* @returns This function returns an axios instance configured to use the provided credentials
|
||||
*/
|
||||
function NtlmClient(credentials, AxiosConfig) {
|
||||
var _this = this;
|
||||
var config = AxiosConfig !== null && AxiosConfig !== void 0 ? AxiosConfig : {};
|
||||
if (!config.httpAgent) {
|
||||
config.httpAgent = new http.Agent({ keepAlive: true });
|
||||
}
|
||||
if (!config.httpsAgent) {
|
||||
config.httpsAgent = new https.Agent({ keepAlive: true });
|
||||
}
|
||||
var client = axios_1.default.create(config);
|
||||
client.interceptors.response.use(function (response) {
|
||||
return response;
|
||||
}, function (err) { return __awaiter(_this, void 0, void 0, function () {
|
||||
var error, t1Msg, t2Msg, t3Msg, stream_1;
|
||||
var _a;
|
||||
return __generator(this, function (_b) {
|
||||
switch (_b.label) {
|
||||
case 0:
|
||||
error = err.response;
|
||||
if (!(error && error.status === 401
|
||||
&& error.headers['www-authenticate']
|
||||
&& error.headers['www-authenticate'].includes('NTLM'))) return [3 /*break*/, 3];
|
||||
// This length check is a hack because SharePoint is awkward and will
|
||||
// include the Negotiate option when responding with the T2 message
|
||||
// There is nore we could do to ensure we are processing correctly,
|
||||
// but this is the easiest option for now
|
||||
if (error.headers['www-authenticate'].length < 50) {
|
||||
t1Msg = ntlm.createType1Message(credentials.workstation, credentials.domain);
|
||||
error.config.headers["Authorization"] = t1Msg;
|
||||
}
|
||||
else {
|
||||
t2Msg = ntlm.decodeType2Message((error.headers['www-authenticate'].match(/^NTLM\s+(.+?)(,|\s+|$)/) || [])[1]);
|
||||
t3Msg = ntlm.createType3Message(t2Msg, credentials.username, credentials.password, credentials.workstation, credentials.domain);
|
||||
error.config.headers["X-retry"] = "false";
|
||||
error.config.headers["Authorization"] = t3Msg;
|
||||
}
|
||||
if (!(error.config.responseType === "stream")) return [3 /*break*/, 2];
|
||||
stream_1 = (_a = err.response) === null || _a === void 0 ? void 0 : _a.data;
|
||||
if (!(stream_1 && !stream_1.readableEnded)) return [3 /*break*/, 2];
|
||||
return [4 /*yield*/, new Promise(function (resolve) {
|
||||
stream_1.pipe((0, dev_null_1.default)());
|
||||
stream_1.once('close', resolve);
|
||||
})];
|
||||
case 1:
|
||||
_b.sent();
|
||||
_b.label = 2;
|
||||
case 2: return [2 /*return*/, client(error.config)];
|
||||
case 3: throw err;
|
||||
}
|
||||
});
|
||||
}); });
|
||||
return client;
|
||||
}
|
||||
exports.NtlmClient = NtlmClient;
|
||||
//# sourceMappingURL=ntlmClient.js.map
|
71
server/monitor-conditions/evaluator.js
Normal file
71
server/monitor-conditions/evaluator.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
const { ConditionExpressionGroup, ConditionExpression, LOGICAL } = require("./expression");
|
||||
const { operatorMap } = require("./operators");
|
||||
|
||||
/**
|
||||
* @param {ConditionExpression} expression Expression to evaluate
|
||||
* @param {object} context Context to evaluate against; These are values for variables in the expression
|
||||
* @returns {boolean} Whether the expression evaluates true or false
|
||||
* @throws {Error}
|
||||
*/
|
||||
function evaluateExpression(expression, context) {
|
||||
/**
|
||||
* @type {import("./operators").ConditionOperator|null}
|
||||
*/
|
||||
const operator = operatorMap.get(expression.operator) || null;
|
||||
if (operator === null) {
|
||||
throw new Error("Unexpected expression operator ID '" + expression.operator + "'. Expected one of [" + operatorMap.keys().join(",") + "]");
|
||||
}
|
||||
|
||||
if (!Object.prototype.hasOwnProperty.call(context, expression.variable)) {
|
||||
throw new Error("Variable missing in context: " + expression.variable);
|
||||
}
|
||||
|
||||
return operator.test(context[expression.variable], expression.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ConditionExpressionGroup} group Group of expressions to evaluate
|
||||
* @param {object} context Context to evaluate against; These are values for variables in the expression
|
||||
* @returns {boolean} Whether the group evaluates true or false
|
||||
* @throws {Error}
|
||||
*/
|
||||
function evaluateExpressionGroup(group, context) {
|
||||
if (!group.children.length) {
|
||||
throw new Error("ConditionExpressionGroup must contain at least one child.");
|
||||
}
|
||||
|
||||
let result = null;
|
||||
|
||||
for (const child of group.children) {
|
||||
let childResult;
|
||||
|
||||
if (child instanceof ConditionExpression) {
|
||||
childResult = evaluateExpression(child, context);
|
||||
} else if (child instanceof ConditionExpressionGroup) {
|
||||
childResult = evaluateExpressionGroup(child, context);
|
||||
} else {
|
||||
throw new Error("Invalid child type in ConditionExpressionGroup. Expected ConditionExpression or ConditionExpressionGroup");
|
||||
}
|
||||
|
||||
if (result === null) {
|
||||
result = childResult; // Initialize result with the first child's result
|
||||
} else if (child.andOr === LOGICAL.OR) {
|
||||
result = result || childResult;
|
||||
} else if (child.andOr === LOGICAL.AND) {
|
||||
result = result && childResult;
|
||||
} else {
|
||||
throw new Error("Invalid logical operator in child of ConditionExpressionGroup. Expected 'and' or 'or'. Got '" + group.andOr + "'");
|
||||
}
|
||||
}
|
||||
|
||||
if (result === null) {
|
||||
throw new Error("ConditionExpressionGroup did not result in a boolean.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
evaluateExpression,
|
||||
evaluateExpressionGroup,
|
||||
};
|
111
server/monitor-conditions/expression.js
Normal file
111
server/monitor-conditions/expression.js
Normal file
|
@ -0,0 +1,111 @@
|
|||
/**
|
||||
* @readonly
|
||||
* @enum {string}
|
||||
*/
|
||||
const LOGICAL = {
|
||||
AND: "and",
|
||||
OR: "or",
|
||||
};
|
||||
|
||||
/**
|
||||
* Recursively processes an array of raw condition objects and populates the given parent group with
|
||||
* corresponding ConditionExpression or ConditionExpressionGroup instances.
|
||||
* @param {Array} conditions Array of raw condition objects, where each object represents either a group or an expression.
|
||||
* @param {ConditionExpressionGroup} parentGroup The parent group to which the instantiated ConditionExpression or ConditionExpressionGroup objects will be added.
|
||||
* @returns {void}
|
||||
*/
|
||||
function processMonitorConditions(conditions, parentGroup) {
|
||||
conditions.forEach(condition => {
|
||||
const andOr = condition.andOr === LOGICAL.OR ? LOGICAL.OR : LOGICAL.AND;
|
||||
|
||||
if (condition.type === "group") {
|
||||
const group = new ConditionExpressionGroup([], andOr);
|
||||
|
||||
// Recursively process the group's children
|
||||
processMonitorConditions(condition.children, group);
|
||||
|
||||
parentGroup.children.push(group);
|
||||
} else if (condition.type === "expression") {
|
||||
const expression = new ConditionExpression(condition.variable, condition.operator, condition.value, andOr);
|
||||
parentGroup.children.push(expression);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class ConditionExpressionGroup {
|
||||
/**
|
||||
* @type {ConditionExpressionGroup[]|ConditionExpression[]} Groups and/or expressions to test
|
||||
*/
|
||||
children = [];
|
||||
|
||||
/**
|
||||
* @type {LOGICAL} Connects group result with previous group/expression results
|
||||
*/
|
||||
andOr;
|
||||
|
||||
/**
|
||||
* @param {ConditionExpressionGroup[]|ConditionExpression[]} children Groups and/or expressions to test
|
||||
* @param {LOGICAL} andOr Connects group result with previous group/expression results
|
||||
*/
|
||||
constructor(children = [], andOr = LOGICAL.AND) {
|
||||
this.children = children;
|
||||
this.andOr = andOr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Monitor} monitor Monitor instance
|
||||
* @returns {ConditionExpressionGroup|null} A ConditionExpressionGroup with the Monitor's conditions
|
||||
*/
|
||||
static fromMonitor(monitor) {
|
||||
const conditions = JSON.parse(monitor.conditions);
|
||||
if (conditions.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const root = new ConditionExpressionGroup();
|
||||
processMonitorConditions(conditions, root);
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
||||
|
||||
class ConditionExpression {
|
||||
/**
|
||||
* @type {string} ID of variable
|
||||
*/
|
||||
variable;
|
||||
|
||||
/**
|
||||
* @type {string} ID of operator
|
||||
*/
|
||||
operator;
|
||||
|
||||
/**
|
||||
* @type {string} Value to test with the operator
|
||||
*/
|
||||
value;
|
||||
|
||||
/**
|
||||
* @type {LOGICAL} Connects expression result with previous group/expression results
|
||||
*/
|
||||
andOr;
|
||||
|
||||
/**
|
||||
* @param {string} variable ID of variable to test against
|
||||
* @param {string} operator ID of operator to test the variable with
|
||||
* @param {string} value Value to test with the operator
|
||||
* @param {LOGICAL} andOr Connects expression result with previous group/expression results
|
||||
*/
|
||||
constructor(variable, operator, value, andOr = LOGICAL.AND) {
|
||||
this.variable = variable;
|
||||
this.operator = operator;
|
||||
this.value = value;
|
||||
this.andOr = andOr;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
LOGICAL,
|
||||
ConditionExpressionGroup,
|
||||
ConditionExpression,
|
||||
};
|
318
server/monitor-conditions/operators.js
Normal file
318
server/monitor-conditions/operators.js
Normal file
|
@ -0,0 +1,318 @@
|
|||
class ConditionOperator {
|
||||
id = undefined;
|
||||
caption = undefined;
|
||||
|
||||
/**
|
||||
* @type {mixed} variable
|
||||
* @type {mixed} value
|
||||
*/
|
||||
test(variable, value) {
|
||||
throw new Error("You need to override test()");
|
||||
}
|
||||
}
|
||||
|
||||
const OP_STR_EQUALS = "equals";
|
||||
|
||||
const OP_STR_NOT_EQUALS = "not_equals";
|
||||
|
||||
const OP_CONTAINS = "contains";
|
||||
|
||||
const OP_NOT_CONTAINS = "not_contains";
|
||||
|
||||
const OP_STARTS_WITH = "starts_with";
|
||||
|
||||
const OP_NOT_STARTS_WITH = "not_starts_with";
|
||||
|
||||
const OP_ENDS_WITH = "ends_with";
|
||||
|
||||
const OP_NOT_ENDS_WITH = "not_ends_with";
|
||||
|
||||
const OP_NUM_EQUALS = "num_equals";
|
||||
|
||||
const OP_NUM_NOT_EQUALS = "num_not_equals";
|
||||
|
||||
const OP_LT = "lt";
|
||||
|
||||
const OP_GT = "gt";
|
||||
|
||||
const OP_LTE = "lte";
|
||||
|
||||
const OP_GTE = "gte";
|
||||
|
||||
/**
|
||||
* Asserts a variable is equal to a value.
|
||||
*/
|
||||
class StringEqualsOperator extends ConditionOperator {
|
||||
id = OP_STR_EQUALS;
|
||||
caption = "equals";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
test(variable, value) {
|
||||
return variable === value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts a variable is not equal to a value.
|
||||
*/
|
||||
class StringNotEqualsOperator extends ConditionOperator {
|
||||
id = OP_STR_NOT_EQUALS;
|
||||
caption = "not equals";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
test(variable, value) {
|
||||
return variable !== value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts a variable contains a value.
|
||||
* Handles both Array and String variable types.
|
||||
*/
|
||||
class ContainsOperator extends ConditionOperator {
|
||||
id = OP_CONTAINS;
|
||||
caption = "contains";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
test(variable, value) {
|
||||
if (Array.isArray(variable)) {
|
||||
return variable.includes(value);
|
||||
}
|
||||
|
||||
return variable.indexOf(value) !== -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts a variable does not contain a value.
|
||||
* Handles both Array and String variable types.
|
||||
*/
|
||||
class NotContainsOperator extends ConditionOperator {
|
||||
id = OP_NOT_CONTAINS;
|
||||
caption = "not contains";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
test(variable, value) {
|
||||
if (Array.isArray(variable)) {
|
||||
return !variable.includes(value);
|
||||
}
|
||||
|
||||
return variable.indexOf(value) === -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts a variable starts with a value.
|
||||
*/
|
||||
class StartsWithOperator extends ConditionOperator {
|
||||
id = OP_STARTS_WITH;
|
||||
caption = "starts with";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
test(variable, value) {
|
||||
return variable.startsWith(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts a variable does not start with a value.
|
||||
*/
|
||||
class NotStartsWithOperator extends ConditionOperator {
|
||||
id = OP_NOT_STARTS_WITH;
|
||||
caption = "not starts with";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
test(variable, value) {
|
||||
return !variable.startsWith(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts a variable ends with a value.
|
||||
*/
|
||||
class EndsWithOperator extends ConditionOperator {
|
||||
id = OP_ENDS_WITH;
|
||||
caption = "ends with";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
test(variable, value) {
|
||||
return variable.endsWith(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts a variable does not end with a value.
|
||||
*/
|
||||
class NotEndsWithOperator extends ConditionOperator {
|
||||
id = OP_NOT_ENDS_WITH;
|
||||
caption = "not ends with";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
test(variable, value) {
|
||||
return !variable.endsWith(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts a numeric variable is equal to a value.
|
||||
*/
|
||||
class NumberEqualsOperator extends ConditionOperator {
|
||||
id = OP_NUM_EQUALS;
|
||||
caption = "equals";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
test(variable, value) {
|
||||
return variable === Number(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts a numeric variable is not equal to a value.
|
||||
*/
|
||||
class NumberNotEqualsOperator extends ConditionOperator {
|
||||
id = OP_NUM_NOT_EQUALS;
|
||||
caption = "not equals";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
test(variable, value) {
|
||||
return variable !== Number(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts a variable is less than a value.
|
||||
*/
|
||||
class LessThanOperator extends ConditionOperator {
|
||||
id = OP_LT;
|
||||
caption = "less than";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
test(variable, value) {
|
||||
return variable < Number(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts a variable is greater than a value.
|
||||
*/
|
||||
class GreaterThanOperator extends ConditionOperator {
|
||||
id = OP_GT;
|
||||
caption = "greater than";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
test(variable, value) {
|
||||
return variable > Number(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts a variable is less than or equal to a value.
|
||||
*/
|
||||
class LessThanOrEqualToOperator extends ConditionOperator {
|
||||
id = OP_LTE;
|
||||
caption = "less than or equal to";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
test(variable, value) {
|
||||
return variable <= Number(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts a variable is greater than or equal to a value.
|
||||
*/
|
||||
class GreaterThanOrEqualToOperator extends ConditionOperator {
|
||||
id = OP_GTE;
|
||||
caption = "greater than or equal to";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
test(variable, value) {
|
||||
return variable >= Number(value);
|
||||
}
|
||||
}
|
||||
|
||||
const operatorMap = new Map([
|
||||
[ OP_STR_EQUALS, new StringEqualsOperator ],
|
||||
[ OP_STR_NOT_EQUALS, new StringNotEqualsOperator ],
|
||||
[ OP_CONTAINS, new ContainsOperator ],
|
||||
[ OP_NOT_CONTAINS, new NotContainsOperator ],
|
||||
[ OP_STARTS_WITH, new StartsWithOperator ],
|
||||
[ OP_NOT_STARTS_WITH, new NotStartsWithOperator ],
|
||||
[ OP_ENDS_WITH, new EndsWithOperator ],
|
||||
[ OP_NOT_ENDS_WITH, new NotEndsWithOperator ],
|
||||
[ OP_NUM_EQUALS, new NumberEqualsOperator ],
|
||||
[ OP_NUM_NOT_EQUALS, new NumberNotEqualsOperator ],
|
||||
[ OP_LT, new LessThanOperator ],
|
||||
[ OP_GT, new GreaterThanOperator ],
|
||||
[ OP_LTE, new LessThanOrEqualToOperator ],
|
||||
[ OP_GTE, new GreaterThanOrEqualToOperator ],
|
||||
]);
|
||||
|
||||
const defaultStringOperators = [
|
||||
operatorMap.get(OP_STR_EQUALS),
|
||||
operatorMap.get(OP_STR_NOT_EQUALS),
|
||||
operatorMap.get(OP_CONTAINS),
|
||||
operatorMap.get(OP_NOT_CONTAINS),
|
||||
operatorMap.get(OP_STARTS_WITH),
|
||||
operatorMap.get(OP_NOT_STARTS_WITH),
|
||||
operatorMap.get(OP_ENDS_WITH),
|
||||
operatorMap.get(OP_NOT_ENDS_WITH)
|
||||
];
|
||||
|
||||
const defaultNumberOperators = [
|
||||
operatorMap.get(OP_NUM_EQUALS),
|
||||
operatorMap.get(OP_NUM_NOT_EQUALS),
|
||||
operatorMap.get(OP_LT),
|
||||
operatorMap.get(OP_GT),
|
||||
operatorMap.get(OP_LTE),
|
||||
operatorMap.get(OP_GTE)
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
OP_STR_EQUALS,
|
||||
OP_STR_NOT_EQUALS,
|
||||
OP_CONTAINS,
|
||||
OP_NOT_CONTAINS,
|
||||
OP_STARTS_WITH,
|
||||
OP_NOT_STARTS_WITH,
|
||||
OP_ENDS_WITH,
|
||||
OP_NOT_ENDS_WITH,
|
||||
OP_NUM_EQUALS,
|
||||
OP_NUM_NOT_EQUALS,
|
||||
OP_LT,
|
||||
OP_GT,
|
||||
OP_LTE,
|
||||
OP_GTE,
|
||||
operatorMap,
|
||||
defaultStringOperators,
|
||||
defaultNumberOperators,
|
||||
ConditionOperator,
|
||||
};
|
31
server/monitor-conditions/variables.js
Normal file
31
server/monitor-conditions/variables.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
/**
|
||||
* Represents a variable used in a condition and the set of operators that can be applied to this variable.
|
||||
*
|
||||
* A `ConditionVariable` holds the ID of the variable and a list of operators that define how this variable can be evaluated
|
||||
* in conditions. For example, if the variable is a request body or a specific field in a request, the operators can include
|
||||
* operations such as equality checks, comparisons, or other custom evaluations.
|
||||
*/
|
||||
class ConditionVariable {
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
id;
|
||||
|
||||
/**
|
||||
* @type {import("./operators").ConditionOperator[]}
|
||||
*/
|
||||
operators = {};
|
||||
|
||||
/**
|
||||
* @param {string} id ID of variable
|
||||
* @param {import("./operators").ConditionOperator[]} operators Operators the condition supports
|
||||
*/
|
||||
constructor(id, operators = []) {
|
||||
this.id = id;
|
||||
this.operators = operators;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ConditionVariable,
|
||||
};
|
|
@ -1,13 +1,22 @@
|
|||
const { MonitorType } = require("./monitor-type");
|
||||
const { UP } = require("../../src/util");
|
||||
const { UP, DOWN } = require("../../src/util");
|
||||
const dayjs = require("dayjs");
|
||||
const { dnsResolve } = require("../util-server");
|
||||
const { R } = require("redbean-node");
|
||||
const { ConditionVariable } = require("../monitor-conditions/variables");
|
||||
const { defaultStringOperators } = require("../monitor-conditions/operators");
|
||||
const { ConditionExpressionGroup } = require("../monitor-conditions/expression");
|
||||
const { evaluateExpressionGroup } = require("../monitor-conditions/evaluator");
|
||||
|
||||
class DnsMonitorType extends MonitorType {
|
||||
|
||||
name = "dns";
|
||||
|
||||
supportsConditions = true;
|
||||
|
||||
conditionVariables = [
|
||||
new ConditionVariable("record", defaultStringOperators ),
|
||||
];
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
|
@ -18,28 +27,48 @@ class DnsMonitorType extends MonitorType {
|
|||
let dnsRes = await dnsResolve(monitor.hostname, monitor.dns_resolve_server, monitor.port, monitor.dns_resolve_type);
|
||||
heartbeat.ping = dayjs().valueOf() - startTime;
|
||||
|
||||
if (monitor.dns_resolve_type === "A" || monitor.dns_resolve_type === "AAAA" || monitor.dns_resolve_type === "TXT" || monitor.dns_resolve_type === "PTR") {
|
||||
dnsMessage += "Records: ";
|
||||
dnsMessage += dnsRes.join(" | ");
|
||||
} else if (monitor.dns_resolve_type === "CNAME" || monitor.dns_resolve_type === "PTR") {
|
||||
dnsMessage += dnsRes[0];
|
||||
} else if (monitor.dns_resolve_type === "CAA") {
|
||||
dnsMessage += dnsRes[0].issue;
|
||||
} else if (monitor.dns_resolve_type === "MX") {
|
||||
dnsRes.forEach(record => {
|
||||
dnsMessage += `Hostname: ${record.exchange} - Priority: ${record.priority} | `;
|
||||
});
|
||||
dnsMessage = dnsMessage.slice(0, -2);
|
||||
} else if (monitor.dns_resolve_type === "NS") {
|
||||
dnsMessage += "Servers: ";
|
||||
dnsMessage += dnsRes.join(" | ");
|
||||
} else if (monitor.dns_resolve_type === "SOA") {
|
||||
dnsMessage += `NS-Name: ${dnsRes.nsname} | Hostmaster: ${dnsRes.hostmaster} | Serial: ${dnsRes.serial} | Refresh: ${dnsRes.refresh} | Retry: ${dnsRes.retry} | Expire: ${dnsRes.expire} | MinTTL: ${dnsRes.minttl}`;
|
||||
} else if (monitor.dns_resolve_type === "SRV") {
|
||||
dnsRes.forEach(record => {
|
||||
dnsMessage += `Name: ${record.name} | Port: ${record.port} | Priority: ${record.priority} | Weight: ${record.weight} | `;
|
||||
});
|
||||
dnsMessage = dnsMessage.slice(0, -2);
|
||||
const conditions = ConditionExpressionGroup.fromMonitor(monitor);
|
||||
let conditionsResult = true;
|
||||
const handleConditions = (data) => conditions ? evaluateExpressionGroup(conditions, data) : true;
|
||||
|
||||
switch (monitor.dns_resolve_type) {
|
||||
case "A":
|
||||
case "AAAA":
|
||||
case "TXT":
|
||||
case "PTR":
|
||||
dnsMessage = `Records: ${dnsRes.join(" | ")}`;
|
||||
conditionsResult = dnsRes.some(record => handleConditions({ record }));
|
||||
break;
|
||||
|
||||
case "CNAME":
|
||||
dnsMessage = dnsRes[0];
|
||||
conditionsResult = handleConditions({ record: dnsRes[0] });
|
||||
break;
|
||||
|
||||
case "CAA":
|
||||
dnsMessage = dnsRes[0].issue;
|
||||
conditionsResult = handleConditions({ record: dnsRes[0].issue });
|
||||
break;
|
||||
|
||||
case "MX":
|
||||
dnsMessage = dnsRes.map(record => `Hostname: ${record.exchange} - Priority: ${record.priority}`).join(" | ");
|
||||
conditionsResult = dnsRes.some(record => handleConditions({ record: record.exchange }));
|
||||
break;
|
||||
|
||||
case "NS":
|
||||
dnsMessage = `Servers: ${dnsRes.join(" | ")}`;
|
||||
conditionsResult = dnsRes.some(record => handleConditions({ record }));
|
||||
break;
|
||||
|
||||
case "SOA":
|
||||
dnsMessage = `NS-Name: ${dnsRes.nsname} | Hostmaster: ${dnsRes.hostmaster} | Serial: ${dnsRes.serial} | Refresh: ${dnsRes.refresh} | Retry: ${dnsRes.retry} | Expire: ${dnsRes.expire} | MinTTL: ${dnsRes.minttl}`;
|
||||
conditionsResult = handleConditions({ record: dnsRes.nsname });
|
||||
break;
|
||||
|
||||
case "SRV":
|
||||
dnsMessage = dnsRes.map(record => `Name: ${record.name} | Port: ${record.port} | Priority: ${record.priority} | Weight: ${record.weight}`).join(" | ");
|
||||
conditionsResult = dnsRes.some(record => handleConditions({ record: record.name }));
|
||||
break;
|
||||
}
|
||||
|
||||
if (monitor.dns_last_result !== dnsMessage && dnsMessage !== undefined) {
|
||||
|
@ -47,7 +76,7 @@ class DnsMonitorType extends MonitorType {
|
|||
}
|
||||
|
||||
heartbeat.msg = dnsMessage;
|
||||
heartbeat.status = UP;
|
||||
heartbeat.status = conditionsResult ? UP : DOWN;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ const { MongoClient } = require("mongodb");
|
|||
const jsonata = require("jsonata");
|
||||
|
||||
class MongodbMonitorType extends MonitorType {
|
||||
|
||||
name = "mongodb";
|
||||
|
||||
/**
|
||||
|
@ -49,8 +48,7 @@ class MongodbMonitorType extends MonitorType {
|
|||
* Connect to and run MongoDB command on a MongoDB database
|
||||
* @param {string} connectionString The database connection string
|
||||
* @param {object} command MongoDB command to run on the database
|
||||
* @returns {Promise<(string[] | object[] | object)>} Response from
|
||||
* server
|
||||
* @returns {Promise<(string[] | object[] | object)>} Response from server
|
||||
*/
|
||||
async runMongodbCommand(connectionString, command) {
|
||||
let client = await MongoClient.connect(connectionString);
|
||||
|
|
|
@ -1,6 +1,19 @@
|
|||
class MonitorType {
|
||||
name = undefined;
|
||||
|
||||
/**
|
||||
* Whether or not this type supports monitor conditions. Controls UI visibility in monitor form.
|
||||
* @type {boolean}
|
||||
*/
|
||||
supportsConditions = false;
|
||||
|
||||
/**
|
||||
* Variables supported by this type. e.g. an HTTP type could have a "response_code" variable to test against.
|
||||
* This property controls the choices displayed in the monitor edit form.
|
||||
* @type {import("../monitor-conditions/variables").ConditionVariable[]}
|
||||
*/
|
||||
conditionVariables = [];
|
||||
|
||||
/**
|
||||
* Run the monitoring check on the given monitor
|
||||
* @param {Monitor} monitor Monitor to check
|
||||
|
@ -11,7 +24,6 @@ class MonitorType {
|
|||
async check(monitor, heartbeat, server) {
|
||||
throw new Error("You need to override check()");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
@ -4,15 +4,10 @@ const mqtt = require("mqtt");
|
|||
const jsonata = require("jsonata");
|
||||
|
||||
class MqttMonitorType extends MonitorType {
|
||||
|
||||
name = "mqtt";
|
||||
|
||||
/**
|
||||
* Run the monitoring check on the MQTT monitor
|
||||
* @param {Monitor} monitor Monitor to check
|
||||
* @param {Heartbeat} heartbeat Monitor heartbeat to update
|
||||
* @param {UptimeKumaServer} server Uptime Kuma server
|
||||
* @returns {Promise<void>}
|
||||
* @inheritdoc
|
||||
*/
|
||||
async check(monitor, heartbeat, server) {
|
||||
const receivedMessage = await this.mqttAsync(monitor.hostname, monitor.mqttTopic, {
|
||||
|
|
63
server/monitor-types/snmp.js
Normal file
63
server/monitor-types/snmp.js
Normal file
|
@ -0,0 +1,63 @@
|
|||
const { MonitorType } = require("./monitor-type");
|
||||
const { UP, log, evaluateJsonQuery } = require("../../src/util");
|
||||
const snmp = require("net-snmp");
|
||||
|
||||
class SNMPMonitorType extends MonitorType {
|
||||
name = "snmp";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async check(monitor, heartbeat, _server) {
|
||||
let session;
|
||||
try {
|
||||
const sessionOptions = {
|
||||
port: monitor.port || "161",
|
||||
retries: monitor.maxretries,
|
||||
timeout: monitor.timeout * 1000,
|
||||
version: snmp.Version[monitor.snmpVersion],
|
||||
};
|
||||
session = snmp.createSession(monitor.hostname, monitor.radiusPassword, sessionOptions);
|
||||
|
||||
// Handle errors during session creation
|
||||
session.on("error", (error) => {
|
||||
throw new Error(`Error creating SNMP session: ${error.message}`);
|
||||
});
|
||||
|
||||
const varbinds = await new Promise((resolve, reject) => {
|
||||
session.get([ monitor.snmpOid ], (error, varbinds) => {
|
||||
error ? reject(error) : resolve(varbinds);
|
||||
});
|
||||
});
|
||||
log.debug("monitor", `SNMP: Received varbinds (Type: ${snmp.ObjectType[varbinds[0].type]} Value: ${varbinds[0].value})`);
|
||||
|
||||
if (varbinds.length === 0) {
|
||||
throw new Error(`No varbinds returned from SNMP session (OID: ${monitor.snmpOid})`);
|
||||
}
|
||||
|
||||
if (varbinds[0].type === snmp.ObjectType.NoSuchInstance) {
|
||||
throw new Error(`The SNMP query returned that no instance exists for OID ${monitor.snmpOid}`);
|
||||
}
|
||||
|
||||
// We restrict querying to one OID per monitor, therefore `varbinds[0]` will always contain the value we're interested in.
|
||||
const value = varbinds[0].value;
|
||||
|
||||
const { status, response } = await evaluateJsonQuery(value, monitor.jsonPath, monitor.jsonPathOperator, monitor.expectedValue);
|
||||
|
||||
if (status) {
|
||||
heartbeat.status = UP;
|
||||
heartbeat.msg = `JSON query passes (comparing ${response} ${monitor.jsonPathOperator} ${monitor.expectedValue})`;
|
||||
} else {
|
||||
throw new Error(`JSON query does not pass (comparing ${response} ${monitor.jsonPathOperator} ${monitor.expectedValue})`);
|
||||
}
|
||||
} finally {
|
||||
if (session) {
|
||||
session.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
SNMPMonitorType,
|
||||
};
|
|
@ -2,23 +2,13 @@ const { MonitorType } = require("./monitor-type");
|
|||
const { UP } = require("../../src/util");
|
||||
const childProcessAsync = require("promisify-child-process");
|
||||
|
||||
/**
|
||||
* A TailscalePing class extends the MonitorType.
|
||||
* It runs Tailscale ping to monitor the status of a specific node.
|
||||
*/
|
||||
class TailscalePing extends MonitorType {
|
||||
|
||||
name = "tailscale-ping";
|
||||
|
||||
/**
|
||||
* Checks the ping status of the URL associated with the monitor.
|
||||
* It then parses the Tailscale ping command output to update the heatrbeat.
|
||||
* @param {object} monitor The monitor object associated with the check.
|
||||
* @param {object} heartbeat The heartbeat object to update.
|
||||
* @returns {Promise<void>}
|
||||
* @throws Error if checking Tailscale ping encounters any error
|
||||
* @inheritdoc
|
||||
*/
|
||||
async check(monitor, heartbeat) {
|
||||
async check(monitor, heartbeat, _server) {
|
||||
try {
|
||||
let tailscaleOutput = await this.runTailscalePing(monitor.hostname, monitor.interval);
|
||||
this.parseTailscaleOutput(tailscaleOutput, heartbeat);
|
||||
|
|
|
@ -33,26 +33,6 @@ class Discord extends NotificationProvider {
|
|||
return okMsg;
|
||||
}
|
||||
|
||||
let address;
|
||||
|
||||
switch (monitorJSON["type"]) {
|
||||
case "ping":
|
||||
address = monitorJSON["hostname"];
|
||||
break;
|
||||
case "port":
|
||||
case "dns":
|
||||
case "gamedig":
|
||||
case "steam":
|
||||
address = monitorJSON["hostname"];
|
||||
if (monitorJSON["port"]) {
|
||||
address += ":" + monitorJSON["port"];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
address = monitorJSON["url"];
|
||||
break;
|
||||
}
|
||||
|
||||
// If heartbeatJSON is not null, we go into the normal alerting loop.
|
||||
if (heartbeatJSON["status"] === DOWN) {
|
||||
let discorddowndata = {
|
||||
|
@ -68,7 +48,7 @@ class Discord extends NotificationProvider {
|
|||
},
|
||||
{
|
||||
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
|
||||
value: monitorJSON["type"] === "push" ? "Heartbeat" : address,
|
||||
value: this.extractAdress(monitorJSON),
|
||||
},
|
||||
{
|
||||
name: `Time (${heartbeatJSON["timezone"]})`,
|
||||
|
@ -105,7 +85,7 @@ class Discord extends NotificationProvider {
|
|||
},
|
||||
{
|
||||
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
|
||||
value: monitorJSON["type"] === "push" ? "Heartbeat" : address,
|
||||
value: this.extractAdress(monitorJSON),
|
||||
},
|
||||
{
|
||||
name: `Time (${heartbeatJSON["timezone"]})`,
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
const { log } = require("../../src/util");
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const {
|
||||
relayInit,
|
||||
|
@ -12,16 +11,7 @@ const {
|
|||
// polyfills for node versions
|
||||
const semver = require("semver");
|
||||
const nodeVersion = process.version;
|
||||
if (semver.lt(nodeVersion, "16.0.0")) {
|
||||
log.warn("notification", "Node <= 16 is unsupported for nostr, sorry :(");
|
||||
} else if (semver.lt(nodeVersion, "18.0.0")) {
|
||||
// polyfills for node 16
|
||||
global.crypto = require("crypto");
|
||||
global.WebSocket = require("isomorphic-ws");
|
||||
if (typeof crypto !== "undefined" && !crypto.subtle && crypto.webcrypto) {
|
||||
crypto.subtle = crypto.webcrypto.subtle;
|
||||
}
|
||||
} else if (semver.lt(nodeVersion, "20.0.0")) {
|
||||
if (semver.lt(nodeVersion, "20.0.0")) {
|
||||
// polyfills for node 18
|
||||
global.crypto = require("crypto");
|
||||
global.WebSocket = require("isomorphic-ws");
|
||||
|
|
|
@ -19,6 +19,36 @@ class NotificationProvider {
|
|||
throw new Error("Have to override Notification.send(...)");
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the address from a monitor JSON object based on its type.
|
||||
* @param {?object} monitorJSON Monitor details (For Up/Down only)
|
||||
* @returns {string} The extracted address based on the monitor type.
|
||||
*/
|
||||
extractAdress(monitorJSON) {
|
||||
if (!monitorJSON) {
|
||||
return "";
|
||||
}
|
||||
switch (monitorJSON["type"]) {
|
||||
case "push":
|
||||
return "Heartbeat";
|
||||
case "ping":
|
||||
return monitorJSON["hostname"];
|
||||
case "port":
|
||||
case "dns":
|
||||
case "gamedig":
|
||||
case "steam":
|
||||
if (monitorJSON["port"]) {
|
||||
return monitorJSON["hostname"] + ":" + monitorJSON["port"];
|
||||
}
|
||||
return monitorJSON["hostname"];
|
||||
default:
|
||||
if (![ "https://", "http://", "" ].includes(monitorJSON["url"])) {
|
||||
return monitorJSON["url"];
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an error
|
||||
* @param {any} error The error to throw
|
||||
|
|
47
server/notification-providers/onesender.js
Normal file
47
server/notification-providers/onesender.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
|
||||
class Onesender extends NotificationProvider {
|
||||
name = "Onesender";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
const okMsg = "Sent Successfully.";
|
||||
|
||||
try {
|
||||
let data = {
|
||||
heartbeat: heartbeatJSON,
|
||||
monitor: monitorJSON,
|
||||
msg,
|
||||
to: notification.onesenderReceiver,
|
||||
type: "text",
|
||||
recipient_type: "individual",
|
||||
text: {
|
||||
body: msg
|
||||
}
|
||||
};
|
||||
if (notification.onesenderTypeReceiver === "private") {
|
||||
data.to = notification.onesenderReceiver + "@s.whatsapp.net";
|
||||
} else {
|
||||
data.recipient_type = "group";
|
||||
data.to = notification.onesenderReceiver + "@g.us";
|
||||
}
|
||||
let config = {
|
||||
headers: {
|
||||
"Authorization": "Bearer " + notification.onesenderToken,
|
||||
}
|
||||
};
|
||||
await axios.post(notification.onesenderURL, data, config);
|
||||
return okMsg;
|
||||
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Onesender;
|
|
@ -1,3 +1,6 @@
|
|||
const { getMonitorRelativeURL } = require("../../src/util");
|
||||
const { setting } = require("../util-server");
|
||||
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
|
||||
|
@ -23,6 +26,12 @@ class Pushover extends NotificationProvider {
|
|||
"html": 1,
|
||||
};
|
||||
|
||||
const baseURL = await setting("primaryBaseURL");
|
||||
if (baseURL && monitorJSON) {
|
||||
data["url"] = baseURL + getMonitorRelativeURL(monitorJSON.id);
|
||||
data["url_title"] = "Link to Monitor";
|
||||
}
|
||||
|
||||
if (notification.pushoverdevice) {
|
||||
data.device = notification.pushoverdevice;
|
||||
}
|
||||
|
|
|
@ -32,28 +32,7 @@ class SevenIO extends NotificationProvider {
|
|||
return okMsg;
|
||||
}
|
||||
|
||||
let address = "";
|
||||
|
||||
switch (monitorJSON["type"]) {
|
||||
case "ping":
|
||||
address = monitorJSON["hostname"];
|
||||
break;
|
||||
case "port":
|
||||
case "dns":
|
||||
case "gamedig":
|
||||
case "steam":
|
||||
address = monitorJSON["hostname"];
|
||||
if (monitorJSON["port"]) {
|
||||
address += ":" + monitorJSON["port"];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (![ "https://", "http://", "" ].includes(monitorJSON["url"])) {
|
||||
address = monitorJSON["url"];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
let address = this.extractAdress(monitorJSON);
|
||||
if (address !== "") {
|
||||
address = `(${address}) `;
|
||||
}
|
||||
|
|
52
server/notification-providers/signl4.js
Normal file
52
server/notification-providers/signl4.js
Normal file
|
@ -0,0 +1,52 @@
|
|||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
const { UP, DOWN } = require("../../src/util");
|
||||
|
||||
class SIGNL4 extends NotificationProvider {
|
||||
name = "SIGNL4";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
const okMsg = "Sent Successfully.";
|
||||
|
||||
try {
|
||||
let data = {
|
||||
heartbeat: heartbeatJSON,
|
||||
monitor: monitorJSON,
|
||||
msg,
|
||||
// Source system
|
||||
"X-S4-SourceSystem": "UptimeKuma",
|
||||
monitorUrl: this.extractAdress(monitorJSON),
|
||||
};
|
||||
|
||||
const config = {
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
};
|
||||
|
||||
if (heartbeatJSON == null) {
|
||||
// Test alert
|
||||
data.title = "Uptime Kuma Alert";
|
||||
data.message = msg;
|
||||
} else if (heartbeatJSON.status === UP) {
|
||||
data.title = "Uptime Kuma Monitor ✅ Up";
|
||||
data["X-S4-ExternalID"] = "UptimeKuma-" + monitorJSON.monitorID;
|
||||
data["X-S4-Status"] = "resolved";
|
||||
} else if (heartbeatJSON.status === DOWN) {
|
||||
data.title = "Uptime Kuma Monitor 🔴 Down";
|
||||
data["X-S4-ExternalID"] = "UptimeKuma-" + monitorJSON.monitorID;
|
||||
data["X-S4-Status"] = "new";
|
||||
}
|
||||
|
||||
await axios.post(notification.webhookURL, data, config);
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SIGNL4;
|
|
@ -47,7 +47,8 @@ class Slack extends NotificationProvider {
|
|||
|
||||
}
|
||||
|
||||
if (monitorJSON.url) {
|
||||
const address = this.extractAdress(monitorJSON);
|
||||
if (address) {
|
||||
actions.push({
|
||||
"type": "button",
|
||||
"text": {
|
||||
|
@ -55,7 +56,7 @@ class Slack extends NotificationProvider {
|
|||
"text": "Visit site",
|
||||
},
|
||||
"value": "Site",
|
||||
"url": monitorJSON.url,
|
||||
"url": address,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -93,12 +93,7 @@ class SMTP extends NotificationProvider {
|
|||
|
||||
if (monitorJSON !== null) {
|
||||
monitorName = monitorJSON["name"];
|
||||
|
||||
if (monitorJSON["type"] === "http" || monitorJSON["type"] === "keyword" || monitorJSON["type"] === "json-query") {
|
||||
monitorHostnameOrURL = monitorJSON["url"];
|
||||
} else {
|
||||
monitorHostnameOrURL = monitorJSON["hostname"];
|
||||
}
|
||||
monitorHostnameOrURL = this.extractAdress(monitorJSON);
|
||||
}
|
||||
|
||||
let serviceStatus = "⚠️ Test";
|
||||
|
|
|
@ -34,25 +34,7 @@ class Squadcast extends NotificationProvider {
|
|||
data.status = "resolve";
|
||||
}
|
||||
|
||||
let address;
|
||||
switch (monitorJSON["type"]) {
|
||||
case "ping":
|
||||
address = monitorJSON["hostname"];
|
||||
break;
|
||||
case "port":
|
||||
case "dns":
|
||||
case "steam":
|
||||
address = monitorJSON["hostname"];
|
||||
if (monitorJSON["port"]) {
|
||||
address += ":" + monitorJSON["port"];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
address = monitorJSON["url"];
|
||||
break;
|
||||
}
|
||||
|
||||
data.tags["AlertAddress"] = address;
|
||||
data.tags["AlertAddress"] = this.extractAdress(monitorJSON);
|
||||
|
||||
monitorJSON["tags"].forEach(tag => {
|
||||
data.tags[tag["name"]] = {
|
||||
|
|
|
@ -216,21 +216,6 @@ class Teams extends NotificationProvider {
|
|||
return okMsg;
|
||||
}
|
||||
|
||||
let monitorUrl;
|
||||
|
||||
switch (monitorJSON["type"]) {
|
||||
case "http":
|
||||
case "keywork":
|
||||
monitorUrl = monitorJSON["url"];
|
||||
break;
|
||||
case "docker":
|
||||
monitorUrl = monitorJSON["docker_host"];
|
||||
break;
|
||||
default:
|
||||
monitorUrl = monitorJSON["hostname"];
|
||||
break;
|
||||
}
|
||||
|
||||
const baseURL = await setting("primaryBaseURL");
|
||||
let dashboardUrl;
|
||||
if (baseURL) {
|
||||
|
@ -240,7 +225,7 @@ class Teams extends NotificationProvider {
|
|||
const payload = this._notificationPayloadFactory({
|
||||
heartbeatJSON: heartbeatJSON,
|
||||
monitorName: monitorJSON.name,
|
||||
monitorUrl: monitorUrl,
|
||||
monitorUrl: this.extractAdress(monitorJSON),
|
||||
dashboardUrl: dashboardUrl,
|
||||
});
|
||||
|
||||
|
|
77
server/notification-providers/threema.js
Normal file
77
server/notification-providers/threema.js
Normal file
|
@ -0,0 +1,77 @@
|
|||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
|
||||
class Threema extends NotificationProvider {
|
||||
name = "threema";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
const url = "https://msgapi.threema.ch/send_simple";
|
||||
|
||||
const config = {
|
||||
headers: {
|
||||
"Accept": "*/*",
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8"
|
||||
}
|
||||
};
|
||||
|
||||
const data = {
|
||||
from: notification.threemaSenderIdentity,
|
||||
secret: notification.threemaSecret,
|
||||
text: msg
|
||||
};
|
||||
|
||||
switch (notification.threemaRecipientType) {
|
||||
case "identity":
|
||||
data.to = notification.threemaRecipient;
|
||||
break;
|
||||
case "phone":
|
||||
data.phone = notification.threemaRecipient;
|
||||
break;
|
||||
case "email":
|
||||
data.email = notification.threemaRecipient;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported recipient type: ${notification.threemaRecipientType}`);
|
||||
}
|
||||
|
||||
try {
|
||||
await axios.post(url, new URLSearchParams(data), config);
|
||||
return "Threema notification sent successfully.";
|
||||
} catch (error) {
|
||||
const errorMessage = this.handleApiError(error);
|
||||
this.throwGeneralAxiosError(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Threema API errors
|
||||
* @param {any} error The error to handle
|
||||
* @returns {string} Additional error context
|
||||
*/
|
||||
handleApiError(error) {
|
||||
if (!error.response) {
|
||||
return error.message;
|
||||
}
|
||||
switch (error.response.status) {
|
||||
case 400:
|
||||
return "Invalid recipient identity or account not set up for basic mode (400).";
|
||||
case 401:
|
||||
return "Incorrect API identity or secret (401).";
|
||||
case 402:
|
||||
return "No credits remaining (402).";
|
||||
case 404:
|
||||
return "Recipient not found (404).";
|
||||
case 413:
|
||||
return "Message is too long (413).";
|
||||
case 500:
|
||||
return "Temporary internal server error (500).";
|
||||
default:
|
||||
return error.message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Threema;
|
51
server/notification-providers/wpush.js
Normal file
51
server/notification-providers/wpush.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
const { DOWN, UP } = require("../../src/util");
|
||||
|
||||
class WPush extends NotificationProvider {
|
||||
name = "WPush";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
const okMsg = "Sent Successfully.";
|
||||
|
||||
try {
|
||||
const context = {
|
||||
"title": this.checkStatus(heartbeatJSON, monitorJSON),
|
||||
"content": msg,
|
||||
"apikey": notification.wpushAPIkey,
|
||||
"channel": notification.wpushChannel
|
||||
};
|
||||
const result = await axios.post("https://api.wpush.cn/api/v1/send", context);
|
||||
if (result.data.code !== 0) {
|
||||
throw result.data.message;
|
||||
}
|
||||
|
||||
return okMsg;
|
||||
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the formatted title for message
|
||||
* @param {?object} heartbeatJSON Heartbeat details (For Up/Down only)
|
||||
* @param {?object} monitorJSON Monitor details (For Up/Down only)
|
||||
* @returns {string} Formatted title
|
||||
*/
|
||||
checkStatus(heartbeatJSON, monitorJSON) {
|
||||
let title = "UptimeKuma Message";
|
||||
if (heartbeatJSON != null && heartbeatJSON["status"] === UP) {
|
||||
title = "UptimeKuma Monitor Up " + monitorJSON["name"];
|
||||
}
|
||||
if (heartbeatJSON != null && heartbeatJSON["status"] === DOWN) {
|
||||
title = "UptimeKuma Monitor Down " + monitorJSON["name"];
|
||||
}
|
||||
return title;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WPush;
|
|
@ -13,9 +13,9 @@ class ZohoCliq extends NotificationProvider {
|
|||
*/
|
||||
_statusMessageFactory = (status, monitorName) => {
|
||||
if (status === DOWN) {
|
||||
return `🔴 Application [${monitorName}] went down\n`;
|
||||
return `🔴 [${monitorName}] went down\n`;
|
||||
} else if (status === UP) {
|
||||
return `✅ Application [${monitorName}] is back online\n`;
|
||||
return `### ✅ [${monitorName}] is back online\n`;
|
||||
}
|
||||
return "Notification\n";
|
||||
};
|
||||
|
@ -46,16 +46,11 @@ class ZohoCliq extends NotificationProvider {
|
|||
monitorUrl,
|
||||
}) => {
|
||||
const payload = [];
|
||||
payload.push("### Uptime Kuma\n");
|
||||
payload.push(this._statusMessageFactory(status, monitorName));
|
||||
payload.push(`*Description:* ${monitorMessage}`);
|
||||
|
||||
if (monitorName) {
|
||||
payload.push(`*Monitor:* ${monitorName}`);
|
||||
}
|
||||
|
||||
if (monitorUrl && monitorUrl !== "https://") {
|
||||
payload.push(`*URL:* [${monitorUrl}](${monitorUrl})`);
|
||||
payload.push(`*URL:* ${monitorUrl}`);
|
||||
}
|
||||
|
||||
return payload;
|
||||
|
@ -87,24 +82,10 @@ class ZohoCliq extends NotificationProvider {
|
|||
return okMsg;
|
||||
}
|
||||
|
||||
let url;
|
||||
switch (monitorJSON["type"]) {
|
||||
case "http":
|
||||
case "keywork":
|
||||
url = monitorJSON["url"];
|
||||
break;
|
||||
case "docker":
|
||||
url = monitorJSON["docker_host"];
|
||||
break;
|
||||
default:
|
||||
url = monitorJSON["hostname"];
|
||||
break;
|
||||
}
|
||||
|
||||
const payload = this._notificationPayloadFactory({
|
||||
monitorMessage: heartbeatJSON.msg,
|
||||
monitorName: monitorJSON.name,
|
||||
monitorUrl: url,
|
||||
monitorUrl: this.extractAdress(monitorJSON),
|
||||
status: heartbeatJSON.status
|
||||
});
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ const Pushy = require("./notification-providers/pushy");
|
|||
const RocketChat = require("./notification-providers/rocket-chat");
|
||||
const SerwerSMS = require("./notification-providers/serwersms");
|
||||
const Signal = require("./notification-providers/signal");
|
||||
const SIGNL4 = require("./notification-providers/signl4");
|
||||
const Slack = require("./notification-providers/slack");
|
||||
const SMSPartner = require("./notification-providers/smspartner");
|
||||
const SMSEagle = require("./notification-providers/smseagle");
|
||||
|
@ -51,6 +52,7 @@ const Stackfield = require("./notification-providers/stackfield");
|
|||
const Teams = require("./notification-providers/teams");
|
||||
const TechulusPush = require("./notification-providers/techulus-push");
|
||||
const Telegram = require("./notification-providers/telegram");
|
||||
const Threema = require("./notification-providers/threema");
|
||||
const Twilio = require("./notification-providers/twilio");
|
||||
const Splunk = require("./notification-providers/splunk");
|
||||
const Webhook = require("./notification-providers/webhook");
|
||||
|
@ -63,6 +65,8 @@ const SevenIO = require("./notification-providers/sevenio");
|
|||
const Whapi = require("./notification-providers/whapi");
|
||||
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");
|
||||
|
||||
class Notification {
|
||||
|
||||
|
@ -110,6 +114,7 @@ class Notification {
|
|||
new Ntfy(),
|
||||
new Octopush(),
|
||||
new OneBot(),
|
||||
new Onesender(),
|
||||
new Opsgenie(),
|
||||
new PagerDuty(),
|
||||
new FlashDuty(),
|
||||
|
@ -123,6 +128,7 @@ class Notification {
|
|||
new ServerChan(),
|
||||
new SerwerSMS(),
|
||||
new Signal(),
|
||||
new SIGNL4(),
|
||||
new SMSManager(),
|
||||
new SMSPartner(),
|
||||
new Slack(),
|
||||
|
@ -133,6 +139,7 @@ class Notification {
|
|||
new Teams(),
|
||||
new TechulusPush(),
|
||||
new Telegram(),
|
||||
new Threema(),
|
||||
new Twilio(),
|
||||
new Splunk(),
|
||||
new Webhook(),
|
||||
|
@ -143,6 +150,7 @@ class Notification {
|
|||
new Whapi(),
|
||||
new GtxMessaging(),
|
||||
new Cellsynt(),
|
||||
new Wpush(),
|
||||
];
|
||||
for (let item of list) {
|
||||
if (! item.name) {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
const { R } = require("redbean-node");
|
||||
const PrometheusClient = require("prom-client");
|
||||
const { log } = require("../src/util");
|
||||
|
||||
|
@ -9,36 +10,102 @@ const commonLabels = [
|
|||
"monitor_port",
|
||||
];
|
||||
|
||||
const monitorCertDaysRemaining = new PrometheusClient.Gauge({
|
||||
name: "monitor_cert_days_remaining",
|
||||
help: "The number of days remaining until the certificate expires",
|
||||
labelNames: commonLabels
|
||||
});
|
||||
|
||||
const monitorCertIsValid = new PrometheusClient.Gauge({
|
||||
name: "monitor_cert_is_valid",
|
||||
help: "Is the certificate still valid? (1 = Yes, 0= No)",
|
||||
labelNames: commonLabels
|
||||
});
|
||||
const monitorResponseTime = new PrometheusClient.Gauge({
|
||||
name: "monitor_response_time",
|
||||
help: "Monitor Response Time (ms)",
|
||||
labelNames: commonLabels
|
||||
});
|
||||
|
||||
const monitorStatus = new PrometheusClient.Gauge({
|
||||
name: "monitor_status",
|
||||
help: "Monitor Status (1 = UP, 0= DOWN, 2= PENDING, 3= MAINTENANCE)",
|
||||
labelNames: commonLabels
|
||||
});
|
||||
|
||||
class Prometheus {
|
||||
monitorLabelValues = {};
|
||||
|
||||
/**
|
||||
* @param {object} monitor Monitor object to monitor
|
||||
* Metric: monitor_cert_days_remaining
|
||||
* @type {PrometheusClient.Gauge<string> | null}
|
||||
*/
|
||||
constructor(monitor) {
|
||||
static monitorCertDaysRemaining = null;
|
||||
|
||||
/**
|
||||
* Metric: monitor_cert_is_valid
|
||||
* @type {PrometheusClient.Gauge<string> | null}
|
||||
*/
|
||||
static monitorCertIsValid = null;
|
||||
|
||||
/**
|
||||
* Metric: monitor_response_time
|
||||
* @type {PrometheusClient.Gauge<string> | null}
|
||||
*/
|
||||
static monitorResponseTime = null;
|
||||
|
||||
/**
|
||||
* Metric: monitor_status
|
||||
* @type {PrometheusClient.Gauge<string> | null}
|
||||
*/
|
||||
static monitorStatus = null;
|
||||
|
||||
/**
|
||||
* All registered metric labels.
|
||||
* @type {string[] | null}
|
||||
*/
|
||||
static monitorLabelNames = null;
|
||||
|
||||
/**
|
||||
* Monitor labels/values combination.
|
||||
* @type {{}}
|
||||
*/
|
||||
monitorLabelValues;
|
||||
|
||||
/**
|
||||
* Initialize metrics and get all label names the first time called.
|
||||
* @returns {void}
|
||||
*/
|
||||
static async initMetrics() {
|
||||
if (!this.monitorLabelNames) {
|
||||
let labelNames = await R.getCol("SELECT name FROM tag");
|
||||
this.monitorLabelNames = [ ...commonLabels, ...labelNames ];
|
||||
}
|
||||
if (!this.monitorCertDaysRemaining) {
|
||||
this.monitorCertDaysRemaining = new PrometheusClient.Gauge({
|
||||
name: "monitor_cert_days_remaining",
|
||||
help: "The number of days remaining until the certificate expires",
|
||||
labelNames: this.monitorLabelNames
|
||||
});
|
||||
}
|
||||
if (!this.monitorCertIsValid) {
|
||||
this.monitorCertIsValid = new PrometheusClient.Gauge({
|
||||
name: "monitor_cert_is_valid",
|
||||
help: "Is the certificate still valid? (1 = Yes, 0 = No)",
|
||||
labelNames: this.monitorLabelNames
|
||||
});
|
||||
}
|
||||
if (!this.monitorResponseTime) {
|
||||
this.monitorResponseTime = new PrometheusClient.Gauge({
|
||||
name: "monitor_response_time",
|
||||
help: "Monitor Response Time (ms)",
|
||||
labelNames: this.monitorLabelNames
|
||||
});
|
||||
}
|
||||
if (!this.monitorStatus) {
|
||||
this.monitorStatus = new PrometheusClient.Gauge({
|
||||
name: "monitor_status",
|
||||
help: "Monitor Status (1 = UP, 0 = DOWN, 2 = PENDING, 3 = MAINTENANCE)",
|
||||
labelNames: this.monitorLabelNames
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper to create a `Prometheus` instance and ensure metrics are initialized.
|
||||
* @param {Monitor} monitor Monitor object to monitor
|
||||
* @returns {Promise<Prometheus>} `Prometheus` instance
|
||||
*/
|
||||
static async createAndInitMetrics(monitor) {
|
||||
await Prometheus.initMetrics();
|
||||
let tags = await monitor.getTags();
|
||||
return new Prometheus(monitor, tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a prometheus metric instance.
|
||||
*
|
||||
* Note: Make sure to call `Prometheus.initMetrics()` once prior creating Prometheus instances.
|
||||
* @param {Monitor} monitor Monitor object to monitor
|
||||
* @param {Promise<LooseObject<any>[]>} tags Tags of the monitor
|
||||
*/
|
||||
constructor(monitor, tags) {
|
||||
this.monitorLabelValues = {
|
||||
monitor_name: monitor.name,
|
||||
monitor_type: monitor.type,
|
||||
|
@ -46,6 +113,12 @@ class Prometheus {
|
|||
monitor_hostname: monitor.hostname,
|
||||
monitor_port: monitor.port
|
||||
};
|
||||
Object.values(tags)
|
||||
// only label names that were known at first metric creation.
|
||||
.filter(tag => Prometheus.monitorLabelNames.includes(tag.name))
|
||||
.forEach(tag => {
|
||||
this.monitorLabelValues[tag.name] = tag.value;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -55,7 +128,6 @@ class Prometheus {
|
|||
* @returns {void}
|
||||
*/
|
||||
update(heartbeat, tlsInfo) {
|
||||
|
||||
if (typeof tlsInfo !== "undefined") {
|
||||
try {
|
||||
let isValid;
|
||||
|
@ -64,7 +136,7 @@ class Prometheus {
|
|||
} else {
|
||||
isValid = 0;
|
||||
}
|
||||
monitorCertIsValid.set(this.monitorLabelValues, isValid);
|
||||
Prometheus.monitorCertIsValid.set(this.monitorLabelValues, isValid);
|
||||
} catch (e) {
|
||||
log.error("prometheus", "Caught error");
|
||||
log.error("prometheus", e);
|
||||
|
@ -72,7 +144,7 @@ class Prometheus {
|
|||
|
||||
try {
|
||||
if (tlsInfo.certInfo != null) {
|
||||
monitorCertDaysRemaining.set(this.monitorLabelValues, tlsInfo.certInfo.daysRemaining);
|
||||
Prometheus.monitorCertDaysRemaining.set(this.monitorLabelValues, tlsInfo.certInfo.daysRemaining);
|
||||
}
|
||||
} catch (e) {
|
||||
log.error("prometheus", "Caught error");
|
||||
|
@ -82,7 +154,7 @@ class Prometheus {
|
|||
|
||||
if (heartbeat) {
|
||||
try {
|
||||
monitorStatus.set(this.monitorLabelValues, heartbeat.status);
|
||||
Prometheus.monitorStatus.set(this.monitorLabelValues, heartbeat.status);
|
||||
} catch (e) {
|
||||
log.error("prometheus", "Caught error");
|
||||
log.error("prometheus", e);
|
||||
|
@ -90,10 +162,10 @@ class Prometheus {
|
|||
|
||||
try {
|
||||
if (typeof heartbeat.ping === "number") {
|
||||
monitorResponseTime.set(this.monitorLabelValues, heartbeat.ping);
|
||||
Prometheus.monitorResponseTime.set(this.monitorLabelValues, heartbeat.ping);
|
||||
} else {
|
||||
// Is it good?
|
||||
monitorResponseTime.set(this.monitorLabelValues, -1);
|
||||
Prometheus.monitorResponseTime.set(this.monitorLabelValues, -1);
|
||||
}
|
||||
} catch (e) {
|
||||
log.error("prometheus", "Caught error");
|
||||
|
@ -108,10 +180,10 @@ class Prometheus {
|
|||
*/
|
||||
remove() {
|
||||
try {
|
||||
monitorCertDaysRemaining.remove(this.monitorLabelValues);
|
||||
monitorCertIsValid.remove(this.monitorLabelValues);
|
||||
monitorResponseTime.remove(this.monitorLabelValues);
|
||||
monitorStatus.remove(this.monitorLabelValues);
|
||||
Prometheus.monitorCertDaysRemaining?.remove(this.monitorLabelValues);
|
||||
Prometheus.monitorCertIsValid?.remove(this.monitorLabelValues);
|
||||
Prometheus.monitorResponseTime?.remove(this.monitorLabelValues);
|
||||
Prometheus.monitorStatus?.remove(this.monitorLabelValues);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
|
|
@ -232,8 +232,8 @@ router.get("/api/badge/:id/uptime/:duration?", cache("5 minutes"), async (reques
|
|||
let requestedDuration = request.params.duration !== undefined ? request.params.duration : "24h";
|
||||
const overrideValue = value && parseFloat(value);
|
||||
|
||||
if (requestedDuration === "24") {
|
||||
requestedDuration = "24h";
|
||||
if (/^[0-9]+$/.test(requestedDuration)) {
|
||||
requestedDuration = `${requestedDuration}h`;
|
||||
}
|
||||
|
||||
let publicMonitor = await R.getRow(`
|
||||
|
@ -265,7 +265,7 @@ router.get("/api/badge/:id/uptime/:duration?", cache("5 minutes"), async (reques
|
|||
// build a label string. If a custom label is given, override the default one (requestedDuration)
|
||||
badgeValues.label = filterAndJoin([
|
||||
labelPrefix,
|
||||
label ?? `Uptime (${requestedDuration}${labelSuffix})`,
|
||||
label ?? `Uptime (${requestedDuration.slice(0, -1)}${labelSuffix})`,
|
||||
]);
|
||||
badgeValues.message = filterAndJoin([ prefix, cleanUptime, suffix ]);
|
||||
}
|
||||
|
@ -302,8 +302,8 @@ router.get("/api/badge/:id/ping/:duration?", cache("5 minutes"), async (request,
|
|||
let requestedDuration = request.params.duration !== undefined ? request.params.duration : "24h";
|
||||
const overrideValue = value && parseFloat(value);
|
||||
|
||||
if (requestedDuration === "24") {
|
||||
requestedDuration = "24h";
|
||||
if (/^[0-9]+$/.test(requestedDuration)) {
|
||||
requestedDuration = `${requestedDuration}h`;
|
||||
}
|
||||
|
||||
// Check if monitor is public
|
||||
|
@ -325,7 +325,7 @@ router.get("/api/badge/:id/ping/:duration?", cache("5 minutes"), async (request,
|
|||
// use a given, custom labelColor or use the default badge label color (defined by badge-maker)
|
||||
badgeValues.labelColor = labelColor ?? "";
|
||||
// build a lable string. If a custom label is given, override the default one (requestedDuration)
|
||||
badgeValues.label = filterAndJoin([ labelPrefix, label ?? `Avg. Ping (${requestedDuration}${labelSuffix})` ]);
|
||||
badgeValues.label = filterAndJoin([ labelPrefix, label ?? `Avg. Ping (${requestedDuration.slice(0, -1)}${labelSuffix})` ]);
|
||||
badgeValues.message = filterAndJoin([ prefix, avgPing, suffix ]);
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,11 @@ router.get("/status/:slug", cache("5 minutes"), async (request, response) => {
|
|||
await StatusPage.handleStatusPageResponse(response, server.indexHTML, slug);
|
||||
});
|
||||
|
||||
router.get("/status/:slug/rss", cache("5 minutes"), async (request, response) => {
|
||||
let slug = request.params.slug;
|
||||
await StatusPage.handleStatusPageRSSResponse(response, slug);
|
||||
});
|
||||
|
||||
router.get("/status", cache("5 minutes"), async (request, response) => {
|
||||
let slug = "default";
|
||||
await StatusPage.handleStatusPageResponse(response, server.indexHTML, slug);
|
||||
|
|
|
@ -19,7 +19,7 @@ const nodeVersion = process.versions.node;
|
|||
|
||||
// Get the required Node.js version from package.json
|
||||
const requiredNodeVersions = require("../package.json").engines.node;
|
||||
const bannedNodeVersions = " < 14 || 20.0.* || 20.1.* || 20.2.* || 20.3.* ";
|
||||
const bannedNodeVersions = " < 18 || 20.0.* || 20.1.* || 20.2.* || 20.3.* ";
|
||||
console.log(`Your Node.js version: ${nodeVersion}`);
|
||||
|
||||
const semver = require("semver");
|
||||
|
@ -132,9 +132,9 @@ const twoFAVerifyOptions = {
|
|||
const testMode = !!args["test"] || false;
|
||||
|
||||
// Must be after io instantiation
|
||||
const { sendNotificationList, sendHeartbeatList, sendInfo, sendProxyList, sendDockerHostList, sendAPIKeyList, sendRemoteBrowserList } = require("./client");
|
||||
const { sendNotificationList, sendHeartbeatList, sendInfo, sendProxyList, sendDockerHostList, sendAPIKeyList, sendRemoteBrowserList, sendMonitorTypeList } = require("./client");
|
||||
const { statusPageSocketHandler } = require("./socket-handlers/status-page-socket-handler");
|
||||
const databaseSocketHandler = require("./socket-handlers/database-socket-handler");
|
||||
const { databaseSocketHandler } = require("./socket-handlers/database-socket-handler");
|
||||
const { remoteBrowserSocketHandler } = require("./socket-handlers/remote-browser-socket-handler");
|
||||
const TwoFA = require("./2fa");
|
||||
const StatusPage = require("./model/status_page");
|
||||
|
@ -246,6 +246,36 @@ let needSetup = false;
|
|||
log.debug("test", request.body);
|
||||
response.send("OK");
|
||||
});
|
||||
|
||||
const fs = require("fs");
|
||||
|
||||
app.get("/_e2e/take-sqlite-snapshot", async (request, response) => {
|
||||
await Database.close();
|
||||
try {
|
||||
fs.cpSync(Database.sqlitePath, `${Database.sqlitePath}.e2e-snapshot`);
|
||||
} catch (err) {
|
||||
throw new Error("Unable to copy SQLite DB.");
|
||||
}
|
||||
await Database.connect();
|
||||
|
||||
response.send("Snapshot taken.");
|
||||
});
|
||||
|
||||
app.get("/_e2e/restore-sqlite-snapshot", async (request, response) => {
|
||||
if (!fs.existsSync(`${Database.sqlitePath}.e2e-snapshot`)) {
|
||||
throw new Error("Snapshot doesn't exist.");
|
||||
}
|
||||
|
||||
await Database.close();
|
||||
try {
|
||||
fs.cpSync(`${Database.sqlitePath}.e2e-snapshot`, Database.sqlitePath);
|
||||
} catch (err) {
|
||||
throw new Error("Unable to copy snapshot file.");
|
||||
}
|
||||
await Database.connect();
|
||||
|
||||
response.send("Snapshot restored.");
|
||||
});
|
||||
}
|
||||
|
||||
// Robots.txt
|
||||
|
@ -686,6 +716,8 @@ let needSetup = false;
|
|||
monitor.kafkaProducerBrokers = JSON.stringify(monitor.kafkaProducerBrokers);
|
||||
monitor.kafkaProducerSaslOptions = JSON.stringify(monitor.kafkaProducerSaslOptions);
|
||||
|
||||
monitor.conditions = JSON.stringify(monitor.conditions);
|
||||
|
||||
bean.import(monitor);
|
||||
bean.user_id = socket.userID;
|
||||
|
||||
|
@ -701,7 +733,7 @@ let needSetup = false;
|
|||
await startMonitor(socket.userID, bean.id);
|
||||
}
|
||||
|
||||
log.info("monitor", `Added Monitor: ${monitor.id} User ID: ${socket.userID}`);
|
||||
log.info("monitor", `Added Monitor: ${bean.id} User ID: ${socket.userID}`);
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
|
@ -826,11 +858,17 @@ let needSetup = false;
|
|||
bean.kafkaProducerAllowAutoTopicCreation = monitor.kafkaProducerAllowAutoTopicCreation;
|
||||
bean.kafkaProducerSaslOptions = JSON.stringify(monitor.kafkaProducerSaslOptions);
|
||||
bean.kafkaProducerMessage = monitor.kafkaProducerMessage;
|
||||
bean.cacheBust = monitor.cacheBust;
|
||||
bean.kafkaProducerSsl = monitor.kafkaProducerSsl;
|
||||
bean.kafkaProducerAllowAutoTopicCreation =
|
||||
monitor.kafkaProducerAllowAutoTopicCreation;
|
||||
bean.gamedigGivenPortOnly = monitor.gamedigGivenPortOnly;
|
||||
bean.remote_browser = monitor.remote_browser;
|
||||
bean.snmpVersion = monitor.snmpVersion;
|
||||
bean.snmpOid = monitor.snmpOid;
|
||||
bean.jsonPathOperator = monitor.jsonPathOperator;
|
||||
bean.timeout = monitor.timeout;
|
||||
bean.conditions = JSON.stringify(monitor.conditions);
|
||||
|
||||
bean.validate();
|
||||
|
||||
|
@ -1636,6 +1674,7 @@ async function afterLogin(socket, user) {
|
|||
sendDockerHostList(socket),
|
||||
sendAPIKeyList(socket),
|
||||
sendRemoteBrowserList(socket),
|
||||
sendMonitorTypeList(socket),
|
||||
]);
|
||||
|
||||
await StatusPage.sendStatusPageList(io, socket);
|
||||
|
|
|
@ -6,7 +6,7 @@ const Database = require("../database");
|
|||
* @param {Socket} socket Socket.io instance
|
||||
* @returns {void}
|
||||
*/
|
||||
module.exports = (socket) => {
|
||||
module.exports.databaseSocketHandler = (socket) => {
|
||||
|
||||
// Post or edit incident
|
||||
socket.on("getDatabaseSize", async (callback) => {
|
||||
|
|
|
@ -29,8 +29,13 @@ function getGameList() {
|
|||
return gameList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for general events
|
||||
* @param {Socket} socket Socket.io instance
|
||||
* @param {UptimeKumaServer} server Uptime Kuma server
|
||||
* @returns {void}
|
||||
*/
|
||||
module.exports.generalSocketHandler = (socket, server) => {
|
||||
|
||||
socket.on("initServerTimezone", async (timezone) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
|
|
|
@ -543,7 +543,9 @@ class UptimeCalculator {
|
|||
if (type === "minute" && num > 24 * 60) {
|
||||
throw new Error("The maximum number of minutes is 1440");
|
||||
}
|
||||
|
||||
if (type === "day" && num > 365) {
|
||||
throw new Error("The maximum number of days is 365");
|
||||
}
|
||||
// Get the current time period key based on the type
|
||||
let key = this.getKey(this.getCurrentDate(), type);
|
||||
|
||||
|
@ -741,20 +743,36 @@ class UptimeCalculator {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the uptime data by duration
|
||||
* @param {'24h'|'30d'|'1y'} duration Only accept 24h, 30d, 1y
|
||||
* Get the uptime data for given duration.
|
||||
* @param {string} duration A string with a number and a unit (m,h,d,w,M,y), such as 24h, 30d, 1y.
|
||||
* @returns {UptimeDataResult} UptimeDataResult
|
||||
* @throws {Error} Invalid duration
|
||||
* @throws {Error} Invalid duration / Unsupported unit
|
||||
*/
|
||||
getDataByDuration(duration) {
|
||||
if (duration === "24h") {
|
||||
return this.get24Hour();
|
||||
} else if (duration === "30d") {
|
||||
return this.get30Day();
|
||||
} else if (duration === "1y") {
|
||||
return this.get1Year();
|
||||
} else {
|
||||
throw new Error("Invalid duration");
|
||||
const durationNumStr = duration.slice(0, -1);
|
||||
|
||||
if (!/^[0-9]+$/.test(durationNumStr)) {
|
||||
throw new Error(`Invalid duration: ${duration}`);
|
||||
}
|
||||
const num = Number(durationNumStr);
|
||||
const unit = duration.slice(-1);
|
||||
|
||||
switch (unit) {
|
||||
case "m":
|
||||
return this.getData(num, "minute");
|
||||
case "h":
|
||||
return this.getData(num, "hour");
|
||||
case "d":
|
||||
return this.getData(num, "day");
|
||||
case "w":
|
||||
return this.getData(7 * num, "day");
|
||||
case "M":
|
||||
return this.getData(30 * num, "day");
|
||||
case "y":
|
||||
return this.getData(365 * num, "day");
|
||||
default:
|
||||
throw new Error(`Unsupported unit (${unit}) for badge duration ${duration}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -113,6 +113,7 @@ class UptimeKumaServer {
|
|||
UptimeKumaServer.monitorTypeList["tailscale-ping"] = new TailscalePing();
|
||||
UptimeKumaServer.monitorTypeList["dns"] = new DnsMonitorType();
|
||||
UptimeKumaServer.monitorTypeList["mqtt"] = new MqttMonitorType();
|
||||
UptimeKumaServer.monitorTypeList["snmp"] = new SNMPMonitorType();
|
||||
UptimeKumaServer.monitorTypeList["mongodb"] = new MongodbMonitorType();
|
||||
|
||||
// Allow all CORS origins (polling) in development
|
||||
|
@ -517,4 +518,5 @@ const { RealBrowserMonitorType } = require("./monitor-types/real-browser-monitor
|
|||
const { TailscalePing } = require("./monitor-types/tailscale-ping");
|
||||
const { DnsMonitorType } = require("./monitor-types/dns");
|
||||
const { MqttMonitorType } = require("./monitor-types/mqtt");
|
||||
const { SNMPMonitorType } = require("./monitor-types/snmp");
|
||||
const { MongodbMonitorType } = require("./monitor-types/mongodb");
|
||||
|
|
|
@ -11,7 +11,7 @@ const mssql = require("mssql");
|
|||
const { Client } = require("pg");
|
||||
const postgresConParse = require("pg-connection-string").parse;
|
||||
const mysql = require("mysql2");
|
||||
const { NtlmClient } = require("axios-ntlm");
|
||||
const { NtlmClient } = require("./modules/axios-ntlm/lib/ntlmClient.js");
|
||||
const { Settings } = require("./settings");
|
||||
const grpc = require("@grpc/grpc-js");
|
||||
const protojs = require("protobufjs");
|
||||
|
|
|
@ -576,6 +576,12 @@ optgroup {
|
|||
outline: none !important;
|
||||
}
|
||||
|
||||
.prism-editor__container {
|
||||
.important {
|
||||
font-weight: var(--bs-body-font-weight) !important;
|
||||
}
|
||||
}
|
||||
|
||||
h5.settings-subheading::after {
|
||||
content: "";
|
||||
display: block;
|
||||
|
|
152
src/components/EditMonitorCondition.vue
Normal file
152
src/components/EditMonitorCondition.vue
Normal file
|
@ -0,0 +1,152 @@
|
|||
<template>
|
||||
<div class="monitor-condition mb-3" data-testid="condition">
|
||||
<button
|
||||
v-if="!isInGroup || !isFirst || !isLast"
|
||||
class="btn btn-outline-danger remove-button"
|
||||
type="button"
|
||||
:aria-label="$t('conditionDelete')"
|
||||
data-testid="remove-condition"
|
||||
@click="remove"
|
||||
>
|
||||
<font-awesome-icon icon="trash" />
|
||||
</button>
|
||||
|
||||
<select v-if="!isFirst" v-model="model.andOr" class="form-select and-or-select" data-testid="condition-and-or">
|
||||
<option value="and">{{ $t("and") }}</option>
|
||||
<option value="or">{{ $t("or") }}</option>
|
||||
</select>
|
||||
|
||||
<select v-model="model.variable" class="form-select" data-testid="condition-variable">
|
||||
<option
|
||||
v-for="variable in conditionVariables"
|
||||
:key="variable.id"
|
||||
:value="variable.id"
|
||||
>
|
||||
{{ $t(variable.id) }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<select v-model="model.operator" class="form-select" data-testid="condition-operator">
|
||||
<option
|
||||
v-for="operator in getVariableOperators(model.variable)"
|
||||
:key="operator.id"
|
||||
:value="operator.id"
|
||||
>
|
||||
{{ $t(operator.caption) }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<input
|
||||
v-model="model.value"
|
||||
type="text"
|
||||
class="form-control"
|
||||
:aria-label="$t('conditionValuePlaceholder')"
|
||||
data-testid="condition-value"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "EditMonitorCondition",
|
||||
|
||||
props: {
|
||||
/**
|
||||
* The monitor condition
|
||||
*/
|
||||
modelValue: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether this is the first condition
|
||||
*/
|
||||
isFirst: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether this is the last condition
|
||||
*/
|
||||
isLast: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether this condition is in a group
|
||||
*/
|
||||
isInGroup: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
|
||||
/**
|
||||
* Variable choices
|
||||
*/
|
||||
conditionVariables: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
emits: [ "update:modelValue", "remove" ],
|
||||
|
||||
computed: {
|
||||
model: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(value) {
|
||||
this.$emit("update:modelValue", value);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
remove() {
|
||||
this.$emit("remove", this.model);
|
||||
},
|
||||
|
||||
getVariableOperators(variableId) {
|
||||
return this.conditionVariables.find(v => v.id === variableId)?.operators ?? [];
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../assets/vars.scss";
|
||||
|
||||
.monitor-condition {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.remove-button {
|
||||
justify-self: flex-end;
|
||||
margin-bottom: 12px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
@container (min-width: 500px) {
|
||||
.monitor-condition {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.remove-button {
|
||||
margin-bottom: 0;
|
||||
margin-left: 10px;
|
||||
order: 100;
|
||||
}
|
||||
|
||||
.and-or-select {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
189
src/components/EditMonitorConditionGroup.vue
Normal file
189
src/components/EditMonitorConditionGroup.vue
Normal file
|
@ -0,0 +1,189 @@
|
|||
<template>
|
||||
<div class="condition-group mb-3" data-testid="condition-group">
|
||||
<div class="d-flex">
|
||||
<select v-if="!isFirst" v-model="model.andOr" class="form-select" style="width: auto;" data-testid="condition-group-and-or">
|
||||
<option value="and">{{ $t("and") }}</option>
|
||||
<option value="or">{{ $t("or") }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="condition-group-inner mt-2 pa-2">
|
||||
<div class="condition-group-conditions">
|
||||
<template v-for="(child, childIndex) in model.children" :key="childIndex">
|
||||
<EditMonitorConditionGroup
|
||||
v-if="child.type === 'group'"
|
||||
v-model="model.children[childIndex]"
|
||||
:is-first="childIndex === 0"
|
||||
:get-new-group="getNewGroup"
|
||||
:get-new-condition="getNewCondition"
|
||||
:condition-variables="conditionVariables"
|
||||
@remove="removeChild"
|
||||
/>
|
||||
<EditMonitorCondition
|
||||
v-else
|
||||
v-model="model.children[childIndex]"
|
||||
:is-first="childIndex === 0"
|
||||
:is-last="childIndex === model.children.length - 1"
|
||||
:is-in-group="true"
|
||||
:condition-variables="conditionVariables"
|
||||
@remove="removeChild"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="condition-group-actions mt-3">
|
||||
<button class="btn btn-outline-secondary me-2" type="button" data-testid="add-condition-button" @click="addCondition">
|
||||
{{ $t("conditionAdd") }}
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary me-2" type="button" data-testid="add-group-button" @click="addGroup">
|
||||
{{ $t("conditionAddGroup") }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-outline-danger"
|
||||
type="button"
|
||||
:aria-label="$t('conditionDeleteGroup')"
|
||||
data-testid="remove-condition-group"
|
||||
@click="remove"
|
||||
>
|
||||
<font-awesome-icon icon="trash" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import EditMonitorCondition from "./EditMonitorCondition.vue";
|
||||
|
||||
export default {
|
||||
name: "EditMonitorConditionGroup",
|
||||
|
||||
components: {
|
||||
EditMonitorCondition,
|
||||
},
|
||||
|
||||
props: {
|
||||
/**
|
||||
* The condition group
|
||||
*/
|
||||
modelValue: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether this is the first condition
|
||||
*/
|
||||
isFirst: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
|
||||
/**
|
||||
* Function to generate a new group model
|
||||
*/
|
||||
getNewGroup: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
|
||||
/**
|
||||
* Function to generate a new condition model
|
||||
*/
|
||||
getNewCondition: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
|
||||
/**
|
||||
* Variable choices
|
||||
*/
|
||||
conditionVariables: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
emits: [ "update:modelValue", "remove" ],
|
||||
|
||||
computed: {
|
||||
model: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(value) {
|
||||
this.$emit("update:modelValue", value);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
addGroup() {
|
||||
const conditions = [ ...this.model.children ];
|
||||
conditions.push(this.getNewGroup());
|
||||
this.model.children = conditions;
|
||||
},
|
||||
|
||||
addCondition() {
|
||||
const conditions = [ ...this.model.children ];
|
||||
conditions.push(this.getNewCondition());
|
||||
this.model.children = conditions;
|
||||
},
|
||||
|
||||
remove() {
|
||||
this.$emit("remove", this.model);
|
||||
},
|
||||
|
||||
removeChild(child) {
|
||||
const idx = this.model.children.indexOf(child);
|
||||
if (idx !== -1) {
|
||||
this.model.children.splice(idx, 1);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../assets/vars.scss";
|
||||
|
||||
.condition-group-inner {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.dark .condition-group-inner {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.condition-group-conditions {
|
||||
container-type: inline-size;
|
||||
}
|
||||
|
||||
.condition-group-actions {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
// Delete button
|
||||
.condition-group-actions > :last-child {
|
||||
margin-left: auto;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
@container (min-width: 400px) {
|
||||
.condition-group-actions {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
// Delete button
|
||||
.condition-group-actions > :last-child {
|
||||
margin-left: auto;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.btn-delete-group {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
149
src/components/EditMonitorConditions.vue
Normal file
149
src/components/EditMonitorConditions.vue
Normal file
|
@ -0,0 +1,149 @@
|
|||
<template>
|
||||
<div class="monitor-conditions">
|
||||
<label class="form-label">{{ $t("Conditions") }}</label>
|
||||
<div class="monitor-conditions-conditions">
|
||||
<template v-for="(condition, conditionIndex) in model" :key="conditionIndex">
|
||||
<EditMonitorConditionGroup
|
||||
v-if="condition.type === 'group'"
|
||||
v-model="model[conditionIndex]"
|
||||
:is-first="conditionIndex === 0"
|
||||
:get-new-group="getNewGroup"
|
||||
:get-new-condition="getNewCondition"
|
||||
:condition-variables="conditionVariables"
|
||||
@remove="removeCondition"
|
||||
/>
|
||||
<EditMonitorCondition
|
||||
v-else
|
||||
v-model="model[conditionIndex]"
|
||||
:is-first="conditionIndex === 0"
|
||||
:is-last="conditionIndex === model.length - 1"
|
||||
:condition-variables="conditionVariables"
|
||||
@remove="removeCondition"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
<div class="monitor-conditions-buttons">
|
||||
<button class="btn btn-outline-secondary me-2" type="button" data-testid="add-condition-button" @click="addCondition">
|
||||
{{ $t("conditionAdd") }}
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary me-2" type="button" data-testid="add-group-button" @click="addGroup">
|
||||
{{ $t("conditionAddGroup") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import EditMonitorConditionGroup from "./EditMonitorConditionGroup.vue";
|
||||
import EditMonitorCondition from "./EditMonitorCondition.vue";
|
||||
|
||||
export default {
|
||||
name: "EditMonitorConditions",
|
||||
|
||||
components: {
|
||||
EditMonitorConditionGroup,
|
||||
EditMonitorCondition,
|
||||
},
|
||||
|
||||
props: {
|
||||
/**
|
||||
* The monitor conditions
|
||||
*/
|
||||
modelValue: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
|
||||
conditionVariables: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
emits: [ "update:modelValue" ],
|
||||
|
||||
computed: {
|
||||
model: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(value) {
|
||||
this.$emit("update:modelValue", value);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
if (this.model.length === 0) {
|
||||
this.addCondition();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
getNewGroup() {
|
||||
return {
|
||||
type: "group",
|
||||
children: [ this.getNewCondition() ],
|
||||
andOr: "and",
|
||||
};
|
||||
},
|
||||
|
||||
getNewCondition() {
|
||||
const firstVariable = this.conditionVariables[0]?.id || null;
|
||||
const firstOperator = this.getVariableOperators(firstVariable)[0] || null;
|
||||
return {
|
||||
type: "expression",
|
||||
variable: firstVariable,
|
||||
operator: firstOperator?.id || null,
|
||||
value: "",
|
||||
andOr: "and",
|
||||
};
|
||||
},
|
||||
|
||||
addGroup() {
|
||||
const conditions = [ ...this.model ];
|
||||
conditions.push(this.getNewGroup());
|
||||
this.$emit("update:modelValue", conditions);
|
||||
},
|
||||
|
||||
addCondition() {
|
||||
const conditions = [ ...this.model ];
|
||||
conditions.push(this.getNewCondition());
|
||||
this.$emit("update:modelValue", conditions);
|
||||
},
|
||||
|
||||
removeCondition(condition) {
|
||||
const conditions = [ ...this.model ];
|
||||
const idx = conditions.indexOf(condition);
|
||||
if (idx !== -1) {
|
||||
conditions.splice(idx, 1);
|
||||
this.$emit("update:modelValue", conditions);
|
||||
}
|
||||
},
|
||||
|
||||
getVariableOperators(variableId) {
|
||||
return this.conditionVariables.find(v => v.id === variableId)?.operators ?? [];
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../assets/vars.scss";
|
||||
|
||||
.monitor-conditions,
|
||||
.monitor-conditions-conditions {
|
||||
container-type: inline-size;
|
||||
}
|
||||
|
||||
.monitor-conditions-buttons {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
@container (min-width: 400px) {
|
||||
.monitor-conditions-buttons {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -14,7 +14,7 @@
|
|||
v-if="!$root.isMobile && size !== 'small' && beatList.length > 4 && $root.styleElapsedTime !== 'none'"
|
||||
class="d-flex justify-content-between align-items-center word" :style="timeStyle"
|
||||
>
|
||||
<div>{{ timeSinceFirstBeat }} ago</div>
|
||||
<div>{{ timeSinceFirstBeat }}</div>
|
||||
<div v-if="$root.styleElapsedTime === 'with-line'" class="connecting-line"></div>
|
||||
<div>{{ timeSinceLastBeat }}</div>
|
||||
</div>
|
||||
|
@ -184,11 +184,11 @@ export default {
|
|||
}
|
||||
|
||||
if (seconds < tolerance) {
|
||||
return "now";
|
||||
return this.$t("now");
|
||||
} else if (seconds < 60 * 60) {
|
||||
return (seconds / 60).toFixed(0) + "m ago";
|
||||
return this.$t("time ago", [ (seconds / 60).toFixed(0) + "m" ]);
|
||||
} else {
|
||||
return (seconds / 60 / 60).toFixed(0) + "h ago";
|
||||
return this.$t("time ago", [ (seconds / 60 / 60).toFixed(0) + "h" ]);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="monitorList" class="monitor-list" :class="{ scrollbar: scrollbar }" :style="monitorListStyle">
|
||||
<div ref="monitorList" class="monitor-list" :class="{ scrollbar: scrollbar }" :style="monitorListStyle" data-testid="monitor-list">
|
||||
<div v-if="Object.keys($root.monitorList).length === 0" class="text-center mt-3">
|
||||
{{ $t("No Monitors, please") }} <router-link to="/add">{{ $t("add one") }}</router-link>
|
||||
</div>
|
||||
|
|
|
@ -43,12 +43,15 @@
|
|||
<div v-if="!isCollapsed" class="childs">
|
||||
<MonitorListItem
|
||||
v-for="(item, index) in sortedChildMonitorList"
|
||||
:key="index" :monitor="item"
|
||||
:key="index"
|
||||
:monitor="item"
|
||||
:isSelectMode="isSelectMode"
|
||||
:isSelected="isSelected"
|
||||
:select="select"
|
||||
:deselect="deselect"
|
||||
:depth="depth + 1"
|
||||
:filter-func="filterFunc"
|
||||
:sort-func="sortFunc"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
|
|
|
@ -135,6 +135,7 @@ export default {
|
|||
"ntfy": "Ntfy",
|
||||
"octopush": "Octopush",
|
||||
"OneBot": "OneBot",
|
||||
"Onesender": "Onesender",
|
||||
"Opsgenie": "Opsgenie",
|
||||
"PagerDuty": "PagerDuty",
|
||||
"PagerTree": "PagerTree",
|
||||
|
@ -144,6 +145,7 @@ export default {
|
|||
"pushy": "Pushy",
|
||||
"rocket.chat": "Rocket.Chat",
|
||||
"signal": "Signal",
|
||||
"SIGNL4": "SIGNL4",
|
||||
"slack": "Slack",
|
||||
"squadcast": "SquadCast",
|
||||
"SMSEagle": "SMSEagle",
|
||||
|
@ -152,6 +154,7 @@ export default {
|
|||
"stackfield": "Stackfield",
|
||||
"teams": "Microsoft Teams",
|
||||
"telegram": "Telegram",
|
||||
"threema": "Threema",
|
||||
"twilio": "Twilio",
|
||||
"Splunk": "Splunk",
|
||||
"webhook": "Webhook",
|
||||
|
@ -177,6 +180,7 @@ export default {
|
|||
"WeCom": "WeCom (企业微信群机器人)",
|
||||
"ServerChan": "ServerChan (Server酱)",
|
||||
"smsc": "SMSC",
|
||||
"WPush": "WPush(wpush.cn)",
|
||||
};
|
||||
|
||||
// Sort by notification name
|
||||
|
|
|
@ -7,12 +7,12 @@
|
|||
:animation="100"
|
||||
>
|
||||
<template #item="group">
|
||||
<div class="mb-5 ">
|
||||
<div class="mb-5" data-testid="group">
|
||||
<!-- Group Title -->
|
||||
<h2 class="group-title">
|
||||
<font-awesome-icon v-if="editMode && showGroupDrag" icon="arrows-alt-v" class="action drag me-3" />
|
||||
<font-awesome-icon v-if="editMode" icon="times" class="action remove me-3" @click="removeGroup(group.index)" />
|
||||
<Editable v-model="group.element.name" :contenteditable="editMode" tag="span" />
|
||||
<Editable v-model="group.element.name" :contenteditable="editMode" tag="span" data-testid="group-name" />
|
||||
</h2>
|
||||
|
||||
<div class="shadow-box monitor-list mt-4 position-relative">
|
||||
|
@ -31,9 +31,9 @@
|
|||
item-key="id"
|
||||
>
|
||||
<template #item="monitor">
|
||||
<div class="item">
|
||||
<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)" />
|
||||
|
@ -45,10 +45,11 @@
|
|||
class="item-name"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
data-testid="monitor-name"
|
||||
>
|
||||
{{ monitor.element.name }}
|
||||
</a>
|
||||
<p v-else class="item-name"> {{ monitor.element.name }} </p>
|
||||
<p v-else class="item-name" data-testid="monitor-name"> {{ monitor.element.name }} </p>
|
||||
|
||||
<span
|
||||
title="Setting"
|
||||
|
@ -66,11 +67,11 @@
|
|||
<Tag :item="{name: $t('Cert Exp.'), value: formattedCertExpiryMessage(monitor), color: certExpiryColor(monitor)}" :size="'sm'" />
|
||||
</div>
|
||||
<div v-if="showTags">
|
||||
<Tag v-for="tag in monitor.element.tags" :key="tag" :item="tag" :size="'sm'" />
|
||||
<Tag v-for="tag in monitor.element.tags" :key="tag" :item="tag" :size="'sm'" data-testid="monitor-tag" />
|
||||
</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>
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
type="button"
|
||||
class="btn btn-outline-secondary btn-add"
|
||||
:disabled="processing"
|
||||
data-testid="add-tag-button"
|
||||
@click.stop="showAddDialog"
|
||||
>
|
||||
<font-awesome-icon class="me-1" icon="plus" /> {{ $t("Add") }}
|
||||
|
@ -59,6 +60,7 @@
|
|||
v-model="newDraftTag.name" class="form-control"
|
||||
:class="{'is-invalid': validateDraftTag.nameInvalid}"
|
||||
:placeholder="$t('Name')"
|
||||
data-testid="tag-name-input"
|
||||
@keydown.enter.prevent="onEnter"
|
||||
/>
|
||||
<div class="invalid-feedback">
|
||||
|
@ -76,6 +78,7 @@
|
|||
label="name"
|
||||
select-label=""
|
||||
deselect-label=""
|
||||
data-testid="tag-color-select"
|
||||
>
|
||||
<template #option="{ option }">
|
||||
<div
|
||||
|
@ -103,6 +106,7 @@
|
|||
v-model="newDraftTag.value" class="form-control"
|
||||
:class="{'is-invalid': validateDraftTag.valueInvalid}"
|
||||
:placeholder="$t('value (optional)')"
|
||||
data-testid="tag-value-input"
|
||||
@keydown.enter.prevent="onEnter"
|
||||
/>
|
||||
<div class="invalid-feedback">
|
||||
|
@ -114,6 +118,7 @@
|
|||
type="button"
|
||||
class="btn btn-secondary float-end"
|
||||
:disabled="processing || validateDraftTag.invalid"
|
||||
data-testid="tag-submit-button"
|
||||
@click.stop="addDraftTag"
|
||||
>
|
||||
{{ $t("Add") }}
|
||||
|
|
81
src/components/notifications/Onesender.vue
Normal file
81
src/components/notifications/Onesender.vue
Normal file
|
@ -0,0 +1,81 @@
|
|||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="host-onesender" class="form-label">{{ $t("Host Onesender") }}</label>
|
||||
<input
|
||||
id="host-onesender"
|
||||
v-model="$parent.notification.onesenderURL"
|
||||
type="url"
|
||||
placeholder="https://xxxxxxxxxxx.com/api/v1/messages"
|
||||
pattern="https?://.+"
|
||||
class="form-control"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="receiver-onesender" class="form-label">{{ $t("Token Onesender") }}</label>
|
||||
<HiddenInput id="receiver-onesender" v-model="$parent.notification.onesenderToken" :required="true" autocomplete="false"></HiddenInput>
|
||||
<i18n-t tag="div" keypath="wayToGetOnesenderUrlandToken" class="form-text">
|
||||
<a href="https://onesender.net/" target="_blank">{{ $t("here") }}</a>
|
||||
</i18n-t>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="webhook-request-body" class="form-label">{{ $t("Recipient Type") }}</label>
|
||||
<select
|
||||
id="webhook-request-body"
|
||||
v-model="$parent.notification.onesenderTypeReceiver"
|
||||
class="form-select"
|
||||
required
|
||||
>
|
||||
<option value="private">{{ $t("Private Number") }}</option>
|
||||
<option value="group">{{ $t("Group ID") }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div v-if="$parent.notification.onesenderTypeReceiver == 'private'" class="form-text">{{ $t("privateOnesenderDesc", ['"application/json"']) }}</div>
|
||||
<div v-else class="form-text">{{ $t("groupOnesenderDesc") }}</div>
|
||||
<div class="mb-3">
|
||||
<input
|
||||
id="type-receiver-onesender"
|
||||
v-model="$parent.notification.onesenderReceiver"
|
||||
type="text"
|
||||
placeholder="628123456789 or 628123456789-34534"
|
||||
class="form-control"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<input
|
||||
id="type-receiver-onesender"
|
||||
v-model="computedReceiverResult"
|
||||
type="text"
|
||||
class="form-control"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HiddenInput from "../HiddenInput.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HiddenInput,
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
computed: {
|
||||
computedReceiverResult() {
|
||||
let receiver = this.$parent.notification.onesenderReceiver;
|
||||
return this.$parent.notification.onesenderTypeReceiver === "private" ? receiver + "@s.whatsapp.net" : receiver + "@g.us";
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
textarea {
|
||||
min-height: 200px;
|
||||
}
|
||||
</style>
|
16
src/components/notifications/SIGNL4.vue
Normal file
16
src/components/notifications/SIGNL4.vue
Normal file
|
@ -0,0 +1,16 @@
|
|||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="signl4-webhook-url" class="form-label">{{ $t("SIGNL4 Webhook URL") }}</label>
|
||||
<input
|
||||
id="signl4-webhook-url"
|
||||
v-model="$parent.notification.webhookURL"
|
||||
type="url"
|
||||
pattern="https?://.+"
|
||||
class="form-control"
|
||||
required
|
||||
/>
|
||||
<i18n-t tag="div" keypath="signl4Docs" class="form-text">
|
||||
<a href="https://docs.signl4.com/integrations/uptime-kuma/uptime-kuma.html" target="_blank">SIGNL4 Docs</a>
|
||||
</i18n-t>
|
||||
</div>
|
||||
</template>
|
|
@ -3,7 +3,7 @@
|
|||
<label for="smspartner-key" class="form-label">{{ $t("API Key") }}</label>
|
||||
<HiddenInput id="smspartner-key" v-model="$parent.notification.smspartnerApikey" :required="true" autocomplete="new-password"></HiddenInput>
|
||||
<div class="form-text">
|
||||
<i18n-t keypath="smspartnerApiurl" as="div" class="form-text">
|
||||
<i18n-t keypath="smspartnerApiurl" tag="div" class="form-text">
|
||||
<a href="https://my.smspartner.fr/dashboard/api" target="_blank">my.smspartner.fr/dashboard/api</a>
|
||||
</i18n-t>
|
||||
</div>
|
||||
|
@ -12,7 +12,7 @@
|
|||
<label for="smspartner-phone-number" class="form-label">{{ $t("smspartnerPhoneNumber") }}</label>
|
||||
<input id="smspartner-phone-number" v-model="$parent.notification.smspartnerPhoneNumber" type="text" minlength="3" maxlength="20" pattern="^[\d+,]+$" class="form-control" required>
|
||||
<div class="form-text">
|
||||
<i18n-t keypath="smspartnerPhoneNumberHelptext" as="div" class="form-text">
|
||||
<i18n-t keypath="smspartnerPhoneNumberHelptext" tag="div" class="form-text">
|
||||
<code>+336xxxxxxxx</code>
|
||||
<code>+496xxxxxxxx</code>
|
||||
<code>,</code>
|
||||
|
|
87
src/components/notifications/Threema.vue
Normal file
87
src/components/notifications/Threema.vue
Normal file
|
@ -0,0 +1,87 @@
|
|||
<template>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="threema-recipient">{{ $t("threemaRecipientType") }}</label>
|
||||
<select
|
||||
id="threema-recipient" v-model="$parent.notification.threemaRecipientType" required
|
||||
class="form-select"
|
||||
>
|
||||
<option value="identity">{{ $t("threemaRecipientTypeIdentity") }}</option>
|
||||
<option value="phone">{{ $t("threemaRecipientTypePhone") }}</option>
|
||||
<option value="email">{{ $t("threemaRecipientTypeEmail") }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div v-if="$parent.notification.threemaRecipientType === 'identity'" class="mb-3">
|
||||
<label class="form-label" for="threema-recipient">{{ $t("threemaRecipient") }} {{ $t("threemaRecipientTypeIdentity") }}</label>
|
||||
<input
|
||||
id="threema-recipient"
|
||||
v-model="$parent.notification.threemaRecipient"
|
||||
class="form-control"
|
||||
minlength="8"
|
||||
maxlength="8"
|
||||
pattern="[A-Z0-9]{8}"
|
||||
required
|
||||
type="text"
|
||||
>
|
||||
<div class="form-text">
|
||||
<p>{{ $t("threemaRecipientTypeIdentityFormat") }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="$parent.notification.threemaRecipientType === 'phone'" class="mb-3">
|
||||
<label class="form-label" for="threema-recipient">{{ $t("threemaRecipient") }} {{ $t("threemaRecipientTypePhone") }}</label>
|
||||
<input
|
||||
id="threema-recipient"
|
||||
v-model="$parent.notification.threemaRecipient"
|
||||
class="form-control"
|
||||
maxlength="15"
|
||||
pattern="\d{1,15}"
|
||||
required
|
||||
type="text"
|
||||
>
|
||||
<div class="form-text">
|
||||
<p>{{ $t("threemaRecipientTypePhoneFormat") }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="$parent.notification.threemaRecipientType === 'email'" class="mb-3">
|
||||
<label class="form-label" for="threema-recipient">{{ $t("threemaRecipient") }} {{ $t("threemaRecipientTypeEmail") }}</label>
|
||||
<input
|
||||
id="threema-recipient"
|
||||
v-model="$parent.notification.threemaRecipient"
|
||||
class="form-control"
|
||||
maxlength="254"
|
||||
required
|
||||
type="email"
|
||||
>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="threema-sender">{{ $t("threemaSenderIdentity") }}</label>
|
||||
<input
|
||||
id="threema-sender"
|
||||
v-model="$parent.notification.threemaSenderIdentity"
|
||||
class="form-control"
|
||||
minlength="8"
|
||||
maxlength="8"
|
||||
pattern="^\*[A-Z0-9]{7}$"
|
||||
required
|
||||
type="text"
|
||||
>
|
||||
<div class="form-text">
|
||||
<p>{{ $t("threemaSenderIdentityFormat") }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="threema-secret">{{ $t("threemaApiAuthenticationSecret") }}</label>
|
||||
<HiddenInput
|
||||
id="threema-secret" v-model="$parent.notification.threemaSecret" required
|
||||
autocomplete="false"
|
||||
></HiddenInput>
|
||||
</div>
|
||||
<i18n-t class="form-text" keypath="wayToGetThreemaGateway" tag="div">
|
||||
<a href="https://threema.ch/en/gateway" target="_blank">{{ $t("here") }}</a>
|
||||
</i18n-t>
|
||||
<i18n-t class="form-text" keypath="threemaBasicModeInfo" tag="div">
|
||||
<a href="https://gateway.threema.ch/en/developer/api" target="_blank">{{ $t("here") }}</a>
|
||||
</i18n-t>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import HiddenInput from "../HiddenInput.vue";
|
||||
</script>
|
31
src/components/notifications/WPush.vue
Normal file
31
src/components/notifications/WPush.vue
Normal file
|
@ -0,0 +1,31 @@
|
|||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="wpush-apikey" class="form-label">WPush {{ $t("API Key") }}</label>
|
||||
<HiddenInput id="wpush-apikey" v-model="$parent.notification.wpushAPIkey" :required="true" autocomplete="new-password" placeholder="WPushxxxxx"></HiddenInput>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="wpush-channel" class="form-label">发送通道</label>
|
||||
<select id="wpush-channel" v-model="$parent.notification.wpushChannel" class="form-select" required>
|
||||
<option value="wechat">微信</option>
|
||||
<option value="sms">短信</option>
|
||||
<option value="mail">邮件</option>
|
||||
<option value="feishu">飞书</option>
|
||||
<option value="dingtalk">钉钉</option>
|
||||
<option value="wechat_work">企业微信</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<i18n-t tag="p" keypath="More info on:">
|
||||
<a href="https://wpush.cn/" rel="noopener noreferrer" target="_blank">https://wpush.cn/</a>
|
||||
</i18n-t>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HiddenInput from "../HiddenInput.vue";
|
||||
export default {
|
||||
components: {
|
||||
HiddenInput,
|
||||
},
|
||||
};
|
||||
</script>
|
|
@ -29,6 +29,7 @@ import Nostr from "./Nostr.vue";
|
|||
import Ntfy from "./Ntfy.vue";
|
||||
import Octopush from "./Octopush.vue";
|
||||
import OneBot from "./OneBot.vue";
|
||||
import Onesender from "./Onesender.vue";
|
||||
import Opsgenie from "./Opsgenie.vue";
|
||||
import PagerDuty from "./PagerDuty.vue";
|
||||
import FlashDuty from "./FlashDuty.vue";
|
||||
|
@ -52,6 +53,7 @@ import STMP from "./SMTP.vue";
|
|||
import Teams from "./Teams.vue";
|
||||
import TechulusPush from "./TechulusPush.vue";
|
||||
import Telegram from "./Telegram.vue";
|
||||
import Threema from "./Threema.vue";
|
||||
import Twilio from "./Twilio.vue";
|
||||
import Webhook from "./Webhook.vue";
|
||||
import WeCom from "./WeCom.vue";
|
||||
|
@ -61,6 +63,8 @@ import Splunk from "./Splunk.vue";
|
|||
import SevenIO from "./SevenIO.vue";
|
||||
import Whapi from "./Whapi.vue";
|
||||
import Cellsynt from "./Cellsynt.vue";
|
||||
import WPush from "./WPush.vue";
|
||||
import SIGNL4 from "./SIGNL4.vue";
|
||||
|
||||
/**
|
||||
* Manage all notification form.
|
||||
|
@ -97,6 +101,7 @@ const NotificationFormList = {
|
|||
"ntfy": Ntfy,
|
||||
"octopush": Octopush,
|
||||
"OneBot": OneBot,
|
||||
"Onesender": Onesender,
|
||||
"Opsgenie": Opsgenie,
|
||||
"PagerDuty": PagerDuty,
|
||||
"FlashDuty": FlashDuty,
|
||||
|
@ -110,6 +115,7 @@ const NotificationFormList = {
|
|||
"rocket.chat": RocketChat,
|
||||
"serwersms": SerwerSMS,
|
||||
"signal": Signal,
|
||||
"SIGNL4": SIGNL4,
|
||||
"SMSManager": SMSManager,
|
||||
"SMSPartner": SMSPartner,
|
||||
"slack": Slack,
|
||||
|
@ -119,6 +125,7 @@ const NotificationFormList = {
|
|||
"stackfield": Stackfield,
|
||||
"teams": Teams,
|
||||
"telegram": Telegram,
|
||||
"threema": Threema,
|
||||
"twilio": Twilio,
|
||||
"Splunk": Splunk,
|
||||
"webhook": Webhook,
|
||||
|
@ -130,6 +137,7 @@ const NotificationFormList = {
|
|||
"whapi": Whapi,
|
||||
"gtxmessaging": GtxMessaging,
|
||||
"Cellsynt": Cellsynt,
|
||||
"WPush": WPush
|
||||
};
|
||||
|
||||
export default NotificationFormList;
|
||||
|
|
|
@ -1,53 +1,63 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="add-btn">
|
||||
<button class="btn btn-primary me-2" type="button" @click="$refs.apiKeyDialog.show()">
|
||||
<font-awesome-icon icon="plus" /> {{ $t("Add API Key") }}
|
||||
</button>
|
||||
<div
|
||||
v-if="settings.disableAuth"
|
||||
class="mt-5 d-flex align-items-center justify-content-center my-3"
|
||||
>
|
||||
{{ $t("apiKeysDisabledMsg") }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="add-btn">
|
||||
<button class="btn btn-primary me-2" type="button" @click="$refs.apiKeyDialog.show()">
|
||||
<font-awesome-icon icon="plus" /> {{ $t("Add API Key") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span v-if="Object.keys(keyList).length === 0" class="d-flex align-items-center justify-content-center my-3">
|
||||
{{ $t("No API Keys") }}
|
||||
</span>
|
||||
<div>
|
||||
<span
|
||||
v-if="Object.keys(keyList).length === 0"
|
||||
class="d-flex align-items-center justify-content-center my-3"
|
||||
>
|
||||
{{ $t("No API Keys") }}
|
||||
</span>
|
||||
|
||||
<div
|
||||
v-for="(item, index) in keyList"
|
||||
:key="index"
|
||||
class="item"
|
||||
:class="item.status"
|
||||
>
|
||||
<div class="left-part">
|
||||
<div
|
||||
class="circle"
|
||||
></div>
|
||||
<div class="info">
|
||||
<div class="title">{{ item.name }}</div>
|
||||
<div class="status">
|
||||
{{ $t("apiKey-" + item.status) }}
|
||||
</div>
|
||||
<div class="date">
|
||||
{{ $t("Created") }}: {{ item.createdDate }}
|
||||
</div>
|
||||
<div class="date">
|
||||
{{ $t("Expires") }}: {{ item.expires || $t("Never") }}
|
||||
<div
|
||||
v-for="(item, index) in keyList"
|
||||
:key="index"
|
||||
class="item"
|
||||
:class="item.status"
|
||||
>
|
||||
<div class="left-part">
|
||||
<div class="circle"></div>
|
||||
<div class="info">
|
||||
<div class="title">{{ item.name }}</div>
|
||||
<div class="status">
|
||||
{{ $t("apiKey-" + item.status) }}
|
||||
</div>
|
||||
<div class="date">
|
||||
{{ $t("Created") }}: {{ item.createdDate }}
|
||||
</div>
|
||||
<div class="date">
|
||||
{{ $t("Expires") }}:
|
||||
{{ item.expires || $t("Never") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="buttons">
|
||||
<div class="btn-group" role="group">
|
||||
<button v-if="item.active" class="btn btn-normal" @click="disableDialog(item.id)">
|
||||
<font-awesome-icon icon="pause" /> {{ $t("Disable") }}
|
||||
</button>
|
||||
<div class="buttons">
|
||||
<div class="btn-group" role="group">
|
||||
<button v-if="item.active" class="btn btn-normal" @click="disableDialog(item.id)">
|
||||
<font-awesome-icon icon="pause" /> {{ $t("Disable") }}
|
||||
</button>
|
||||
|
||||
<button v-if="!item.active" class="btn btn-primary" @click="enableKey(item.id)">
|
||||
<font-awesome-icon icon="play" /> {{ $t("Enable") }}
|
||||
</button>
|
||||
<button v-if="!item.active" class="btn btn-primary" @click="enableKey(item.id)">
|
||||
<font-awesome-icon icon="play" /> {{ $t("Enable") }}
|
||||
</button>
|
||||
|
||||
<button class="btn btn-danger" @click="deleteDialog(item.id)">
|
||||
<font-awesome-icon icon="trash" /> {{ $t("Delete") }}
|
||||
</button>
|
||||
<button class="btn btn-danger" @click="deleteDialog(item.id)">
|
||||
<font-awesome-icon icon="trash" /> {{ $t("Delete") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -88,6 +98,9 @@ export default {
|
|||
let result = Object.values(this.$root.apiKeyList);
|
||||
return result;
|
||||
},
|
||||
settings() {
|
||||
return this.$parent.$parent.$parent.settings;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -126,9 +139,11 @@ export default {
|
|||
* @returns {void}
|
||||
*/
|
||||
disableKey() {
|
||||
this.$root.getSocket().emit("disableAPIKey", this.selectedKeyID, (res) => {
|
||||
this.$root.toastRes(res);
|
||||
});
|
||||
this.$root
|
||||
.getSocket()
|
||||
.emit("disableAPIKey", this.selectedKeyID, (res) => {
|
||||
this.$root.toastRes(res);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -146,113 +161,113 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../assets/vars.scss";
|
||||
|
||||
.mobile {
|
||||
.item {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
@import "../../assets/vars.scss";
|
||||
|
||||
.mobile {
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
text-decoration: none;
|
||||
border-radius: 10px;
|
||||
transition: all ease-in-out 0.15s;
|
||||
justify-content: space-between;
|
||||
padding: 10px;
|
||||
min-height: 90px;
|
||||
margin-bottom: 5px;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $highlight-white;
|
||||
.add-btn {
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
text-decoration: none;
|
||||
border-radius: 10px;
|
||||
transition: all ease-in-out 0.15s;
|
||||
justify-content: space-between;
|
||||
padding: 10px;
|
||||
min-height: 90px;
|
||||
margin-bottom: 5px;
|
||||
|
||||
&:hover {
|
||||
background-color: $highlight-white;
|
||||
}
|
||||
|
||||
&.active {
|
||||
.circle {
|
||||
background-color: $primary;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
.circle {
|
||||
background-color: $primary;
|
||||
}
|
||||
}
|
||||
|
||||
&.inactive {
|
||||
.circle {
|
||||
background-color: $danger;
|
||||
}
|
||||
}
|
||||
|
||||
&.expired {
|
||||
.left-part {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.circle {
|
||||
background-color: $dark-font-color;
|
||||
}
|
||||
&.inactive {
|
||||
.circle {
|
||||
background-color: $danger;
|
||||
}
|
||||
}
|
||||
|
||||
&.expired {
|
||||
.left-part {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
|
||||
.circle {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border-radius: 50rem;
|
||||
}
|
||||
|
||||
.info {
|
||||
.title {
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.status {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-direction: row-reverse;
|
||||
.circle {
|
||||
background-color: $dark-font-color;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
width: 310px;
|
||||
.left-part {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
|
||||
.circle {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border-radius: 50rem;
|
||||
}
|
||||
|
||||
.info {
|
||||
.title {
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.status {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.date {
|
||||
margin-top: 5px;
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
border-radius: 20px;
|
||||
padding: 0 10px;
|
||||
width: fit-content;
|
||||
.buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-direction: row-reverse;
|
||||
|
||||
.dark & {
|
||||
color: white;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
.btn-group {
|
||||
width: 310px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
.item {
|
||||
&:hover {
|
||||
background-color: $dark-bg2;
|
||||
}
|
||||
.date {
|
||||
margin-top: 5px;
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
border-radius: 20px;
|
||||
padding: 0 10px;
|
||||
width: fit-content;
|
||||
|
||||
.dark & {
|
||||
color: white;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
.item {
|
||||
&:hover {
|
||||
background-color: $dark-bg2;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -6,6 +6,7 @@ const languageList = {
|
|||
"cs-CZ": "Čeština",
|
||||
"zh-HK": "繁體中文 (香港)",
|
||||
"bg-BG": "Български",
|
||||
"be": "Беларуская",
|
||||
"de-DE": "Deutsch (Deutschland)",
|
||||
"de-CH": "Deutsch (Schweiz)",
|
||||
"nl-NL": "Nederlands",
|
||||
|
|
935
src/lang/be.json
Normal file
935
src/lang/be.json
Normal file
|
@ -0,0 +1,935 @@
|
|||
{
|
||||
"Edit": "Змяніць",
|
||||
"-hour": "-гадзін",
|
||||
"ignoreTLSErrorGeneral": "Ігнараваць памылку TLS/SSL для злучэння",
|
||||
"pushOthers": "Іншыя",
|
||||
"Yes": "Так",
|
||||
"Show URI": "Паказаць URI",
|
||||
"Tags": "Тэгі",
|
||||
"Tag with this value already exist.": "Тэг з такім значэннем ужо існуе.",
|
||||
"color": "Колер",
|
||||
"value (optional)": "значэнне (неабавязкова)",
|
||||
"Gray": "Шэры",
|
||||
"Red": "Чырвоны",
|
||||
"Orange": "Аранжавы",
|
||||
"Search monitored sites": "Пошук адсочваемых сайтаў",
|
||||
"Avg. Ping": "Сярэдні пінг",
|
||||
"Body": "Цела",
|
||||
"Headers": "Загалоўкі",
|
||||
"Create Incident": "Стварыць інцыдэнт",
|
||||
"Style": "Стыль",
|
||||
"Proxies": "Проксі",
|
||||
"default": "Па змаўчанні",
|
||||
"enabled": "Уключана",
|
||||
"setAsDefault": "Усталяваць па змаўчанні",
|
||||
"Remove the expiry notification": "Выдаліць дату сканчэньня тэрміну дзеяння абвесткі",
|
||||
"Refresh Interval": "Інтэрвал абнаўлення",
|
||||
"Refresh Interval Description": "Старонка статусу будзе цалкам абнаўляць сайт кожныя {0} секунд",
|
||||
"maintenanceStatus-ended": "Скончыўся(ліся)",
|
||||
"Select message type": "Выберыце тып паведамлення",
|
||||
"Create new forum post": "Стварыць новы пост",
|
||||
"postToExistingThread": "Стварыць пост у гэтай галіне",
|
||||
"forumPostName": "Назва паста",
|
||||
"e.g. {discordThreadID}": "Напр. {discordThreadID}",
|
||||
"Number": "Нумар",
|
||||
"lineDevConsoleTo": "Кансоль распрацошчыкаў Line - {0}",
|
||||
"recurringIntervalMessage": "Запускаць 1 раз кожны дзень | Запускаць 1 раз кожныя {0} дзён",
|
||||
"affectedMonitorsDescription": "Выберыце маніторы, якія будуць затронутыя падчас тэхабслугоўвання",
|
||||
"pushoversounds gamelan": "Гамелан",
|
||||
"pushoversounds incoming": "Уваходны",
|
||||
"pushoversounds climb": "Падым (доўгі)",
|
||||
"wayToGetKookBotToken": "Стварыце праграму і атрымайце токен бота па адрасу {0}",
|
||||
"Device": "Прылада",
|
||||
"Huawei": "Huawei",
|
||||
"Expiry date": "Дата сканчэння",
|
||||
"Don't expire": "Не сканчаецца",
|
||||
"Badge URL": "URL значка",
|
||||
"nostrRelays": "Рэле Nostr",
|
||||
"gamedigGuessPort": "Gamedig: Угадай порт",
|
||||
"GrafanaOncallUrl": "URL-адрас Grafana Oncall",
|
||||
"API URL": "API URL-адрас",
|
||||
"Originator type": "Тып крыніцы",
|
||||
"Destination": "Прызначэнне",
|
||||
"languageName": "Беларуская",
|
||||
"setupDatabaseChooseDatabase": "Якую базу даных вы хацелі б выкарыстоўваць?",
|
||||
"setupDatabaseEmbeddedMariaDB": "Вам не трэба нічога наладжваць. У гэты Docker-вобраз аўтаматычна ўбудавана і наладжана MariaDB. Uptime Kuma будзе падключацца да гэтай базы даных праз unix-socket.",
|
||||
"setupDatabaseMariaDB": "Падключыцца да знешняй базы даных MariaDB. Вам трэба задаць інфармацыю аб падлучэнні да базы даных.",
|
||||
"setupDatabaseSQLite": "Просты файл базы даных, рэкамендуецца для невялікіх разгортванняў. Да версіі 2.0.0 Uptime Kuma выкарыстоўваў SQLite як базу даных па змаўчанні.",
|
||||
"settingUpDatabaseMSG": "Настраиваем базу даных. Гэта можа заняць некаторы час, калі ласка, пачакайце.",
|
||||
"dbName": "Назва базы даных",
|
||||
"Settings": "Налады",
|
||||
"Dashboard": "Панэль кіравання",
|
||||
"Help": "Дапамога",
|
||||
"New Update": "Даступна абнаўленне",
|
||||
"Language": "Мова",
|
||||
"Appearance": "Знешні выгляд",
|
||||
"Theme": "Тэма",
|
||||
"General": "Агульныя",
|
||||
"Game": "Гульня",
|
||||
"Primary Base URL": "Асноўны URL",
|
||||
"Version": "Версія",
|
||||
"Check Update On GitHub": "Праверыць абнаўленні ў GitHub",
|
||||
"List": "Спіс",
|
||||
"Home": "Галоўная",
|
||||
"Add": "Дадаць",
|
||||
"Add New Monitor": "Дадаць новы манітор",
|
||||
"Quick Stats": "Статыстыка",
|
||||
"Up": "Працуе",
|
||||
"Down": "Не працуе",
|
||||
"Pending": "У чаканні",
|
||||
"statusMaintenance": "Тэхабслугоўванне",
|
||||
"Maintenance": "Тэхабслугоўванне",
|
||||
"Unknown": "Невядома",
|
||||
"Cannot connect to the socket server": "Немагчыма падключыцца да сервера",
|
||||
"Reconnecting...": "Падключэнне...",
|
||||
"General Monitor Type": "Агульны Тып Манітора",
|
||||
"Passive Monitor Type": "Пасіўны Тып Манітора",
|
||||
"Specific Monitor Type": "Спецыфічны Тып Манітора",
|
||||
"markdownSupported": "Падтрымліваецца сінтаксіс Markdown",
|
||||
"pauseDashboardHome": "Паўза",
|
||||
"Pause": "Паўза",
|
||||
"Name": "Назва",
|
||||
"Status": "Статус",
|
||||
"DateTime": "Дата і час",
|
||||
"Message": "Паведамленне",
|
||||
"No important events": "Няма важных падзей",
|
||||
"Resume": "Узнавіць",
|
||||
"Delete": "Выдаліць",
|
||||
"Current": "Бягучы",
|
||||
"Uptime": "Час працы",
|
||||
"Cert Exp.": "Сертыфікат сконч.",
|
||||
"Monitor": "Манітор | Маніторы",
|
||||
"day": "дзень | дзён",
|
||||
"-day": "-дзён",
|
||||
"hour": "гадзіна",
|
||||
"Response": "Адказ",
|
||||
"Ping": "Пінг",
|
||||
"Monitor Type": "Тып манітора",
|
||||
"Keyword": "Ключавое слова",
|
||||
"Invert Keyword": "Інвертаваць ключавое слова",
|
||||
"Friendly Name": "Назва",
|
||||
"URL": "URL-спасылка",
|
||||
"Hostname": "Адрас хоста",
|
||||
"Expected Value": "Чаканае значэнне",
|
||||
"Json Query": "JSON Запыт",
|
||||
"Host URL": "URL Хоста",
|
||||
"locally configured mail transfer agent": "Наладжаны лакальна агент перадачы паштовых паведамленняў",
|
||||
"Port": "Порт",
|
||||
"Heartbeat Interval": "Частата апытання",
|
||||
"Request Timeout": "Тайм-Аут запыту",
|
||||
"timeoutAfter": "Тайм-Аут праз {0} секундаў",
|
||||
"Retries": "Спробы",
|
||||
"Heartbeat Retry Interval": "Інтэрвал паўтору апытання",
|
||||
"Resend Notification if Down X times consecutively": "Паўторная адпраўка абвесткі пры адключэнні некалькі раз",
|
||||
"Advanced": "Дадаткова",
|
||||
"checkEverySecond": "Праверка кожныя {0} секунд",
|
||||
"retryCheckEverySecond": "Паўтараць кожныя {0} секунд",
|
||||
"resendEveryXTimes": "Перасылаць кожныя {0} раз",
|
||||
"resendDisabled": "Перасылка адключана",
|
||||
"retriesDescription": "Максімальная колькасць спробаў перад адзнакай службы, як недаступная, і адпраўкай абвесткі",
|
||||
"ignoreTLSError": "Ігнараваць памылкі TLS/SSL для HTTPS сайтаў",
|
||||
"upsideDownModeDescription": "Змяніць статус службы на ПРАЦУЕ, калі яна даступная, а пазначаецца як НЕ ПРАЦУЕ.",
|
||||
"maxRedirectDescription": "Максімальная колькасць перанакіраванняў. Пастаўце 0, каб адключыць перанакіраванні.",
|
||||
"Upside Down Mode": "Рэжым змены статусу",
|
||||
"Max. Redirects": "Макс. колькасць перанакіраванняў",
|
||||
"Accepted Status Codes": "Дапушчальныя коды статуса",
|
||||
"Push URL": "URL-спасылка пуш абвестак",
|
||||
"needPushEvery": "Да гэтага URL неабходна звяртацца кожныя {0} секунд.",
|
||||
"pushOptionalParams": "Неабавязковыя параметры: {0}",
|
||||
"pushViewCode": "Як выкарыстоўваць манітор Push? (Паглядзець код)",
|
||||
"programmingLanguages": "Мовы праграмавання",
|
||||
"Save": "Захаваць",
|
||||
"Notifications": "Апавяшчэнні",
|
||||
"Not available, please setup.": "Апавяшчэнні недаступныя, патрабуецца налада.",
|
||||
"Setup Notification": "Наладзіць апавяшчэнні",
|
||||
"Light": "Светлая",
|
||||
"Dark": "Цёмная",
|
||||
"Auto": "Як у сістэме",
|
||||
"Theme - Heartbeat Bar": "Тэма - радка частаты апытання",
|
||||
"styleElapsedTime": "Мінулы час пад радком частаты апытання",
|
||||
"styleElapsedTimeShowNoLine": "Паказаць (Без лініі)",
|
||||
"styleElapsedTimeShowWithLine": "Паказаць (З лініяй)",
|
||||
"Normal": "Звычайны",
|
||||
"Bottom": "Унізе",
|
||||
"None": "Адсутнічае",
|
||||
"Timezone": "Часавы пояс TZ",
|
||||
"Search Engine Visibility": "Бачнасць у пошукавых сістэмах",
|
||||
"Allow indexing": "Дазволіць індэксацыю",
|
||||
"Discourage search engines from indexing site": "Забараніць індэксацыю",
|
||||
"Change Password": "Змяніць пароль",
|
||||
"Current Password": "Бягучы пароль",
|
||||
"New Password": "Новы пароль",
|
||||
"Repeat New Password": "Паўтарыць новы пароль",
|
||||
"Update Password": "Абнавіць пароль",
|
||||
"Disable Auth": "Адключыць аўтарызацыю",
|
||||
"Enable Auth": "Уключыць аўтарызацыю",
|
||||
"disableauth.message1": "Вы ўпэўнены, што хочаце {disableAuth}?",
|
||||
"disable authentication": "адключыць аўтарызацыю",
|
||||
"disableauth.message2": "Гэта падыходзіць для {intendThirdPartyAuth} перад адкрыццём Uptime Kuma, такіх як Cloudflare Access, Authelia або іншыя.",
|
||||
"where you intend to implement third-party authentication": "тых, у каго настроена старонняя сістэма аўтарызацыі",
|
||||
"Please use this option carefully!": "Выкарыстоўвайце гэтую наладу асцярожна!",
|
||||
"Logout": "Выйсці",
|
||||
"Leave": "Пакінуць",
|
||||
"I understand, please disable": "Я разумею, усё роўна адключыць",
|
||||
"Confirm": "Пацвердзіць",
|
||||
"No": "Не",
|
||||
"Username": "Лагін",
|
||||
"Password": "Пароль",
|
||||
"Remember me": "Запомніць мяне",
|
||||
"Login": "Уваход у сістэму",
|
||||
"No Monitors, please": "Няма манітораў, калі ласка",
|
||||
"add one": "дадаць",
|
||||
"Notification Type": "Тып абвесткі",
|
||||
"Email": "Электронная пошта",
|
||||
"Test": "Тэст",
|
||||
"Certificate Info": "Інфармацыя пра сертыфікат",
|
||||
"Resolver Server": "DNS сервер",
|
||||
"Resource Record Type": "Тып рэсурснай запісі",
|
||||
"Last Result": "Апошні вынік",
|
||||
"Create your admin account": "Стварыце акаўнт адміністратара",
|
||||
"Repeat Password": "Паўтарыць пароль",
|
||||
"Import Backup": "Імпартаваць Backup",
|
||||
"Export Backup": "Спампаваць Backup",
|
||||
"Export": "Экспарт",
|
||||
"Import": "Імпарт",
|
||||
"respTime": "Час адказу (мс)",
|
||||
"notAvailableShort": "N/A",
|
||||
"Default enabled": "Па змаўчанні ўключана",
|
||||
"Apply on all existing monitors": "Ужыць да ўсіх існуючых манітораў",
|
||||
"Create": "Стварыць",
|
||||
"Clear Data": "Выдаліць даныя",
|
||||
"Events": "Падзеі",
|
||||
"Heartbeats": "Апытанні",
|
||||
"Auto Get": "Аўта-атрыманне",
|
||||
"Schedule maintenance": "Запланаваць тэхабслугоўванне",
|
||||
"Affected Monitors": "Задзейнічаныя Маніторы",
|
||||
"Pick Affected Monitors...": "Выберыце Задзейнічаныя Маніторы…",
|
||||
"Start of maintenance": "Пачатак тэхабслугоўвання",
|
||||
"All Status Pages": "Усе старонкі статусаў",
|
||||
"Select status pages...": "Выберыце старонку статуса…",
|
||||
"alertNoFile": "Выберыце файл для імпарту.",
|
||||
"alertWrongFileType": "Выберыце JSON-файл.",
|
||||
"Clear all statistics": "Ачысціць усю статыстыку",
|
||||
"Skip existing": "Прапусціць існуючыя",
|
||||
"Overwrite": "Перазапісаць",
|
||||
"Options": "Опцыі",
|
||||
"Keep both": "Пакінуць абодва",
|
||||
"Verify Token": "Праверыць токен",
|
||||
"Setup 2FA": "Налады 2FA",
|
||||
"Enable 2FA": "Уключыць 2FA",
|
||||
"Disable 2FA": "Адключыць 2FA",
|
||||
"2FA Settings": "Налады 2FA",
|
||||
"Two Factor Authentication": "Двухфактарная аўтэнтыфікацыя",
|
||||
"filterActive": "Актыўны",
|
||||
"filterActivePaused": "На паўзе",
|
||||
"Active": "Актыўна",
|
||||
"Inactive": "Неактыўна",
|
||||
"Token": "Токен",
|
||||
"Add New Tag": "Дадаць тэг",
|
||||
"Add New below or Select...": "Дадаць новы або выбраць…",
|
||||
"Tag with this name already exist.": "Тэг з такім імем ужо існуе.",
|
||||
"Green": "Зялёны",
|
||||
"Blue": "Сіні",
|
||||
"Indigo": "Індыга",
|
||||
"Purple": "Пурпуровы",
|
||||
"Pink": "Ружовы",
|
||||
"Custom": "Сваёродны",
|
||||
"Search...": "Пошук…",
|
||||
"Avg. Response": "Сярэдні адказ",
|
||||
"Entry Page": "Галоўная",
|
||||
"statusPageNothing": "Нічога няма, дадайце групу або манітор.",
|
||||
"statusPageRefreshIn": "Абнаўленне праз: {0}",
|
||||
"No Services": "Няма сэрвісаў",
|
||||
"All Systems Operational": "Усе сістэмы працуюць",
|
||||
"Partially Degraded Service": "Часткова працуючы сэрвіс",
|
||||
"Degraded Service": "Пашкоджаная служба",
|
||||
"Add Group": "Дадаць групу",
|
||||
"Add a monitor": "Дадаць манітор",
|
||||
"Edit Status Page": "Рэдагаваць старонку статусаў",
|
||||
"Go to Dashboard": "Перайсці да панэлі кіравання",
|
||||
"Status Page": "Старонка статуса",
|
||||
"Status Pages": "Старонкі статуса",
|
||||
"defaultNotificationName": "Абвесткі {notification} ({number})",
|
||||
"here": "тут",
|
||||
"Required": "Абавязкова",
|
||||
"Post URL": "Post URL",
|
||||
"Content Type": "Тып кантэнту",
|
||||
"webhookJsonDesc": "{0} падыходзіць для любых сучасных HTTP-сервераў, напрыклад Express.js",
|
||||
"webhookFormDataDesc": "{multipart} падыходзіць для PHP. JSON-вывад неабходна будзе апрацаваць з дапамогай {decodeFunction}",
|
||||
"liquidIntroduction": "Шаблоннасьць дасягаецца з дапамогай мовы шаблонаў Liquid. Інструкцыі па выкарыстаньні прадстаўлены ў раздзеле {0}. Вось даступныя зменныя:",
|
||||
"templateMsg": "паведамленне апавешчання",
|
||||
"templateHeartbeatJSON": "аб'ект, які апісвае сігнал",
|
||||
"templateMonitorJSON": "аб'ект, які апісвае манітор",
|
||||
"templateLimitedToUpDownNotifications": "даступна толькі для апавешчанняў UP/DOWN",
|
||||
"templateLimitedToUpDownCertNotifications": "даступна толькі для апавешчанняў UP/DOWN і аб заканчэньні тэрміну дзеяньня сертыфіката",
|
||||
"webhookAdditionalHeadersTitle": "Дадатковыя Загалоўкі",
|
||||
"webhookAdditionalHeadersDesc": "Устанаўлівае дадатковыя загалоўкі, якія адпраўляюцца з дапамогай вэб-хука. Кожны загаловак павінен быць вызначаны як JSON ключ/значэнне.",
|
||||
"webhookBodyPresetOption": "Прэсет - {0}",
|
||||
"webhookBodyCustomOption": "Карыстацкі аб'ект",
|
||||
"Webhook URL": "URL вэбхука",
|
||||
"Application Token": "Токен праграмы",
|
||||
"Server URL": "URL сервера",
|
||||
"Priority": "Прыярытэт",
|
||||
"emojiCheatSheet": "Шпаргалка па Emoji: {0}",
|
||||
"Read more": "Падрабязней",
|
||||
"appriseInstalled": "Апавяшчэнне ўсталявана.",
|
||||
"appriseNotInstalled": "Апавяшчэнне не ўсталявана. {0}",
|
||||
"Method": "Метад",
|
||||
"PushUrl": "URL пуша",
|
||||
"HeadersInvalidFormat": "Загалоўкі запыту не з'яўляюцца валідным JSON: ",
|
||||
"BodyInvalidFormat": "Цела запыту не з'яўляецца валідным JSON: ",
|
||||
"Monitor History": "Гісторыя маніторынгу",
|
||||
"clearDataOlderThan": "Захоўваць статыстыку за {0} дзён.",
|
||||
"PasswordsDoNotMatch": "Паролі не супадаюць.",
|
||||
"records": "запісы",
|
||||
"One record": "Адзін запіс",
|
||||
"steamApiKeyDescription": "Для маніторынгу гульнявога сервера Steam вам патрэбны Web-API ключ Steam. Зарэгістраваць яго можна тут: ",
|
||||
"Current User": "Бягучы карыстальнік",
|
||||
"topic": "Тэма",
|
||||
"topicExplanation": "MQTT топік для маніторынгу",
|
||||
"successKeyword": "Ключавое слова паспяховасці",
|
||||
"successKeywordExplanation": "Ключавое слова MQTT, якое будзе лічыцца паспяховым",
|
||||
"recent": "Апошняе",
|
||||
"Reset Token": "Скід токена",
|
||||
"Done": "Гатова",
|
||||
"Info": "Інфа",
|
||||
"Security": "Бяспека",
|
||||
"Steam API Key": "Steam API-Ключ",
|
||||
"Shrink Database": "Сціснуць базу даных",
|
||||
"Pick a RR-Type...": "Выберыце RR-Тып…",
|
||||
"Pick Accepted Status Codes...": "Выберыце прынятыя коды статуса…",
|
||||
"Default": "Па змаўчанні",
|
||||
"HTTP Options": "HTTP Опцыі",
|
||||
"Title": "Назва інцыдэнту",
|
||||
"Content": "Змест інцыдэнту",
|
||||
"info": "ІНФА",
|
||||
"warning": "УВАГА",
|
||||
"danger": "ПАМЫЛКА",
|
||||
"error": "памылка",
|
||||
"critical": "крытычна",
|
||||
"primary": "АСНОЎНЫ",
|
||||
"light": "СВЕТЛЫ",
|
||||
"dark": "ЦЁМНЫ",
|
||||
"Post": "Апублікаваць",
|
||||
"Please input title and content": "Калі ласка, увядзіце назву і змест",
|
||||
"Created": "Створана",
|
||||
"Last Updated": "Апошняе абнаўленне",
|
||||
"Switch to Light Theme": "Светлая тэма",
|
||||
"Switch to Dark Theme": "Цёмная тэма",
|
||||
"Show Tags": "Паказаць тэгі",
|
||||
"Hide Tags": "Схаваць тэгі",
|
||||
"Description": "Апісанне",
|
||||
"No monitors available.": "Няма даступных манітораў.",
|
||||
"Add one": "Дадаць новы",
|
||||
"No Monitors": "Маніторы адсутнічаюць",
|
||||
"Untitled Group": "Група без назвы",
|
||||
"Services": "Службы",
|
||||
"Powered by": "Працуе на",
|
||||
"Discard": "Скасаваць",
|
||||
"Cancel": "Скасаваць",
|
||||
"Select": "Выбраць",
|
||||
"selectedMonitorCount": "Выбрана: {0}",
|
||||
"Check/Uncheck": "Адзначыць/Зняць",
|
||||
"shrinkDatabaseDescription": "Уключае VACUUM для базы даных SQLite. Калі ваша база даных была створана на версіі 1.10.0 і больш, AUTO_VACUUM ужо ўключаны і гэтае дзеянне не патрабуецца.",
|
||||
"Customize": "Персаналізаваць",
|
||||
"Custom Footer": "Карыстацкі footer",
|
||||
"Custom CSS": "Карыстацкі CSS",
|
||||
"enableProxyDescription": "Гэты проксі не будзе ўплываць на запыты манітора, пакуль ён не будзе актываваны. Вы можаце кантраляваць часовае адключэнне проксі для ўсіх манітораў праз статус актывацыі.",
|
||||
"deleteStatusPageMsg": "Вы сапраўды хочаце выдаліць гэтую старонку статуса?",
|
||||
"deleteProxyMsg": "Вы сапраўды хочаце выдаліць гэты проксі для ўсіх манітораў?",
|
||||
"proxyDescription": "Проксі павінны быць прывязаныя да манітора, каб працаваць.",
|
||||
"setAsDefaultProxyDescription": "Гэты проксі будзе па змаўчанні ўключаны для новых манітораў. Вы ўсё яшчэ можаце асобна адключаць проксі ў кожным маніторы.",
|
||||
"Certificate Chain": "Ланцуг сертыфікатаў",
|
||||
"Valid": "Дзейны",
|
||||
"Invalid": "Нядзейсны",
|
||||
"User": "Карыстальнік",
|
||||
"Page Not Found": "Старонка не знойдзена",
|
||||
"Installed": "Усталявана",
|
||||
"Not installed": "Не ўсталявана",
|
||||
"Running": "Працуе",
|
||||
"Not running": "Не працуе",
|
||||
"Remove Token": "Выдаліць токен",
|
||||
"Start": "Пачаць",
|
||||
"Stop": "Спыніць",
|
||||
"Add New Status Page": "Дадаць старонку статуса",
|
||||
"Slug": "Slug",
|
||||
"Accept characters:": "Прымаць сімвалы:",
|
||||
"startOrEndWithOnly": "Пачынаецца або заканчваецца толькі на {0}",
|
||||
"No consecutive dashes": "Без паслядоўных тырэ",
|
||||
"statusPageSpecialSlugDesc": "Спецыяльны значок {0}: гэтая старонка будзе адлюстроўвацца, калі значок не пазначаны",
|
||||
"Next": "Далей",
|
||||
"The slug is already taken. Please choose another slug.": "Гэты slug ужо заняты. Калі ласка, выберыце іншы slug.",
|
||||
"No Proxy": "Без проксі",
|
||||
"Authentication": "Аўтэнтыфікацыя",
|
||||
"HTTP Basic Auth": "HTTP Аўтарызацыя",
|
||||
"New Status Page": "Новая старонка статуса",
|
||||
"Reverse Proxy": "Зваротны проксі",
|
||||
"Backup": "Рэзервовая копія",
|
||||
"About": "Аб праграме",
|
||||
"wayToGetCloudflaredURL": "(Спампаваць cloudflared з {0})",
|
||||
"cloudflareWebsite": "Вэб-сайт Cloudflare",
|
||||
"Message:": "Паведамленне:",
|
||||
"Don't know how to get the token? Please read the guide:": "Не ведаеце, як атрымаць токен? Калі ласка, прачытайце кіраўніцтва:",
|
||||
"The current connection may be lost if you are currently connecting via Cloudflare Tunnel. Are you sure want to stop it? Type your current password to confirm it.": "Бягучае злучэнне можа быць страчана, калі вы ў цяперашні час падключаецеся праз тунэль Cloudflare. Вы ўпэўнены, што хочаце яго спыніць? Увядзіце свой бягучы пароль, каб пацвердзіць гэта.",
|
||||
"HTTP Headers": "Загалоўкі HTTP",
|
||||
"Trust Proxy": "Давераны проксі",
|
||||
"Other Software": "Іншае праграмнае забеспячэнне",
|
||||
"For example: nginx, Apache and Traefik.": "Напрыклад: nginx, Apache і Traefik.",
|
||||
"Please read": "Калі ласка, прачытайце",
|
||||
"Subject:": "Тэма:",
|
||||
"Valid To:": "Дзейсны да:",
|
||||
"Days Remaining:": "Засталося дзён:",
|
||||
"Issuer:": "Выдавец:",
|
||||
"Fingerprint:": "Адбітак:",
|
||||
"No status pages": "Няма старонак статуса",
|
||||
"Domain Name Expiry Notification": "Абвестка пра сканчэнне тэрміну дзеяння даменнай назвы",
|
||||
"Add a new expiry notification day": "Дадаць новы дзень абвесткі пра сканчэньне тэрміну дзеяння",
|
||||
"Proxy": "Проксі",
|
||||
"Date Created": "Дата стварэння",
|
||||
"Footer Text": "Тэкст у ніжнім калонтытуле",
|
||||
"Show Powered By": "Паказаць на чым створана",
|
||||
"Domain Names": "Даменныя імёны",
|
||||
"signedInDisp": "Вы ўвайшлі як {0}",
|
||||
"signedInDispDisabled": "Аўтэнтыфікацыя адключана.",
|
||||
"RadiusSecret": "Сакрэт Radius",
|
||||
"RadiusSecretDescription": "Агульны сакрэт паміж кліентам і серверам",
|
||||
"RadiusCalledStationId": "Ідэнтыфікатар вызываемай станцыі",
|
||||
"RadiusCalledStationIdDescription": "Ідэнтыфікатар вызываемага прылады",
|
||||
"RadiusCallingStationId": "Ідэнтыфікатар вызывальніка станцыі",
|
||||
"RadiusCallingStationIdDescription": "Ідэнтыфікатар вызывальніка прылады",
|
||||
"Certificate Expiry Notification": "Абвестка пра сканчэнне тэрміну дзеяння сертыфіката",
|
||||
"API Username": "Імя карыстальніка API",
|
||||
"API Key": "API ключ",
|
||||
"Show update if available": "Паказваць даступныя абнаўленні",
|
||||
"Also check beta release": "Праверыць абнаўленні для бета версій",
|
||||
"Using a Reverse Proxy?": "Выкарыстоўваеце зваротны проксі?",
|
||||
"Check how to config it for WebSocket": "Праверце, як наладзіць яго для WebSocket",
|
||||
"Steam Game Server": "Гульнявы сервер Steam",
|
||||
"Most likely causes:": "Найбольш верагодныя прычыны:",
|
||||
"The resource is no longer available.": "Рэсурс больш не даступны.",
|
||||
"There might be a typing error in the address.": "У адрасе можа быць памылка ў друку.",
|
||||
"What you can try:": "Што вы можаце паспрабаваць:",
|
||||
"Retype the address.": "Паўтарыце адрас.",
|
||||
"Go back to the previous page.": "Вярнуцца на папярэднюю старонку.",
|
||||
"Coming Soon": "Хутка",
|
||||
"Connection String": "Радок падлучэння",
|
||||
"Query": "Запыт",
|
||||
"settingsCertificateExpiry": "Сканчэнне TLS сертыфіката",
|
||||
"certificationExpiryDescription": "HTTPS Маніторы ініцыююць абвестку, калі срок дзеяння сертыфіката TLS скончыцца:",
|
||||
"Setup Docker Host": "Налада Docker Host",
|
||||
"Connection Type": "Тып злучэння",
|
||||
"Docker Daemon": "Дэман Docker",
|
||||
"noDockerHostMsg": "Не даступна. Спачатку наладзце хост Docker.",
|
||||
"DockerHostRequired": "Усталюйце хост Docker для гэтага манітора.",
|
||||
"deleteDockerHostMsg": "Вы сапраўды хочаце выдаліць гэты вузел docker для ўсіх манітораў?",
|
||||
"socket": "Сокет",
|
||||
"tcp": "TCP / HTTP",
|
||||
"tailscalePingWarning": "Для таго, каб выкарыстоўваць манітор Tailscale Ping, неабходна ўсталяваць Uptime Kuma без Docker, а таксама ўсталяваць на сервер кліент Tailscale.",
|
||||
"Docker Container": "Docker кантэйнер",
|
||||
"Container Name / ID": "Назва кантэйнера / ID",
|
||||
"Docker Host": "Хост Docker",
|
||||
"Docker Hosts": "Хосты Docker",
|
||||
"Domain": "Дамен",
|
||||
"Workstation": "Рабочая станцыя",
|
||||
"Packet Size": "Памер пакета",
|
||||
"Bot Token": "Токен бота",
|
||||
"wayToGetTelegramToken": "Вы можаце атрымаць токен тут - {0}.",
|
||||
"Chat ID": "ID чата",
|
||||
"telegramMessageThreadID": "(Неабавязкова) ID ланцуга паведамленняў",
|
||||
"telegramMessageThreadIDDescription": "Неабавязковы ўнікальны ідэнтыфікатар для ланцуга паведамленняў (тэмы) форума; толькі для форумаў-супергруп",
|
||||
"telegramSendSilently": "Адправіць без гуку",
|
||||
"telegramSendSilentlyDescription": "Карыстальнікі атрымаюць абвестку без гуку.",
|
||||
"telegramProtectContent": "Забараніць перасылку/захаванне",
|
||||
"telegramProtectContentDescription": "Калі ўключана, паведамленні бота ў Telegram будуць забароненыя для перасылкі і захавання.",
|
||||
"supportTelegramChatID": "Падтрымліваюцца ID чатаў, груп і каналаў",
|
||||
"wayToGetTelegramChatID": "Вы можаце атрымаць ID вашага чата, адправіўшы паведамленне боту і перайсці па гэтаму URL для прагляду chat_id:",
|
||||
"YOUR BOT TOKEN HERE": "ВАШ ТОКЕН БОТА ТУТ",
|
||||
"chatIDNotFound": "ID чата не знойдзены; спачатку адпраўце паведамленне боту",
|
||||
"disableCloudflaredNoAuthMsg": "Вы знаходзіцеся ў рэжыме без аўтарызацыі, пароль не патрабуецца.",
|
||||
"trustProxyDescription": "Давяраць загалоўкам 'X-Forwarded-*'. Калі вы хочаце атрымаць правільны IP-адрас кліента, а ваш Uptime Kuma знаходзіцца пад Nginx або Apache, вам след включить гэты параметр.",
|
||||
"wayToGetLineNotifyToken": "Вы можаце атрымаць токен доступу ў {0}",
|
||||
"Examples": "Прыклады",
|
||||
"Home Assistant URL": "URL-адрас Home Assistant",
|
||||
"Long-Lived Access Token": "Токен доступу з доўгім тэрмінам службы",
|
||||
"Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "Токен доступу з доўгім тэрмінам дзеяння можна стварыць, націснуўшы на імя вашага профілю (ўнізе злева) і пракруціўшы яго ўніз, потым націсніце Стварыць токен. ",
|
||||
"Notification Service": "Служба абвестак",
|
||||
"default: notify all devices": "па змаўчанні: апавяшчаць усе прылады",
|
||||
"A list of Notification Services can be found in Home Assistant under \"Developer Tools > Services\" search for \"notification\" to find your device/phone name.": "Спіс службаў абвестак можна знайсці ў Home Assistant у раздзеле \"Інструменты распрацоўніка > Службы\", выканаўшы пошук па слове \"абвестка\", каб знайсці назву вашага прылады/тэлефона.",
|
||||
"Automations can optionally be triggered in Home Assistant:": "Пры жаданні аўтаматызацыю можна актываваць у Home Assistant.:",
|
||||
"Trigger type:": "Тып трыгера:",
|
||||
"Event type:": "Тып падзеі:",
|
||||
"Event data:": "даныя падзеі:",
|
||||
"Then choose an action, for example switch the scene to where an RGB light is red.": "Затым выберыце дзеянне, напрыклад, пераключыце сцэну на чырвоны індыкатар RGB..",
|
||||
"Frontend Version": "Версія інтэрфейса",
|
||||
"Frontend Version do not match backend version!": "Версія інтэрфейса не адпавядае версіі сервернай часткі!",
|
||||
"backupOutdatedWarning": "Састарэла: гэтая функцыя рэзервовага капіявання больш не падтрымліваецца. Праз даданыя шмат функцый, яна не можа стварыць або аднавіць поўную рэзервовую копію.",
|
||||
"backupRecommend": "Зрабіце рэзервовую копію таму або папцы з данымі (./data/) напрамую.",
|
||||
"Optional": "Неабавязкова",
|
||||
"or": "або",
|
||||
"sameAsServerTimezone": "Аналагічна часавому поясу сервера",
|
||||
"startDateTime": "Пачатковая дата і час",
|
||||
"endDateTime": "Канчатковая дата і час",
|
||||
"cronExpression": "Выраз для Cron",
|
||||
"cronSchedule": "Расклад: ",
|
||||
"invalidCronExpression": "Няправільны выраз Cron: {0}",
|
||||
"recurringInterval": "Інтэрвал",
|
||||
"Recurring": "Паўторны",
|
||||
"strategyManual": "Актыўны/Неактыўны Ручным спосабам",
|
||||
"warningTimezone": "Выкарыстоўваецца часавы пояс сервера",
|
||||
"weekdayShortMon": "Пн",
|
||||
"weekdayShortTue": "Аўт",
|
||||
"weekdayShortWed": "Ср",
|
||||
"weekdayShortThu": "Чт",
|
||||
"weekdayShortFri": "Пт",
|
||||
"weekdayShortSat": "Сб",
|
||||
"weekdayShortSun": "Нд",
|
||||
"dayOfWeek": "Дзень тыдня",
|
||||
"dayOfMonth": "Дзень месяца",
|
||||
"lastDay": "Апошні дзень",
|
||||
"lastDay1": "Апошні дзень месяца",
|
||||
"lastDay2": "Другі апошні дзень месяца",
|
||||
"lastDay3": "Трэці апошні дзень месяца",
|
||||
"maintenanceStatus-scheduled": "Запланавана(ы)",
|
||||
"maintenanceStatus-unknown": "Невядома",
|
||||
"lastDay4": "Чацвёрты апошні дзень месяца",
|
||||
"No Maintenance": "Няма тэхабслугоўванняў",
|
||||
"pauseMaintenanceMsg": "Вы ўпэўненыя, што хочаце паставіць на паўзу?",
|
||||
"maintenanceStatus-under-maintenance": "На тэхабслугоўванні",
|
||||
"maintenanceStatus-inactive": "Неактыўны",
|
||||
"Display Timezone": "Паказаць часавы пояс",
|
||||
"Server Timezone": "Часавы пояс сервера",
|
||||
"statusPageMaintenanceEndDate": "Канец",
|
||||
"IconUrl": "URL значка",
|
||||
"Enable DNS Cache": "(Састарэла) Уключыць DNS кэш для манітораў HTTP(S)",
|
||||
"Enable": "Уключыць",
|
||||
"Disable": "Адключыць",
|
||||
"enableNSCD": "Уключыць NSCD (Name Service Cache Daemon) для кэшавання ўсіх DNS-запытаў",
|
||||
"chromeExecutable": "Выканаўчы файл Chrome/Chromium",
|
||||
"chromeExecutableAutoDetect": "Аўтавызначэнне",
|
||||
"chromeExecutableDescription": "Для карыстальнікаў Docker, калі Chromium яшчэ не ўсталяваны, можа спатрэбіцца некалькі хвілін для ўсталявання і адлюстравання выніку тэставання. Ён займае 1 ГБ дыскавага прастору.",
|
||||
"dnsCacheDescription": "Гэта можа не працаваць на некаторых IPv6 асяроддзях, адключыце гэта, калі ў вас узнікаюць праблемы.",
|
||||
"Single Maintenance Window": "Адзінае акно тэхабслугоўвання",
|
||||
"Maintenance Time Window of a Day": "Суточны інтэрвал для тэхабслугоўвання",
|
||||
"Effective Date Range": "Даты дзеяння (Неабавязкова)",
|
||||
"Schedule Maintenance": "Запланаваць тэхабслугоўванне",
|
||||
"Edit Maintenance": "Рэдагаваць тэхабслугоўванне",
|
||||
"Date and Time": "Дата і час",
|
||||
"DateTime Range": "Дыяпазон даты і часу",
|
||||
"loadingError": "Немагчыма атрымаць даныя, калі ласка паспрабуйце пазней.",
|
||||
"plugin": "Плагін | Плагіны",
|
||||
"install": "Усталяваць",
|
||||
"installing": "Усталяваецца",
|
||||
"uninstall": "Выдаліць",
|
||||
"uninstalling": "Выдаляецца",
|
||||
"confirmUninstallPlugin": "Вы ўпэўнены, што хочаце выдаліць гэты плагін?",
|
||||
"notificationRegional": "Рэгіянальны",
|
||||
"Clone Monitor": "Копія",
|
||||
"Clone": "Кланаваць",
|
||||
"cloneOf": "Копія {0}",
|
||||
"smtp": "Email (SMTP)",
|
||||
"secureOptionNone": "Няма / STARTTLS (25, 587)",
|
||||
"secureOptionTLS": "TLS (465)",
|
||||
"Ignore TLS Error": "Ігнараваць памылкі TLS",
|
||||
"From Email": "Ад каго",
|
||||
"emailCustomisableContent": "Наладжвальны змест",
|
||||
"smtpLiquidIntroduction": "Наступныя два поля з'яўляюцца шабланізаванымі з дапамогай мовы шаблонаў Liquid. Інструкцыі па іх выкарыстаньні прадстаўлены ў раздзеле {0}. Вось даступныя зменныя:",
|
||||
"emailCustomSubject": "Свая тэма",
|
||||
"leave blank for default subject": "пакіньце пустым для тэмы па змаўчаньні",
|
||||
"emailCustomBody": "Карыстацкі аб'ект",
|
||||
"leave blank for default body": "пакіньце пустым для аб'екта па змаўчаньні",
|
||||
"emailTemplateServiceName": "Назва сэрвіса",
|
||||
"emailTemplateHostnameOrURL": "Назва хоста або URL",
|
||||
"emailTemplateStatus": "Статус",
|
||||
"emailTemplateMonitorJSON": "аб'ект, які апісвае манітор",
|
||||
"emailTemplateHeartbeatJSON": "аб'ект, які апісвае сігнал",
|
||||
"emailTemplateMsg": "паведамленне апавешчання",
|
||||
"emailTemplateLimitedToUpDownNotification": "даступны толькі для сігналаў UP/DOWN, у адваротным выпадку null",
|
||||
"To Email": "Каму",
|
||||
"smtpCC": "Копія",
|
||||
"smtpBCC": "Схаваная копія",
|
||||
"Discord Webhook URL": "Discord вэбхук URL",
|
||||
"wayToGetDiscordURL": "Вы можаце стварыць яго ў наладах канала \"Налады -> Інтэграцыі -> Стварыць Вэбхук\"",
|
||||
"Bot Display Name": "Адлюстраваная назва бота",
|
||||
"Prefix Custom Message": "Свой прэфікс паведамлення",
|
||||
"Hello @everyone is...": "Прывітанне {'@'}everyone гэта…",
|
||||
"wayToGetTeamsURL": "Як стварыць URL вэбхука вы можаце даведацца тут - {0}.",
|
||||
"wayToGetZohoCliqURL": "Вы можаце даведацца, як стварыць webhook URL тут {0}.",
|
||||
"needSignalAPI": "Вам патрэбны кліент Signal з падтрымкай REST API.",
|
||||
"Channel access token": "Токен доступу да канала",
|
||||
"wayToCheckSignalURL": "Перайдзіце па гэтаму URL, каб даведацца, як наладзіць такі кліент:",
|
||||
"Recipients": "Атрымальнікі",
|
||||
"Access Token": "Токен доступу",
|
||||
"Channel access token (Long-lived)": "Токен доступу да канала (даўгавечны)",
|
||||
"Line Developers Console": "Кансоль распрацошчыкаў Line",
|
||||
"Basic Settings": "Базавыя налады",
|
||||
"User ID": "ID карыстальніка",
|
||||
"Your User ID": "Ваш ідэнтыфікатар карыстальніка",
|
||||
"Messaging API": "API паведамленняў",
|
||||
"wayToGetLineChannelToken": "Спачатку зайдзіце ў {0}, стварыце правайдэра і канал (API паведамленняў), потым вы зможаце атрымаць токен доступу да канала і ID карыстальніка з вышэйзгаданых пунктаў меню.",
|
||||
"Icon URL": "URL значка",
|
||||
"aboutIconURL": "Вы можаце ўставіць спасылку на значок ў поле \"URL значка\" каб змяніць малюнак профілю па змаўчанні. Не выкарыстоўваецца, калі зададзена значок Emoji.",
|
||||
"aboutMattermostChannelName": "Вы можаце перавызначыць канал па змаўчанні, у які вэбхук піша, уведаўшы імя канала ў поле \"Імя канала\". Гэта неабходна ўключыць у наладах вэбхука Mattermost. Напрыклад: #other-channel",
|
||||
"dataRetentionTimeError": "Перыяд захавання павінен быць 0 або больш",
|
||||
"infiniteRetention": "Выберыце 0 для бясконцага захавання.",
|
||||
"confirmDeleteTagMsg": "Вы сапраўды хочаце выдаліць гэты тэг? Маніторы, звязаныя з гэтым тэгам не будуць выдаленыя.",
|
||||
"enableGRPCTls": "Дазволіць адпраўляць gRPC запыт праз TLS злучэнне",
|
||||
"grpcMethodDescription": "Імя метада пераўтвараецца ў фармат camelCase, напрыклад, sayHello, check і г.д.",
|
||||
"acceptedStatusCodesDescription": "Выберыце коды статусаў для вызначэння даступнасці службы.",
|
||||
"deleteMonitorMsg": "Вы сапраўды хочаце выдаліць гэты манітор?",
|
||||
"deleteMaintenanceMsg": "Вы сапраўды хочаце выдаліць гэтае тэхабслугоўванне?",
|
||||
"deleteNotificationMsg": "Вы сапраўды хочаце выдаліць гэтую абвестку для ўсіх манітораў?",
|
||||
"dnsPortDescription": "Па змаўчанні порт DNS сервера - 53. Мы можаце змяніць яго ў любы час.",
|
||||
"resolverserverDescription": "Cloudflare з'яўляецца серверам па змаўчанні. Вы заўсёды можаце змяніць гэты сервер.",
|
||||
"rrtypeDescription": "Выберыце тып рэсурснага запісу, які вы хочаце адсочваць",
|
||||
"pauseMonitorMsg": "Вы сапраўды хочаце прыпыніць?",
|
||||
"enableDefaultNotificationDescription": "Для кожнага новага манітора гэта апавяшчэнне будзе ўключана па змаўчанні. Вы ўсё яшчэ можаце адключыць апавяшчэнні ў кожным маніторы асобна.",
|
||||
"clearEventsMsg": "Вы сапраўды хочаце выдаліць усю статыстыку падзей гэтага манітора?",
|
||||
"clearHeartbeatsMsg": "Вы сапраўды хочаце выдаліць усю статыстыку апытанняў гэтага манітора?",
|
||||
"confirmClearStatisticsMsg": "Вы сапраўды хочаце выдаліць УСЮ статыстыку?",
|
||||
"importHandleDescription": "Выберыце \"Прапусціць існуючыя\", калі вы хочаце прапусціць кожны манітор або апавяшчэнне з такой жа назвай. \"Перазапісаць\" выдаліць кожны існуючы манітор або апавяшчэнне і дадаць зноў. Варыянт \"Не правяраць\" прымусова адновіць усе маніторы і апавяшчэнні, нават калі яны ўжо існуюць.",
|
||||
"twoFAVerifyLabel": "Увядзіце свой токен, каб праверыць працу 2FA:",
|
||||
"tokenValidSettingsMsg": "Токен сапраўдны! Цяпер вы можаце захаваць налады 2FA.",
|
||||
"confirmImportMsg": "Вы сапраўды хочаце аднавіць рэзервовую копію? Пераканайцеся, што вы выбралі правільны варыянт імпарту.",
|
||||
"confirmEnableTwoFAMsg": "Вы сапраўды хочаце ўключыць 2FA?",
|
||||
"confirmDisableTwoFAMsg": "Вы сапраўды хочаце адключыць 2FA?",
|
||||
"affectedStatusPages": "Паказваць абвестку аб тэхабслугоўванні на выбраных старонках статуса",
|
||||
"atLeastOneMonitor": "Выберыце больш за адзін затрагаваны манітор",
|
||||
"passwordNotMatchMsg": "Уведзеныя паролі не супадаюць.",
|
||||
"notificationDescription": "Прымацаваць абвесткі да манітораў.",
|
||||
"keywordDescription": "Пошук слова ў чыстым HTML або ў JSON-адказе (адчувальны да рэгістра).",
|
||||
"invertKeywordDescription": "Шукаць, каб ключавое слова адсутнічала, а не прысутнічала.",
|
||||
"jsonQueryDescription": "Выконайце json-запыт да адказу і праверце наяўнасць чаканага значэння (вяртанае значэнне будзе пераўтворана ў радок для параўнання). Глядзіце {0} для атрымання дакументацыі па мове запытаў. А трэніравацца вы можаце {1}.",
|
||||
"backupDescription": "Вы можаце захаваць рэзервовую копію ўсіх манітораў і апавешчанняў у выглядзе JSON-файла.",
|
||||
"backupDescription2": "Важна: гісторыя і падзеі захаваныя не будуць.",
|
||||
"backupDescription3": "Важныя даныя, такія як токены апавешчанняў, дадаюцца пры экспарце, таму захоўвайце файлы ў бяспечным месцы.",
|
||||
"endpoint": "канчатковая кропка",
|
||||
"octopushAPIKey": "\"{API key}\" з даных уліковых запісаў HTTP API ў панэлі кіравання",
|
||||
"octopushLogin": "\"Login\" з даных уліковых запісаў HTTP API ў панэлі кіравання",
|
||||
"promosmsLogin": "Лагін API",
|
||||
"promosmsPassword": "Пароль API",
|
||||
"pushoversounds pushover": "Pushover (па змаўчанні)",
|
||||
"pushoversounds bike": "Веласіпед",
|
||||
"pushoversounds bugle": "Горн",
|
||||
"pushoversounds cashregister": "Касавы апарат",
|
||||
"pushoversounds classical": "Класічны",
|
||||
"pushoversounds cosmic": "Касмічны",
|
||||
"pushoversounds falling": "Падаючы",
|
||||
"pushoversounds intermission": "Антракт",
|
||||
"pushoversounds magic": "Магія",
|
||||
"pushoversounds mechanical": "Механічны",
|
||||
"pushoversounds pianobar": "Піяна-бар",
|
||||
"pushoversounds siren": "Сірэна",
|
||||
"pushoversounds spacealarm": "Касмічная сігналізацыя",
|
||||
"pushoversounds tugboat": "Буксір",
|
||||
"pushoversounds alien": "Іншаплянетная трывога (доўгая)",
|
||||
"pushoversounds persistent": "Настойлівы (доўгі)",
|
||||
"pushoversounds echo": "Pushover Эха (доўгае)",
|
||||
"pushoversounds updown": "Уверх уніз (доўгае)",
|
||||
"pushoversounds vibrate": "Толькі вібрацыя",
|
||||
"pushoversounds none": "Няма (ціха)",
|
||||
"pushyAPIKey": "Сакрэтны ключ API",
|
||||
"pushyToken": "Токен прылады",
|
||||
"apprise": "Apprise (Падтрымка 50+ сэрвісаў абвестак)",
|
||||
"GoogleChat": "Google Chat (толькі Google Workspace)",
|
||||
"wayToGetKookGuildID": "Уключыце \"Рэжым распрацошчыка\" у наладах Kook, а затым націсніце правай кнопкай на гільдыю, каб скапіраваць яе ID",
|
||||
"Guild ID": "Ідэнтыфікатар гільдыі",
|
||||
"User Key": "Ключ карыстальніка",
|
||||
"Message Title": "Загаловак паведамлення",
|
||||
"Notification Sound": "Гук абвесткі",
|
||||
"More info on:": "Больш інфармацыі на: {0}",
|
||||
"pushoverDesc1": "Экстрэмальны прыярытэт (2) мае таймаўт паўтору па змаўчанні 30 секунд і сканчаецца праз 1 гадзіну.",
|
||||
"pushoverDesc2": "Калі вы хочаце адпраўляць абвесткі розным прыладам, неабходна запоўніць поле Прылада.",
|
||||
"pushoverMessageTtl": "TTL паведамлення (у секундах)",
|
||||
"SMS Type": "Тып SMS",
|
||||
"octopushTypePremium": "Преміум (Хуткі - рэкамендуецца для аляртаў)",
|
||||
"octopushTypeLowCost": "Танны (Павольны - часам блакуецца аператарамі)",
|
||||
"apiCredentials": "API рэквізіты",
|
||||
"checkPrice": "Тарыфы {0}:",
|
||||
"octopushLegacyHint": "Вы выкарыстоўваеце старую версію Octopush (2011-2020) ці новую?",
|
||||
"Check octopush prices": "Тарыфы Octopush {0}.",
|
||||
"octopushPhoneNumber": "Нумар тэлефона (міжнародны фармат напр. +48123456789) ",
|
||||
"octopushSMSSender": "Імя адпраўніка SMS: 3-11 сімвалаў алфавіта, лічбаў і прабелаў (a-zA-Z0-9)",
|
||||
"LunaSea Device ID": "ID прылады LunaSea",
|
||||
"Apprise URL": "URL апавяшчэння",
|
||||
"Example:": "Прыклад: {0}",
|
||||
"Read more:": "Падрабязней: {0}",
|
||||
"Status:": "Статус: {0}",
|
||||
"Strategy": "Стратэгія",
|
||||
"Free Mobile User Identifier": "Бясплатны мабільны ідэнтыфікатар карыстальніка",
|
||||
"Free Mobile API Key": "API ключ Free Mobile",
|
||||
"Enable TLS": "Уключыць TLS",
|
||||
"Proto Service Name": "Назва службы Proto",
|
||||
"Proto Method": "Метад Proto",
|
||||
"Proto Content": "Змест Proto",
|
||||
"Economy": "Эканомія",
|
||||
"Lowcost": "Бюджэтны",
|
||||
"high": "высокі",
|
||||
"SendKey": "SendKey",
|
||||
"SMSManager API Docs": "Дакументацыя да API SMSManager ",
|
||||
"Gateway Type": "Тып шлюза",
|
||||
"You can divide numbers with": "Вы можаце дзяліць лічбы з",
|
||||
"Base URL": "Базавы URL",
|
||||
"goAlertInfo": "GoAlert — гэта праграма з адкрытым зыходным кодам для складання раскладу выклікаў, аўтаматычнай эскаляцыі і абвестак (напрыклад, SMS або галасавых выклікаў). Аўтаматычна прывабляйце патрэбнага чалавека, патрэбным спосабам і ў патрэбны час! {0}",
|
||||
"goAlertIntegrationKeyInfo": "Атрымаць агульны ключ інтэграцыі API для службы ў гэтым фармаце \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\" звычайна значэнне параметра токена скапіяванага URL.",
|
||||
"AccessKeyId": "ID ключа доступу",
|
||||
"SecretAccessKey": "Сакрэтны ключ доступу",
|
||||
"PhoneNumbers": "Нумары тэлефонаў",
|
||||
"TemplateCode": "Код шаблону",
|
||||
"SignName": "SignName",
|
||||
"Sms template must contain parameters: ": "Шаблон SMS павінен змяшчаць параметры: ",
|
||||
"Bark API Version": "Версія Bark API",
|
||||
"Bark Endpoint": "Канчатковая кропка Bark",
|
||||
"Bark Group": "Bark Group",
|
||||
"Bark Sound": "Bark Sound",
|
||||
"WebHookUrl": "WebHookUrl",
|
||||
"SecretKey": "Сакрэтны Ключ",
|
||||
"For safety, must use secret key": "Для бяспекі, неабходна выкарыстоўваць сакрэтны ключ",
|
||||
"Mentioning": "Згадванне",
|
||||
"Don't mention people": "Не згадваць людзей",
|
||||
"Mention group": "Згадаць {group}",
|
||||
"Device Token": "Токен прылады",
|
||||
"Platform": "Платформа",
|
||||
"High": "Высокі",
|
||||
"Retry": "Паўторыць",
|
||||
"Topic": "Тэма",
|
||||
"WeCom Bot Key": "WeCom Bot Key",
|
||||
"Setup Proxy": "Налада Проксі",
|
||||
"Proxy Protocol": "Пратакол Проксі",
|
||||
"Proxy Server": "Проксі",
|
||||
"Proxy server has authentication": "Проксі мае аўтэнтыфікацыю",
|
||||
"promosmsTypeEco": "SMS ECO - танкі і павольны, часта перагружаны. Толькі для атрымальнікаў з Польшчы.",
|
||||
"promosmsTypeFlash": "SMS FLASH - паведамленні аўтаматычна з'яўляюцца на прыладзе атрымальніка. Толькі для атрымальнікаў з Польшчы.",
|
||||
"promosmsTypeFull": "SMS FULL - прэміум-узровень SMS, можна выкарыстоўваць сваё імя адпраўніка (папярэдне зарэгістраваў яго). Надзейна для аляртаў.",
|
||||
"promosmsTypeSpeed": "SMS SPEED - найвышэйшы прыярытэт у сістэме. Вельмі хутка і надзейна, але вельмі дорага (у два разы дорага, чым SMS FULL).",
|
||||
"promosmsPhoneNumber": "Нумар тэлефона (для атрымальнікаў з Польшчы можна прапусціць код рэгіёна)",
|
||||
"promosmsSMSSender": "Імя адпраўніка SMS: Зарэгістраванае або адно з імён па змаўчанні: InfoSMS, SMS Info, MaxSMS, INFO, SMS",
|
||||
"promosmsAllowLongSMS": "Дазволіць доўгія SMS",
|
||||
"Feishu WebHookUrl": "Feishu WebHookURL",
|
||||
"matrixHomeserverURL": "URL сервера (разам з http(s):// і па жаданні порт)",
|
||||
"Internal Room Id": "Унутраны ID пакою",
|
||||
"matrixDesc1": "Унутраны ID пакою можна знайсці ў Падрабязнасцях у параметрах канала вашага кліента Matrix. Ён павінен выглядаць прыблізна так !QMdRCpUIfLwsfjxye6:home.server.",
|
||||
"matrixDesc2": "Рэкамендуецца стварыць новага карыстальніка і не выкарыстоўваць токен доступу асабістага карыстальніка Matrix, т.к. гэта ўяўляе за сабой поўны доступ да акаўнта і да пакояў, у якіх вы знаходзіцеся. Замест гэтага стварыце новага карыстальніка і запрасіце яго толькі ў той пакой, у якім вы хочаце атрымліваць абвесткі. Токен доступу можна атрымаць, выканаўшы каманду {0}",
|
||||
"Channel Name": "Назва канала",
|
||||
"Notify Channel": "Канал апавешчанняў",
|
||||
"aboutNotifyChannel": "Апавяшчэнне аб канале выкліча настольнае або мабільнае апавяшчэнне для ўсіх удзельнікаў канала, незалежна ад таго, ці ўстаноўлена іх даступнасць як актыўная або адсутная.",
|
||||
"Uptime Kuma URL": "Uptime Kuma URL",
|
||||
"setup a new monitor group": "наладзіць новую групу манітораў",
|
||||
"openModalTo": "адкрыць мадальнае акно {0}",
|
||||
"Add a domain": "Дадаць дамен",
|
||||
"Remove domain": "Выдаліць дамен '{0}'",
|
||||
"Icon Emoji": "Emoji",
|
||||
"signalImportant": "ВАЖНА: Нельга змешваць у Атрымальніках групы і нумары!",
|
||||
"aboutWebhooks": "Больш інфармацыі аб вэбхуках: {0}",
|
||||
"aboutChannelName": "Увядзіце назву канала ў поле {0} Назва канала, калі вы хочаце абысці канал вэбхука. Напрыклад: #other-channel",
|
||||
"aboutKumaURL": "Калі поле Uptime Kuma URL у наладах застанецца пустым, па змаўчанні будзе выкарыстоўвацца спасылка на праект на GitHub.",
|
||||
"smtpDkimSettings": "DKIM Налады",
|
||||
"smtpDkimDesc": "Калі ласка, азнаёмцеся з {0} Nodemailer DKIM для выкарыстання.",
|
||||
"documentation": "дакументацыяй",
|
||||
"smtpDkimDomain": "Назва дамена",
|
||||
"smtpDkimKeySelector": "Ключ",
|
||||
"smtpDkimPrivateKey": "Прыватны ключ",
|
||||
"smtpDkimHashAlgo": "Алгарытм хэша (неабавязкова)",
|
||||
"smtpDkimheaderFieldNames": "Загаловак ключоў для подпісу (неабавязкова)",
|
||||
"smtpDkimskipFields": "Загаловак ключоў не для подпісу (опцыянальна)",
|
||||
"wayToGetPagerDutyKey": "Вы можаце гэта атрымаць, перайшоўшы ў Сервіс -> Каталог сервісаў -> (Выберыце сервіс) -> Інтэграцыі -> Дадаць інтэграцыю. Тут вы можаце шукаць «Events API V2». Падрабязней {0}",
|
||||
"Integration Key": "Ключ інтэграцыі",
|
||||
"Integration URL": "URL інтэграцыі",
|
||||
"Auto resolve or acknowledged": "Аўтаматычнае развязванне або пацверджанне",
|
||||
"do nothing": "нічога не рабіць",
|
||||
"auto acknowledged": "аўтаматычна пацверджана",
|
||||
"auto resolve": "аўтаматычна развязана",
|
||||
"alertaApiEndpoint": "Канчатковая кропка API",
|
||||
"alertaEnvironment": "Асяроддзе",
|
||||
"alertaApiKey": "Ключ API",
|
||||
"alertaAlertState": "Стан алярта",
|
||||
"alertaRecoverState": "Стан аднаўлення",
|
||||
"serwersmsAPIUser": "API Карыстальнік (уключаючы прэфікс webapi_)",
|
||||
"serwersmsAPIPassword": "API Пароль",
|
||||
"serwersmsPhoneNumber": "Нумар тэлефона",
|
||||
"serwersmsSenderName": "SMS Імя адпраўніка (зарэгістравана праз карыстальніцкі партал)",
|
||||
"smseagleTo": "Нумар(ы) тэлефона",
|
||||
"smseagleGroup": "Назва(ы) групы тэлефоннай кнігі",
|
||||
"smseagleContact": "Імёны кантактаў тэлефоннай кнігі",
|
||||
"smseagleRecipientType": "Тып атрымальніка",
|
||||
"smseagleRecipient": "Атрымальнік(і) (калі множнасць, павінны быць раздзеленыя коскай)",
|
||||
"smseagleToken": "Токен доступу API",
|
||||
"smseagleUrl": "URL вашага прылады SMSEagle",
|
||||
"smseagleEncoding": "Адправіць у Unicode",
|
||||
"smseaglePriority": "Прыярытэт паведамлення (0-9, па змаўчанні = 0)",
|
||||
"Recipient Number": "Нумар атрымальніка",
|
||||
"From Name/Number": "Імя/нумар адпраўніка",
|
||||
"Leave blank to use a shared sender number.": "Пакіньце пустым, каб выкарыстоўваць агульны нумар адпраўніка.",
|
||||
"Octopush API Version": "Версія API Octopush",
|
||||
"Legacy Octopush-DM": "Састарэлы Octopush-DM",
|
||||
"ntfy Topic": "Тэма ntfy",
|
||||
"Server URL should not contain the nfty topic": "URL сервера не павінен утрымліваць тэму nfty",
|
||||
"onebotHttpAddress": "HTTP-адрас OneBot",
|
||||
"onebotMessageType": "Тып паведамлення OneBot",
|
||||
"onebotGroupMessage": "Група",
|
||||
"onebotPrivateMessage": "Асабістае",
|
||||
"onebotUserOrGroupId": "ID групы/карыстальніка",
|
||||
"onebotSafetyTips": "Для бяспекі неабходна ўсталяваць токен доступу",
|
||||
"PushDeer Server": "Сервер PushDeer",
|
||||
"pushDeerServerDescription": "Пакіньце пустым для выкарыстання афіцыйнага сервера",
|
||||
"PushDeer Key": "Ключ PushDeer",
|
||||
"wayToGetClickSendSMSToken": "Вы можаце атрымаць імя карыстальніка API і ключ API з {0} .",
|
||||
"Custom Monitor Type": "Сваёродны тып манітора",
|
||||
"Google Analytics ID": "ID Google Аналітыкі",
|
||||
"Edit Tag": "Рэдагаваць тэг",
|
||||
"Server Address": "Адрас сервера",
|
||||
"Learn More": "Даведацца больш",
|
||||
"Body Encoding": "Тып зместу запыту.(JSON або XML)",
|
||||
"API Keys": "API Ключы",
|
||||
"Expiry": "Сканчэнне",
|
||||
"Continue": "Працягнуць",
|
||||
"Add Another": "Дадаць яшчэ",
|
||||
"Key Added": "Ключ дададзены",
|
||||
"apiKeyAddedMsg": "Ваш ключ API дададзены. Звярніце ўвагу на гэтае паведамленне, так як яно адлюстроўваецца адзін раз.",
|
||||
"Add API Key": "Дадаць API ключ",
|
||||
"No API Keys": "Няма ключоў API",
|
||||
"apiKey-active": "Актыўны",
|
||||
"apiKey-expired": "Скончыўся",
|
||||
"apiKey-inactive": "Неактыўны",
|
||||
"Expires": "Сканчаецца",
|
||||
"disableAPIKeyMsg": "Вы ўпэўнены, што хочаце адключыць гэты API ключ?",
|
||||
"deleteAPIKeyMsg": "Вы ўпэўнены, што хочаце выдаліць гэты ключ API?",
|
||||
"Generate": "Згенераваць",
|
||||
"pagertreeIntegrationUrl": "URL-адрас інтэграцыі",
|
||||
"pagertreeUrgency": "Тэрміновасць",
|
||||
"pagertreeSilent": "Ціхі",
|
||||
"pagertreeLow": "Нізкі",
|
||||
"pagertreeMedium": "Сярэдні",
|
||||
"pagertreeHigh": "Высокі",
|
||||
"pagertreeCritical": "Крытычны",
|
||||
"pagertreeResolve": "Аўтаматычнае развязванне",
|
||||
"pagertreeDoNothing": "Нічога не рабіць",
|
||||
"wayToGetPagerTreeIntegrationURL": "Пасля стварэння інтэграцыі Uptime Kuma ў PagerTree скапіруйце файл {Endpoint}. Гл. поўную інфармацыю {0}",
|
||||
"lunaseaTarget": "Мэта",
|
||||
"lunaseaDeviceID": "Ідэнтыфікатар прылады",
|
||||
"lunaseaUserID": "Ідэнтыфікатар карыстальніка",
|
||||
"ntfyAuthenticationMethod": "Метад уваходу",
|
||||
"ntfyPriorityHelptextAllEvents": "Усе падзеі адпраўляюцца з максімальным прыярытэтам",
|
||||
"ntfyPriorityHelptextAllExceptDown": "Усе падзеі адпраўляюцца з гэтым прыярытэтам, акрамя {0}-падзеяў, якія маюць прыярытэт {1}",
|
||||
"ntfyUsernameAndPassword": "Лагін і пароль",
|
||||
"twilioAccountSID": "SID уліковага запісу",
|
||||
"twilioApiKey": "Ключ API (неабавязкова)",
|
||||
"twilioAuthToken": "Токен аўтарызацыі / Сакрэтны API ключ",
|
||||
"twilioFromNumber": "З нумара",
|
||||
"twilioToNumber": "На нумар",
|
||||
"Monitor Setting": "Налада манітора {0}",
|
||||
"Show Clickable Link": "Паказаць націскальную спасылку",
|
||||
"Show Clickable Link Description": "Калі пазначаны флажок, усе, хто мае доступ да гэтай старонкі стану, могуць мець доступ да URL-адрасу манітора.",
|
||||
"Open Badge Generator": "Адкрыць генератар значкаў",
|
||||
"Badge Generator": "Генератар значкоў для {0}",
|
||||
"Badge Type": "Тып значка",
|
||||
"Badge Duration (in hours)": "Тэрмін дзеяння значка (у гадзінах)",
|
||||
"Badge Label": "Надпіс для значка",
|
||||
"Badge Prefix": "Значэнне прэфікса значка",
|
||||
"Badge Suffix": "Значэнне суфікса значка",
|
||||
"Badge Label Color": "Колер надпісу значка",
|
||||
"Badge Color": "Колер значка",
|
||||
"Badge Label Prefix": "Прэфікс надпісу для значка",
|
||||
"Badge Preview": "Папярэдні прагляд значка",
|
||||
"Badge Label Suffix": "Суфікс надпісу для значка",
|
||||
"Badge Up Color": "Колер значка для статусу \"Даступны\"",
|
||||
"Badge Down Color": "Колер значка для статусу \"Недаступны\"",
|
||||
"Badge Pending Color": "Колер значка для статусу \"Чаканне\"",
|
||||
"Badge Maintenance Color": "Колер значка для статусу \"Тэхабслугоўванне\"",
|
||||
"Badge Warn Color": "Колер значка для папярэджання",
|
||||
"Badge Warn Days": "Значок для \"дзён папярэджання\"",
|
||||
"Badge Down Days": "Значок для \"дзён недаступнасці\"",
|
||||
"Badge Style": "Стыль значка",
|
||||
"Badge value (For Testing only.)": "Значэнне значка (толькі для тэставання)",
|
||||
"Group": "Група",
|
||||
"Monitor Group": "Група манітораў",
|
||||
"monitorToastMessagesLabel": "Апавяшчэнні",
|
||||
"monitorToastMessagesDescription": "Паведамленні для манітораў знікаюць праз зададзены час у секундах. Значэнне -1 адключае тайм-аўт. Значэнне 0 адключае апавяшчэнні.",
|
||||
"toastErrorTimeout": "Таймаут для апавешчанняў пра памылкі",
|
||||
"toastSuccessTimeout": "Таймаут для апавешчанняў пра паспяховасьць",
|
||||
"Enter the list of brokers": "Увядзіце спіс брокераў",
|
||||
"Kafka Brokers": "Kafka Brokers",
|
||||
"Press Enter to add broker": "Націсніце Enter, каб дадаць брокера",
|
||||
"Kafka Topic Name": "Назва тэмы Kafka",
|
||||
"Kafka Producer Message": "Паведамленне продюсера Kafka",
|
||||
"Enable Kafka SSL": "Уключэнне пратаколу Kafka SSL",
|
||||
"Enable Kafka Producer Auto Topic Creation": "Уключэнне аўтаматычнага стварэння тэм у Kafka Producer",
|
||||
"Kafka SASL Options": "Параметры SASL у Kafka",
|
||||
"Mechanism": "Механізм",
|
||||
"Pick a SASL Mechanism...": "Выберыце механізм SASL…",
|
||||
"Authorization Identity": "Аўтарызацыйная ідэнтычнасць",
|
||||
"AccessKey Id": "AccessKey Id",
|
||||
"Secret AccessKey": "Сакрэтны ключ доступу",
|
||||
"Session Token": "Токен сеансу",
|
||||
"noGroupMonitorMsg": "Не даступна. Спачатку стварыце групу манітораў.",
|
||||
"Close": "Закрыць",
|
||||
"Request Body": "Цела запыту",
|
||||
"wayToGetFlashDutyKey": "Вы можаце перайсці на старонку \"Канал\" -> (Выберыце канал) -> \"Інтэграцыі\" -> \"Дадаць новую старонку інтэграцыі\", дадаць \"Карыстацкую падзею\", каб атрымаць push-адрас, скапіяваць ключ інтэграцыі ў адрас. Для атрымання дадатковай інфармацыі, калі ласка, наведайце",
|
||||
"FlashDuty Severity": "Сур'ёзнасць",
|
||||
"nostrRecipients": "Адкрытыя ключы атрымальнікаў (npub)",
|
||||
"nostrRelaysHelp": "Адзін URL-адрас рэтрансляцыі ў кожным радку",
|
||||
"nostrSender": "Закрыты ключ адпраўшчыка (nsec)",
|
||||
"nostrRecipientsHelp": "фармат npub, па адным у радку",
|
||||
"showCertificateExpiry": "Паказваць пратэрмінаваны сертыфікат",
|
||||
"noOrBadCertificate": "Адсутнасць сертыфіката",
|
||||
"gamedigGuessPortDescription": "Порт, які выкарыстоўваецца пратаколам Valve Server Query Protocol, можа адрознівацца ад порта кліента. Паспрабуйце гэта, калі манітор не можа падключыцца да сервера.",
|
||||
"authUserInactiveOrDeleted": "Карыстальнік неактыўны або выдалены.",
|
||||
"authInvalidToken": "Няправільны токен.",
|
||||
"authIncorrectCreds": "Няправільнае імя карыстальніка або пароль.",
|
||||
"2faAlreadyEnabled": "2FA ўжо ўключана.",
|
||||
"2faEnabled": "2FA ўключана.",
|
||||
"2faDisabled": "2FA адключана.",
|
||||
"successAdded": "Паспяхова дададзена.",
|
||||
"successResumed": "Паспяхова прадоўжана.",
|
||||
"successPaused": "Паспяхова спынена.",
|
||||
"successDeleted": "Паспяхова выдалена.",
|
||||
"successEdited": "Паспяхова зменена.",
|
||||
"successAuthChangePassword": "Пароль паспяхова абноўлены.",
|
||||
"successBackupRestored": "Рэзервовая копія паспяхова адноўлена.",
|
||||
"successDisabled": "Паспяхова адключана.",
|
||||
"successEnabled": "Паспяхова ўключана.",
|
||||
"tagNotFound": "Тэг не знойдзены.",
|
||||
"foundChromiumVersion": "Выяўлены Chromium/Chrome. Версіі: {0}",
|
||||
"Remote Browsers": "Аддаленыя браўзеры",
|
||||
"Remote Browser": "Аддалены браўзер",
|
||||
"Add a Remote Browser": "Дадаць аддалены браўзер",
|
||||
"Remote Browser not found!": "Аддалены браўзер не знойдзены!",
|
||||
"remoteBrowsersDescription": "Аддаленыя браўзеры — альтэрнатыва лакальнаму запуску Chromium. Усталюйце такі сервіс, як browserless.io, або падлучыцеся да свайго ўласнага",
|
||||
"self-hosted container": "кантэйнер, які хостыцца самастойна",
|
||||
"remoteBrowserToggle": "Па змаўчаньні Chromium працуе ўнутры кантэйнера Uptime Kuma. Вы можаце выкарыстоўваць аддалены браўзер, пераключыўшы гэты пераключальнік.",
|
||||
"useRemoteBrowser": "Выкарыстоўваць удалены браўзер",
|
||||
"deleteRemoteBrowserMessage": "Вы ўпэўнены, што хочаце выдаліць гэты удалены браўзер для ўсіх манітораў?",
|
||||
"Browser Screenshot": "Скрыншот браўзера",
|
||||
"wayToGetSevenIOApiKey": "Зайдзіце на панэль кіравання па адрасе app.seven.io > распрацоўшчык > {api key} > зялёная кнопка дадаць",
|
||||
"senderSevenIO": "Адпраўляе нумар або імя",
|
||||
"receiverSevenIO": "Нумар атрымання",
|
||||
"apiKeySevenIO": "SevenIO {API Key}",
|
||||
"receiverInfoSevenIO": "Калі нумар атрымальніка не знаходзіцца ў Германіі, то перад нумарам неабходна дадаць код краіны (напрыклад, для ЗША код краіны 1, тады выкарыстоўвайце 117612121212, замест 017612121212)",
|
||||
"wayToGetWhapiUrlAndToken": "Вы можаце атрымаць {API URL} і токен, зайдзяўшы ў патрэбны вам канал з {0}",
|
||||
"whapiRecipient": "Нумар тэлефона / ID кантакта / ID групы",
|
||||
"documentationOf": "{0} Дакументацыя",
|
||||
"What is a Remote Browser?": "Што такое аддалены браўзер?",
|
||||
"wayToGetHeiiOnCallDetails": "Як атрымаць ID трыгера і {API Keys}, напісана ў {documentation}",
|
||||
"callMeBotGet": "Тут вы можаце стварыць {endpoint} для {0}, {1} і {2}. Майце на ўвазе, што вы можаце атрымаць абмежаванне па хуткасці. Абмежаванні па хуткасці выглядаюць наступным чынам: {3}",
|
||||
"gtxMessagingApiKeyHint": "Вы можаце знайсці свой {API key} на старонцы: Мае уліковыя запісы маршрутызацыі > Паказаць інфармацыю аб уліковым запісе > Уліковыя даныя API > REST API (v2.x)",
|
||||
"From Phone Number / Transmission Path Originating Address (TPOA)": "Нумар тэлефона / Адрас крыніцы шляху перадачы (АІПП)",
|
||||
"To Phone Number": "На нумар тэлефона",
|
||||
"gtxMessagingToHint": "Міжнародны фармат, з «+» ({e164}, {e212} або {e214})",
|
||||
"Alphanumeric (recommended)": "Літарна-лічбавая (рэкамендуецца)",
|
||||
"Telephone number": "Нумар тэлефона",
|
||||
"cellsyntOriginatortypeAlphanumeric": "Літарна-лічбавы радок (не больш за 11 літарна-лічбавых сімвалаў). Атрымальнікі не могуць адказаць на гэта паведамленне.",
|
||||
"cellsyntOriginatortypeNumeric": "Лічбавае значэнне (не больш за 15 лічбаў) з нумарам тэлефона ў міжнародным фармаце без 00 ў пачатку (напрыклад, нумар Вялікабрытаніі 07920 110 000 павінен быць заданы, як 447920110000). Атрымальнікі могуць адказаць на паведамленне.",
|
||||
"Originator": "Крыніца",
|
||||
"cellsyntOriginator": "Бачны на мабільным тэлефоне атрымальніка як адпраўшчыка паведамлення. Дапушчальныя значэнні і функцыя залежаць ад параметра {originatortype}.",
|
||||
"cellsyntDestination": "Нумар тэлефона атрымальніка ў міжнародным фармаце з 00 ў пачатку, за якім следуе код краіны, напрыклад, 00447920110000 для нумара Вялікабрытаніі 07920 110 000 (не больш за 17 лічбаў у суме). Не больш за 25000 атрымальнікаў, раздзеленых коскамі, на адзін HTTP-запыт.",
|
||||
"Allow Long SMS": "Дазволіць доўгія SMS",
|
||||
"cellsyntSplitLongMessages": "Раздзеляць доўгія паведамленні на 6 частак. 153 x 6 = 918 сімвалаў.",
|
||||
"max 15 digits": "макс. 15 лічбаў",
|
||||
"max 11 alphanumeric characters": "максімум 11 літарна-лічбавых сімвалаў",
|
||||
"Either enter the hostname of the server you want to connect to or localhost if you intend to use a locally configured mail transfer agent": "Увядзіце {Hostname} сервера, да якога вы хочаце падключыцца, або {localhost}, калі вы хочаце выкарыстоўваць {local_mta}",
|
||||
"Saved.": "Захавана.",
|
||||
"wayToWriteWhapiRecipient": "Нумар тэлефона з міжнародным прэфіксам, але без знака плюс у пачатку ({0}), ідэнтыфікатара кантакта ({1}) або ідэнтыфікатара групы ({2}).",
|
||||
"gtxMessagingFromHint": "На мабільных тэлефонах атрымальнікі бачаць АІПП як адпраўшчыка паведамлення. Дапускаецца выкарыстаньне да 11 літарна-лічбавых сімвалаў, шорткода, мясцовага доўгага кода або міжнародных нумароў ({e164}, {e212} або {e214})",
|
||||
"Send to channel": "Адправіць у канал",
|
||||
"threadForumPostID": "Трэд / ID паста",
|
||||
"whatHappensAtForumPost": "Стварыць новы пост на форуме. Гэта НЕ размяшчае паведамленні ў існуючым пасце. Для публікацыі ў існуючай публікацыі выкарыстоўвайце \"{option}\""
|
||||
}
|
|
@ -77,7 +77,7 @@
|
|||
"Save": "Запази",
|
||||
"Notifications": "Известия",
|
||||
"Not available, please setup.": "Не са налични. Моля, настройте.",
|
||||
"Setup Notification": "Настрой известие",
|
||||
"Setup Notification": "Настройка на известие",
|
||||
"Light": "Светла",
|
||||
"Dark": "Тъмна",
|
||||
"Auto": "Автоматично",
|
||||
|
@ -146,7 +146,7 @@
|
|||
"Options": "Опции",
|
||||
"Keep both": "Запази двете",
|
||||
"Verify Token": "Провери токен код",
|
||||
"Setup 2FA": "Настройка 2FA",
|
||||
"Setup 2FA": "Настройка на 2FA",
|
||||
"Enable 2FA": "Активирай 2FA",
|
||||
"Disable 2FA": "Деактивирай 2FA",
|
||||
"2FA Settings": "Настройка за 2FA",
|
||||
|
@ -403,7 +403,7 @@
|
|||
"Retry": "Повтори",
|
||||
"Topic": "Тема",
|
||||
"WeCom Bot Key": "WeCom бот ключ",
|
||||
"Setup Proxy": "Настрой прокси",
|
||||
"Setup Proxy": "Настройка на прокси",
|
||||
"Proxy Protocol": "Прокси протокол",
|
||||
"Proxy Server": "Прокси сървър",
|
||||
"Proxy server has authentication": "Прокси сървърът е с удостоверяване",
|
||||
|
@ -802,7 +802,6 @@
|
|||
"twilioApiKey": "API ключ (по избор)",
|
||||
"Expected Value": "Очаквана стойност",
|
||||
"Json Query": "Заявка тип JSON",
|
||||
"jsonQueryDescription": "Прави JSON заявка срещу отговора и проверява за очаквана стойност (Върнатата стойност ще бъде преобразувана в низ за сравнение). Разгледайте {0} за документация относно езика на заявката. Имате възможност да тествате {1}.",
|
||||
"Badge Duration (in hours)": "Времетраене на баджа (в часове)",
|
||||
"Badge Preview": "Преглед на баджа",
|
||||
"Notify Channel": "Канал за известяване",
|
||||
|
@ -897,10 +896,10 @@
|
|||
"DockerHostRequired": "Моля, задайте \"Docker\" хоста за този монитор.",
|
||||
"Browser Screenshot": "Екранна снимка на браузър",
|
||||
"remoteBrowserToggle": "По подразбиране Chromium работи в контейнера Uptime Kuma. Можете да използвате отдалечен браузър, като превключите този ключ.",
|
||||
"remoteBrowsersDescription": "Отдалечените браузъри са алтернатива на локалното стартиране на Chromium. Настройте с услуга като browserless.io или свържете с вашата собствена",
|
||||
"remoteBrowsersDescription": "Отдалечените браузъри са алтернатива на локалното стартиране на Chromium. Настройте с услуга като \"browserless.io\" или свържете с Вашата собствена",
|
||||
"Remove the expiry notification": "Премахни деня за известяване при изтичане",
|
||||
"Add a new expiry notification day": "Добави нов ден за известяване при изтичане",
|
||||
"setup a new monitor group": "настройване на нова група от монитори",
|
||||
"setup a new monitor group": "настройка на нова група от монитори",
|
||||
"openModalTo": "отвори модален прозорец към {0}",
|
||||
"Add a domain": "Добави домейн",
|
||||
"Remove domain": "Премахни домейн '{0}'",
|
||||
|
@ -973,5 +972,23 @@
|
|||
"Create new forum post": "Създай нова публикация във форум",
|
||||
"postToExistingThread": "Публикувай в съществуваща тема/публикация във форум",
|
||||
"forumPostName": "Име на публикацията във форума",
|
||||
"threadForumPostID": "ID на публикация в темата/форум"
|
||||
"threadForumPostID": "ID на публикация в темата/форум",
|
||||
"smspartnerApiurl": "Можете да намерите вашия API ключ в таблото на {0}",
|
||||
"smspartnerPhoneNumber": "Телефон номер(а)",
|
||||
"smspartnerPhoneNumberHelptext": "Номерът задължително е в международен формат {0}, {1}. Отделните номера трябва да бъдат разделени посредством {2}",
|
||||
"smspartnerSenderName": "Име на изпращащия на SMS",
|
||||
"smspartnerSenderNameInfo": "Трябва да е между 3..=11 стандартни знака",
|
||||
"wayToGetThreemaGateway": "Можете да се регистрирате за Threema Gateway {0}.",
|
||||
"threemaRecipient": "Получател",
|
||||
"threemaRecipientType": "Тип получател",
|
||||
"threemaRecipientTypeIdentity": "Threema-ID",
|
||||
"threemaRecipientTypePhone": "Телефонен номер",
|
||||
"threemaRecipientTypeEmail": "Имейл адрес",
|
||||
"threemaSenderIdentity": "Gateway-ID",
|
||||
"threemaSenderIdentityFormat": "8 знака, обикновено започва с *",
|
||||
"threemaApiAuthenticationSecret": "Gateway-ID Тайна фраза",
|
||||
"threemaRecipientTypeIdentityFormat": "8 знака",
|
||||
"threemaRecipientTypePhoneFormat": "E.164, без водещ +",
|
||||
"threemaBasicModeInfo": "Забележка: Тази интеграция използва Threema Gateway в основен режим (сървърно базирано криптиране). Допълнителни подробности можете да намерите {0}.",
|
||||
"apiKeysDisabledMsg": "API ключовете са деактивирани, защото удостоверяването е деактивирано."
|
||||
}
|
||||
|
|
|
@ -1 +1,18 @@
|
|||
{}
|
||||
{
|
||||
"setupDatabaseChooseDatabase": "আপনি কোন ডাটাবেজটি ব্যবহার করতে চান?",
|
||||
"setupDatabaseEmbeddedMariaDB": "আপনাকে কিছু নিযুক্ত করতে হবে না। এই ডকার ইমেজটি (Docker image) স্বয়ংক্রিয়ভাবে আপনার জন্য মারিয়া ডিবি (MariaDB) বসিয়েছে এবং প্রস্তুত করেছে।Uptime Kuma ইউনিক্স সকেটের (Unix Socket) মাধ্যমে এই ডাটাবেসের সাথে সংযুক্ত হবে।",
|
||||
"setupDatabaseMariaDB": "একটি বহিরাগত মারিয়া ডিবি (MariaDB) ডাটাবেসের সাথে সংযোগ করুন। আপনাকে ডাটাবেস সংযোগ তথ্য নিযুক্ত করতে হবে।",
|
||||
"Add": "সংযোগ করুন",
|
||||
"dbName": "ডাটাবেজের নাম",
|
||||
"languageName": "ইংরেজি",
|
||||
"Settings": "সেটিংস",
|
||||
"Dashboard": "ড্যাশবোর্ড",
|
||||
"Help": "সাহায্য",
|
||||
"New Update": "নতুন আপডেট",
|
||||
"Language": "ভাষা",
|
||||
"Version": "সংস্করণ",
|
||||
"Check Update On GitHub": "GitHub-এ আপডেট চেক করুন",
|
||||
"List": "তালিকা",
|
||||
"General": "সাধারণ",
|
||||
"Game": "খেলা"
|
||||
}
|
||||
|
|
145
src/lang/ca.json
145
src/lang/ca.json
|
@ -70,5 +70,148 @@
|
|||
"Either enter the hostname of the server you want to connect to or localhost if you intend to use a locally configured mail transfer agent": "Introduïu el nom del servidor al qual voleu connectar-vos o {localhost} si voleu utilitzar un {local_mta}",
|
||||
"Host URL": "URL del servidor",
|
||||
"Friendly Name": "Nom senzill",
|
||||
"markdownSupported": "Sintaxi de Markdown suportada"
|
||||
"markdownSupported": "Sintaxi de Markdown suportada",
|
||||
"Retries": "Reintents",
|
||||
"Advanced": "Avançat",
|
||||
"ignoreTLSErrorGeneral": "Ignora errors TLS/SSL per connexió",
|
||||
"maxRedirectDescription": "Nombre màxim de redireccions a seguir. Establiu a 0 per a desactivar les redireccions.",
|
||||
"Upside Down Mode": "Mode al revés",
|
||||
"Max. Redirects": "Redireccions Màx",
|
||||
"Accepted Status Codes": "Codis d'Estat Acceptats",
|
||||
"needPushEvery": "Hauries de cridar a aquesta URL cada {0} segons.",
|
||||
"Heartbeat Retry Interval": "Reintent de l'interval de \"heartbeat\"",
|
||||
"Resend Notification if Down X times consecutively": "Reenvia notificacions si Down X vegades consecutives",
|
||||
"resendEveryXTimes": "Reenvia cada {0} vegades",
|
||||
"retryCheckEverySecond": "Reintenta cada {0} segons",
|
||||
"checkEverySecond": "Comprova cada {0} segons",
|
||||
"resendDisabled": "Reenvia deshabilitat",
|
||||
"retriesDescription": "Màxim d'intents abans de que el servei sigui marcat com a caigut i la notificació sigui enviada",
|
||||
"ignoreTLSError": "Ignora errors de TLS/SSL per a pàgines web HTTPS",
|
||||
"upsideDownModeDescription": "Canvia l'estat al revés. Si el servei és accessible, està CAIGUT.",
|
||||
"Setup Notification": "Configurar Notificació",
|
||||
"Allow indexing": "Permetre indexat",
|
||||
"Discourage search engines from indexing site": "Desencoratjar als motors de cerca que indexin la pàgina",
|
||||
"Current Password": "Contrasenya actual",
|
||||
"Please use this option carefully!": "Per favor, empra aquesta opció amb cura !",
|
||||
"disable authentication": "deshabilita autenticació",
|
||||
"Partially Degraded Service": "Servei Parcialment Degradat",
|
||||
"Degraded Service": "Servei Degradat",
|
||||
"Add Group": "Afegir Grup",
|
||||
"Add a monitor": "Afegir monitor",
|
||||
"pushViewCode": "Com emprar el monitor de Push? (Veure Codi)",
|
||||
"Notifications": "Notificacions",
|
||||
"pushOthers": "Altres",
|
||||
"programmingLanguages": "Llenguatges de programació",
|
||||
"Dark": "Fosc",
|
||||
"Remember me": "Recordar-me",
|
||||
"Login": "Iniciar sessió",
|
||||
"No Monitors, please": "Sense Monitors, per favor",
|
||||
"notAvailableShort": "N/A",
|
||||
"Two Factor Authentication": "Segon Factor d'Autenticació",
|
||||
"Custom": "Personalitzat",
|
||||
"Search...": "Cercar…",
|
||||
"Search monitored sites": "Cercar llocs monitoritzats",
|
||||
"Avg. Response": "Resp. Promig",
|
||||
"Add New below or Select...": "Afegir nova o Seleccionar…",
|
||||
"Tag with this name already exist.": "Aquesta etiqueta ja existeix.",
|
||||
"color": "Color",
|
||||
"Gray": "Gris",
|
||||
"value (optional)": "valor (opcional)",
|
||||
"Active": "Actiu",
|
||||
"Push URL": "URL push",
|
||||
"pushOptionalParams": "Paràmetres opcionals: {0}",
|
||||
"Save": "Desa",
|
||||
"Not available, please setup.": "No disponible, per favor configura-ho.",
|
||||
"Light": "Clar",
|
||||
"Auto": "Auto",
|
||||
"Theme - Heartbeat Bar": "Tema - Heartbet Bar",
|
||||
"styleElapsedTime": "Temps transcorregut a la barra",
|
||||
"styleElapsedTimeShowNoLine": "Mostrar (Fora Línia)",
|
||||
"styleElapsedTimeShowWithLine": "Mostrar (Amb línia)",
|
||||
"Normal": "Normal",
|
||||
"Bottom": "Inferior",
|
||||
"None": "Cap",
|
||||
"Timezone": "Zona Horària",
|
||||
"Search Engine Visibility": "Visibilitat motor de cerca",
|
||||
"Change Password": "Canviar contrasenya",
|
||||
"New Password": "Nova Contrasenya",
|
||||
"Repeat New Password": "Repeteix Nova Contrasenya",
|
||||
"Update Password": "Actualitzar Contrasenya",
|
||||
"Disable Auth": "Deshabilita autenticació",
|
||||
"Enable Auth": "Habilita Autenticació",
|
||||
"disableauth.message1": "Estau segur que voleu {disableAuth}?",
|
||||
"disableauth.message2": "Està dissenyat per a escenaris {intendThirdPartyAuth} davant Uptime Kuma, com ara Cloudflare Access, Authelia o altres mecanismes d'autenticació.",
|
||||
"where you intend to implement third-party authentication": "on es vol implementar l'autenticació de tercers",
|
||||
"Logout": "Tancar sessió",
|
||||
"Leave": "Marxar",
|
||||
"I understand, please disable": "Ho entenc, per favor deshabilita-ho",
|
||||
"Confirm": "Confirma",
|
||||
"Yes": "Si",
|
||||
"No": "No",
|
||||
"Username": "Nom d'usuari",
|
||||
"Password": "Contrasenya",
|
||||
"add one": "afegir un",
|
||||
"Notification Type": "Tipus de notificació",
|
||||
"Test": "Test",
|
||||
"Certificate Info": "Informació del certificat",
|
||||
"Resolver Server": "Servidor DNS",
|
||||
"Resource Record Type": "Tipus de registre",
|
||||
"Create your admin account": "Crear compte d'administració",
|
||||
"Repeat Password": "Repeteix Contrasenya",
|
||||
"Export Backup": "Exportar Còpia",
|
||||
"Import Backup": "Importar Còpia",
|
||||
"Export": "Exporta",
|
||||
"Import": "Importa",
|
||||
"respTime": "Temps Resp. (ms)",
|
||||
"Default enabled": "Habilitat per defecte",
|
||||
"Apply on all existing monitors": "Aplicar a tots els monitors existents",
|
||||
"Create": "Crear",
|
||||
"Clear Data": "Esborra dades",
|
||||
"Events": "Events",
|
||||
"Heartbeats": "Heartbeat",
|
||||
"Auto Get": "Obtenir automàticament",
|
||||
"Schedule maintenance": "Programar manteniment",
|
||||
"Affected Monitors": "Monitors Afectats",
|
||||
"Pick Affected Monitors...": "Seleccionar Monitors Afectats…",
|
||||
"Start of maintenance": "Inici del manteniment",
|
||||
"All Status Pages": "Totes les pàgines d'estat",
|
||||
"Select status pages...": "Selecciona pàgines d'estat…",
|
||||
"alertNoFile": "Per favor, selecciona fitxer a importar.",
|
||||
"alertWrongFileType": "Selecciona un fitxer JSON.",
|
||||
"Clear all statistics": "Esborra totes les Estadístiques",
|
||||
"Skip existing": "Ometre existent",
|
||||
"Overwrite": "Sobreescriu",
|
||||
"Options": "Opcions",
|
||||
"Keep both": "Manté ambdos",
|
||||
"Verify Token": "Verificar token",
|
||||
"Setup 2FA": "Configurar 2FA",
|
||||
"Enable 2FA": "Habilitar 2FA",
|
||||
"Disable 2FA": "Deshabilitar 2FA",
|
||||
"2FA Settings": "Ajustaments 2FA",
|
||||
"filterActive": "Actiu",
|
||||
"filterActivePaused": "Pausat",
|
||||
"Inactive": "Inactiu",
|
||||
"Token": "Token",
|
||||
"Show URI": "Mostrar URI",
|
||||
"Tags": "Etiquetes",
|
||||
"Red": "Vermell",
|
||||
"Orange": "Taronja",
|
||||
"Green": "Verd",
|
||||
"Blue": "Blau",
|
||||
"Indigo": "Morat",
|
||||
"Purple": "Porpra",
|
||||
"Pink": "Rosa",
|
||||
"Avg. Ping": "Ping promig",
|
||||
"Entry Page": "Pàgina d'entrada",
|
||||
"statusPageNothing": "Res per aquí, per favor afegeix un grup o un monitor.",
|
||||
"statusPageRefreshIn": "Refrescat en: {0]",
|
||||
"No Services": "Sense Servei",
|
||||
"All Systems Operational": "Tots els sistemes operatius",
|
||||
"Edit Status Page": "Editar pàgina Estat",
|
||||
"Go to Dashboard": "Anar al Panell",
|
||||
"Status Page": "Pàgina d'Estat",
|
||||
"Email": "Correu",
|
||||
"Last Result": "Darrer Resultat",
|
||||
"Add New Tag": "Afegir nova etiqueta",
|
||||
"Tag with this value already exist.": "Ja existeix una etiqueta amb aquest valor."
|
||||
}
|
||||
|
|
|
@ -823,7 +823,6 @@
|
|||
"Enable Kafka Producer Auto Topic Creation": "Povolit Kafka zprostředkovateli automatické vytváření vláken",
|
||||
"Kafka Producer Message": "Zpráva Kafka zprostředkovatele",
|
||||
"tailscalePingWarning": "Abyste mohli používat Tailscale Ping monitor, je nutné Uptime Kuma nainstalovat mimo Docker, a dále na váš server nainstalovat Tailscale klienta.",
|
||||
"jsonQueryDescription": "Proveďte JSON dotaz vůči odpovědi a zkontrolujte očekávaný výstup (za účelem porovnání bude návratová hodnota převedena na řetězec). Dokumentaci k dotazovacímu jazyku naleznete na {0}, a využít můžete též {1}.",
|
||||
"Select": "Vybrat",
|
||||
"selectedMonitorCount": "Vybráno: {0}",
|
||||
"Check/Uncheck": "Vybrat/Zrušit výběr",
|
||||
|
@ -833,16 +832,16 @@
|
|||
"nostrRelays": "Relé Nostr",
|
||||
"FlashDuty Severity": "Závažnost",
|
||||
"PushDeer Server": "Server PushDeer",
|
||||
"wayToGetFlashDutyKey": "Můžete přejít na stránku Kanál -> (Vyberte kanál) -> Integrace -> Přidat novou integraci, přidat \"Vlastní událost\" a získat adresu pro odeslání, zkopírovat klíč integrace do adresy. Další informace naleznete na adrese",
|
||||
"wayToGetFlashDutyKey": "Můžete přejít na stránku \"Kanál -> (Vyberte kanál) -> Integrace -> Přidat novou integraci\", přidat \"Uptime Kuma\" a získat push adresu, zkopírovat integrační klíč v adrese. Další informace naleznete na adrese",
|
||||
"Request Timeout": "Časový limit požadavku",
|
||||
"timeoutAfter": "Vypršení časového limitu po {0} sekundách",
|
||||
"styleElapsedTime": "Čas uplynulý pod heartbeat ukazatelem",
|
||||
"styleElapsedTimeShowWithLine": "Zobrazit (s linkou)",
|
||||
"gamedigGuessPortDescription": "Port používaný protokolem Valve Server Query Protocol se může lišit od portu klienta. Pokud se monitor nemůže připojit k serveru, zkuste to.",
|
||||
"styleElapsedTimeShowNoLine": "Zobrazit (bez linky)",
|
||||
"gamedigGuessPort": "Gamedig: Guess Port",
|
||||
"gamedigGuessPort": "Gamedig: Port",
|
||||
"Saved.": "Uloženo.",
|
||||
"setupDatabaseChooseDatabase": "Kterou databázi chcete použít?",
|
||||
"setupDatabaseChooseDatabase": "Kterou databázi byste chtěli používat?",
|
||||
"setupDatabaseEmbeddedMariaDB": "Nemusíte nic nastavovat. Tento Docker obraz pro vás automaticky vložil a nakonfiguroval databázi MariaDB. Uptime Kuma se k této databázi připojí prostřednictvím unixového socketu.",
|
||||
"setupDatabaseMariaDB": "Připojení k externí databázi MariaDB. Je třeba nastavit informace o připojení k databázi.",
|
||||
"setupDatabaseSQLite": "Jednoduchý databázový soubor, doporučený pro malé instalace. Před verzí 2.0.0 používal Uptime Kuma jako výchozí databázi SQLite.",
|
||||
|
@ -895,9 +894,9 @@
|
|||
"emailTemplateLimitedToUpDownNotification": "dostupné pouze pro heatbeaty BĚŽÍ/NEBĚŽÍ, jinak null",
|
||||
"templateMonitorJSON": "objekt popisující dohled",
|
||||
"templateLimitedToUpDownNotifications": "dostupné pouze pro oznámení BĚŽÍ/NEBĚŽÍ",
|
||||
"successKeyword": "Nalezení klíčového slova",
|
||||
"Search monitored sites": "Vyhledávání dohledů",
|
||||
"settingUpDatabaseMSG": "Vytvářím strukturu databáze. Může to chvíli trvat, buďte trpěliví.",
|
||||
"successKeyword": "Úspěch Klíčové slovo",
|
||||
"Search monitored sites": "Hledat v monitorovaných umístěních",
|
||||
"settingUpDatabaseMSG": "Nastavuje se databáze. Prosím buďte trpělivý, může to chvíli trvat.",
|
||||
"successKeywordExplanation": "Klíčové slovo MQTT, které bude považováno za úspěch",
|
||||
"Browser Screenshot": "Snímek obrazovky prohlížeče",
|
||||
"setup a new monitor group": "nastavení nové skupiny dohledů",
|
||||
|
@ -944,7 +943,7 @@
|
|||
"remoteBrowserToggle": "Ve výchozím nastavení běží Chromium uvnitř kontejneru Uptime Kuma. Přepnutím tohoto přepínače můžete použít vzdálený prohlížeč.",
|
||||
"wayToGetWhapiUrlAndToken": "URL rozhraní API a token získáte tak, že přejdete do požadovaného kanálu z {0}",
|
||||
"wayToWriteWhapiRecipient": "Telefonní číslo s mezinárodní předvolbou, ale bez znaménka plus na začátku ({0}), ID kontaktu ({1}) nebo ID skupiny ({2}).",
|
||||
"remoteBrowsersDescription": "Vzdálené prohlížeče jsou alternativou k lokálně spuštěnému Chromu. Nastavte tuto možnost pomocí služby jako je browserless.io, nebo se připojte k vlastnímu vzdálenému prohlížeči",
|
||||
"remoteBrowsersDescription": "Vzdálené prohlížeče jsou alternativou k místnímu spuštění Chromu. Nastavte se pomocí služby, jako je browserless.io, nebo se připojte k vlastnímu",
|
||||
"statusPageSpecialSlugDesc": "Speciální slug {0}: tato stránka se zobrazí, pokud není definován žádný slug",
|
||||
"Mentioning": "Zmínky",
|
||||
"wayToGetSevenIOApiKey": "Navštivte ovládací panel v části app.seven.io > developer > api key > zelené tlačítko přidat",
|
||||
|
@ -953,5 +952,42 @@
|
|||
"apiKeySevenIO": "API klíč SevenIO",
|
||||
"locally configured mail transfer agent": "místně nakonfigurovaný agent pro přenos pošty",
|
||||
"Either enter the hostname of the server you want to connect to or localhost if you intend to use a locally configured mail transfer agent": "Zadejte název hostitele serveru, ke kterému se chcete připojit, nebo {localhost}, pokud hodláte použít {local_mta}",
|
||||
"receiverInfoSevenIO": "Pokud se přijímající číslo nenachází v Německu, musíte před číslo přidat kód země (např. pro kód země 420 z ČR použijte 420603603603 místo 603603603)"
|
||||
"receiverInfoSevenIO": "Pokud se přijímající číslo nenachází v Německu, musíte před číslo přidat kód země (např. pro kód země 420 z ČR použijte 420603603603 místo 603603603)",
|
||||
"Command": "Příkaz",
|
||||
"mongodbCommandDescription": "Spusťte příkaz MongoDB proti databázi. Informace o dostupných příkazech najdete v {dokumentaci}",
|
||||
"ignoreTLSErrorGeneral": "Ignorování chyby TLS/SSL u připojení",
|
||||
"smspartnerApiurl": "Svůj klíč API najdete v ovládacím panelu na adrese {0}",
|
||||
"smspartnerPhoneNumber": "Telefonní číslo (čísla)",
|
||||
"smspartnerPhoneNumberHelptext": "Číslo musí být v mezinárodním formátu {0}, {1}. Více čísel musí být odděleno znakem {2}",
|
||||
"smspartnerSenderName": "Název odesílatele SMS",
|
||||
"smspartnerSenderNameInfo": "Musí obsahovat 3..=11 běžných znaků",
|
||||
"Bitrix24 Webhook URL": "Adresa URL webhooku služby Bitrix24",
|
||||
"wayToGetBitrix24Webhook": "Webhook můžete vytvořit podle pokynů na adrese {0}",
|
||||
"bitrix24SupportUserID": "Zadejte své uživatelské ID v Bitrix24. ID zjistíte z odkazu, který najdete v profilu uživatele.",
|
||||
"Refresh Interval": "Interval obnovení",
|
||||
"Refresh Interval Description": "Stavová stránka provede úplnou obnovu webu každých {0} sekund",
|
||||
"Select message type": "Zvolte typ zprávy",
|
||||
"Send to channel": "Poslat na kanál",
|
||||
"Create new forum post": "Vytvořit nový příspěvek na fóru",
|
||||
"postToExistingThread": "Příspěvek do existujícího vlákna / příspěvek na fóru",
|
||||
"forumPostName": "Název příspěvku ve fóru",
|
||||
"threadForumPostID": "Vlákno / ID příspěvku ve fóru",
|
||||
"e.g. {discordThreadID}": "např. {discordThreadID}",
|
||||
"whatHappensAtForumPost": "Vytvořte nový příspěvek ve fóru. Tímto způsobem NENÍ možné odesílat zprávy v existujících příspěvcích. Pro vložení příspěvku do existujícího příspěvku použijte \"{option}\"",
|
||||
"wayToGetDiscordThreadId": "Získání ID vlákna / příspěvku ve fóru je podobné získání ID kanálu. Přečtěte si více o tom, jak získat id {0}",
|
||||
"Don't mention people": "Nezmiňujte se o osobách",
|
||||
"Mention group": "Zmínka o {skupině}",
|
||||
"Host URL": "URL hosta",
|
||||
"threemaRecipientType": "Typ příjemce",
|
||||
"threemaRecipient": "Příjemce",
|
||||
"threemaSenderIdentityFormat": "8 znaků, obvykle začíná na *",
|
||||
"threemaRecipientTypeEmail": "Emailová adresa",
|
||||
"threemaSenderIdentity": "ID brány",
|
||||
"threemaApiAuthenticationSecret": "Tajný klíč ID brány",
|
||||
"apiKeysDisabledMsg": "Klíče API jsou zakázány, protože je zakázáno ověřování.",
|
||||
"threemaBasicModeInfo": "Poznámka: Tato integrace využívá bránu Threema v základním režimu (šifrování pomocí serveru). Další podrobnosti naleznete {0}.",
|
||||
"threemaRecipientTypeIdentityFormat": "8 znaků",
|
||||
"threemaRecipientTypeIdentity": "Threema-ID",
|
||||
"threemaRecipientTypePhone": "Telefonní číslo",
|
||||
"wayToGetThreemaGateway": "Můžete se zaregistrovat do služby brány Threema {0}."
|
||||
}
|
||||
|
|
|
@ -812,7 +812,6 @@
|
|||
"Json Query": "Json-Abfrage",
|
||||
"filterActive": "Aktiv",
|
||||
"filterActivePaused": "Pausiert",
|
||||
"jsonQueryDescription": "Führe eine JSON-Abfrage gegen die Antwort durch und prüfe den erwarteten Wert (der Rückgabewert wird zum Vergleich in eine Zeichenkette umgewandelt). Auf {0} findest du die Dokumentation zur Abfragesprache. {1} kannst du Abfragen üben.",
|
||||
"Badge Duration (in hours)": "Abzeichen Dauer (in Stunden)",
|
||||
"Badge Preview": "Abzeichen Vorschau",
|
||||
"tailscalePingWarning": "Um den Tailscale Ping Monitor nutzen zu können, musst du Uptime Kuma ohne Docker installieren und den Tailscale Client auf dem Server installieren.",
|
||||
|
@ -970,5 +969,23 @@
|
|||
"ignoreTLSErrorGeneral": "TLS/SSL-Fehler für Verbindung ignorieren",
|
||||
"threadForumPostID": "Themen-/Forumbeitrags-ID",
|
||||
"e.g. {discordThreadID}": "z.B. {discordThreadID}",
|
||||
"wayToGetDiscordThreadId": "Das Abrufen einer Thread-/Forumspost-ID ist ähnlich wie das Abrufen einer Channel-ID. Lese mehr darüber, wie man IDs abruft {0}"
|
||||
"wayToGetDiscordThreadId": "Das Abrufen einer Thread-/Forumspost-ID ist ähnlich wie das Abrufen einer Channel-ID. Lese mehr darüber, wie man IDs abruft {0}",
|
||||
"smspartnerApiurl": "Den API-Schlüssel findest du im Dashboard unter {0}",
|
||||
"smspartnerPhoneNumber": "Telefonnummer(n)",
|
||||
"smspartnerSenderName": "SMS Absender Name",
|
||||
"smspartnerSenderNameInfo": "Muss zwischen 3..=11 regulären Zeichen sein",
|
||||
"smspartnerPhoneNumberHelptext": "Die Nummer muss das internationale Format {0}, {1} haben. Mehrere Nummern müssen durch {2} getrennt werden",
|
||||
"threemaRecipient": "Empfänger",
|
||||
"threemaRecipientType": "Empfänger Typ",
|
||||
"threemaRecipientTypeIdentity": "Threema-ID",
|
||||
"threemaRecipientTypePhone": "Telefonnummer",
|
||||
"threemaRecipientTypePhoneFormat": "E.164, ohne führendes +",
|
||||
"threemaRecipientTypeEmail": "E-Mail Adresse",
|
||||
"threemaSenderIdentity": "Gateway-ID",
|
||||
"threemaApiAuthenticationSecret": "Gateway-ID Schlüssel",
|
||||
"wayToGetThreemaGateway": "Du kannst dich für Threema Gateway {0} registrieren.",
|
||||
"threemaRecipientTypeIdentityFormat": "8 Zeichen",
|
||||
"threemaSenderIdentityFormat": "8 Zeichen, beginnt normalerweise mit *",
|
||||
"threemaBasicModeInfo": "Hinweis: Diese Integration verwendet Threema Gateway im Basismodus (serverbasierte Verschlüsselung). Weitere Details siehe {0}.",
|
||||
"apiKeysDisabledMsg": "API-Schlüssel sind deaktiviert, da die Authentifizierung deaktiviert ist."
|
||||
}
|
||||
|
|
|
@ -817,7 +817,6 @@
|
|||
"filterActivePaused": "Pausiert",
|
||||
"Expected Value": "Erwarteter Wert",
|
||||
"Json Query": "Json-Abfrage",
|
||||
"jsonQueryDescription": "Führe eine JSON-Abfrage gegen die Antwort durch und prüfe den erwarteten Wert (der Rückgabewert wird zum Vergleich in eine Zeichenkette umgewandelt). Auf {0} findest du die Dokumentation zur Abfragesprache. {1} kannst du Abfragen üben.",
|
||||
"tailscalePingWarning": "Um den Tailscale Ping Monitor nutzen zu können, musst du Uptime Kuma ohne Docker installieren und den Tailscale Client auf dem Server installieren.",
|
||||
"Server URL should not contain the nfty topic": "Die Server-URL sollte das nfty-Thema nicht enthalten",
|
||||
"pushDeerServerDescription": "Leer lassen um den offiziellen Server zu verwenden",
|
||||
|
@ -973,5 +972,23 @@
|
|||
"forumPostName": "Name des Forumsbeitrags",
|
||||
"threadForumPostID": "Themen-/Forumbeitrags-ID",
|
||||
"e.g. {discordThreadID}": "z.B. {discordThreadID}",
|
||||
"wayToGetDiscordThreadId": "Das Abrufen einer Thread-/Forumspost-ID ist ähnlich wie das Abrufen einer Channel-ID. Lese mehr darüber, wie man IDs abruft {0}"
|
||||
"wayToGetDiscordThreadId": "Das Abrufen einer Thread-/Forumspost-ID ist ähnlich wie das Abrufen einer Channel-ID. Lese mehr darüber, wie man IDs abruft {0}",
|
||||
"smspartnerPhoneNumber": "Telefonnummer(n)",
|
||||
"smspartnerSenderName": "SMS Absender Name",
|
||||
"smspartnerSenderNameInfo": "Muss zwischen 3..=11 regulären Zeichen sein",
|
||||
"smspartnerApiurl": "Den API-Schlüssel findest du im Dashboard unter {0}",
|
||||
"smspartnerPhoneNumberHelptext": "Die Nummer muss das internationale Format {0}, {1} haben. Mehrere Nummern müssen durch {2} getrennt werden",
|
||||
"threemaRecipient": "Empfänger",
|
||||
"threemaRecipientType": "Empfänger Typ",
|
||||
"threemaRecipientTypeIdentity": "Threema-ID",
|
||||
"threemaRecipientTypePhone": "Telefonnummer",
|
||||
"threemaRecipientTypePhoneFormat": "E.164, ohne führendes +",
|
||||
"threemaRecipientTypeEmail": "E-Mail Adresse",
|
||||
"threemaSenderIdentity": "Gateway-ID",
|
||||
"threemaApiAuthenticationSecret": "Gateway-ID Schlüssel",
|
||||
"wayToGetThreemaGateway": "Du kannst dich für Threema Gateway {0} registrieren.",
|
||||
"threemaRecipientTypeIdentityFormat": "8 Zeichen",
|
||||
"threemaSenderIdentityFormat": "8 Zeichen, beginnt normalerweise mit *",
|
||||
"threemaBasicModeInfo": "Hinweis: Diese Integration verwendet Threema Gateway im Basismodus (serverbasierte Verschlüsselung). Weitere Details siehe {0}.",
|
||||
"apiKeysDisabledMsg": "API-Schlüssel sind deaktiviert, da die Authentifizierung deaktiviert ist."
|
||||
}
|
||||
|
|
|
@ -31,13 +31,13 @@
|
|||
"confirmDisableTwoFAMsg": "Είστε βέβαιοι ότι θέλετε να απενεργοποιήσετε το 2FA;",
|
||||
"Settings": "Ρυθμίσεις",
|
||||
"Dashboard": "Πίνακας",
|
||||
"New Update": "Νέα αναβάθμιση",
|
||||
"New Update": "Νέα ενημέρωση",
|
||||
"Language": "Γλώσσα",
|
||||
"Appearance": "Εμφάνιση",
|
||||
"Theme": "Θέμα",
|
||||
"General": "Γενικά",
|
||||
"Primary Base URL": "Κύρια βασική διεύθυνση URL",
|
||||
"Version": "Εκδοχή",
|
||||
"Version": "Έκδοση",
|
||||
"Check Update On GitHub": "Ελέγξτε για Ενημέρωση στο GitHub",
|
||||
"List": "Λίστα",
|
||||
"Add": "Προσθήκη",
|
||||
|
@ -56,7 +56,7 @@
|
|||
"Resume": "Συνέχιση",
|
||||
"Edit": "Επεξεργασία",
|
||||
"Delete": "Διαγράφη",
|
||||
"Current": "Τρέχον",
|
||||
"Current": "Τωρινό",
|
||||
"Uptime": "Χρόνος λειτουργίας",
|
||||
"Cert Exp.": "Λήξη Πιστοπ.",
|
||||
"day": "ημέρα | ημέρες",
|
||||
|
@ -700,13 +700,13 @@
|
|||
"Add New Tag": "Πρόσθεσε νέα ετικέτα",
|
||||
"setupDatabaseMariaDB": "Συνδεθείτε με εξωτερική βάση δεδομένων MariaDB. Θα πρέπει να ορίσετε τα στοιχεία σύνδεσης της βάσης δεδομένων.",
|
||||
"setupDatabaseChooseDatabase": "Ποια βάση δεδομένων θέλετε να χρησιμοποιήσετε;",
|
||||
"setupDatabaseEmbeddedMariaDB": "Δεν χρειάζεται να ρυθμίσετε τίποτε. Η εικόνα Docker έχει ενσωματώσει και ρυθμίσει μια βάση δεδομένων MariaDB για εσάς αυτόματα. Το Uptime Kuma θα συνδεθεί με την βάση δεδομένων μέσω unix socket.",
|
||||
"setupDatabaseEmbeddedMariaDB": "Δεν χρειάζεται να ορίσετε τίποτε. Το Docker Image έχει ενθέσειι και διαμορφώσει μια βάση δεδομένων MariaDB για εσάς αυτόματα. Το Uptime Kuma θα συνδεθεί με αυτήν την βάση δεδομένων μέσω unix socket.",
|
||||
"setupDatabaseSQLite": "Ένα απλό αρχείο βάσης δεδομένων, προτεινόμενο για εγκαταστάσεις μικρής κλίμακας. Πρίν από την έκδοση 2.0.0, το Uptime Kuma χρησιμοποιούσε το SQLite ως την προεπιλεγμένη βάση δεδομένων.",
|
||||
"Cannot connect to the socket server": "Δεν είναι δυνατή η σύνδεση με τον διακομιστή socket",
|
||||
"Reconnecting...": "Επανασύνδεση",
|
||||
"Reconnecting...": "Επανασύνδεση...",
|
||||
"Home": "Αρχική",
|
||||
"settingUpDatabaseMSG": "Ρύθμιση της βάσης δεδομένων. Μπορεί να διαρκέσει λίγο, παρακαλώ να είστε υπομονετικοί.",
|
||||
"dbName": "Όνομα Βάσης Δεδομένων",
|
||||
"settingUpDatabaseMSG": "Ρύθμιση της βάσης δεδομένων. Μπορεί να διαρκέσει λίγη ώρα, παρακαλώ κάνετε υπομονετικοί.",
|
||||
"dbName": "Όνομα βάσης δεδομένων",
|
||||
"Invert Keyword": "Αντιστροφή Λέξης-Κλειδιού",
|
||||
"Expected Value": "Αναμενόμενη Τιμή",
|
||||
"Json Query": "Ερώτημα Json"
|
||||
|
|
|
@ -49,17 +49,20 @@
|
|||
"Uptime": "Uptime",
|
||||
"Cert Exp.": "Cert Exp.",
|
||||
"Monitor": "Monitor | Monitors",
|
||||
"now": "now",
|
||||
"time ago": "{0} ago",
|
||||
"day": "day | days",
|
||||
"-day": "-day",
|
||||
"hour": "hour",
|
||||
"-hour": "-hour",
|
||||
"-year": "-year",
|
||||
"Response": "Response",
|
||||
"Ping": "Ping",
|
||||
"Monitor Type": "Monitor Type",
|
||||
"Keyword": "Keyword",
|
||||
"Invert Keyword": "Invert Keyword",
|
||||
"Expected Value": "Expected Value",
|
||||
"Json Query": "Json Query",
|
||||
"Json Query Expression": "Json Query Expression",
|
||||
"Friendly Name": "Friendly Name",
|
||||
"URL": "URL",
|
||||
"Hostname": "Hostname",
|
||||
|
@ -80,7 +83,7 @@
|
|||
"resendDisabled": "Resend disabled",
|
||||
"retriesDescription": "Maximum retries before the service is marked as down and a notification is sent",
|
||||
"ignoreTLSError": "Ignore TLS/SSL errors for HTTPS websites",
|
||||
"ignoreTLSErrorGeneral": "Ignore TLS/SSL error for connection",
|
||||
"ignoreTLSErrorGeneral": "Ignore TLS/SSL error for connection",
|
||||
"upsideDownModeDescription": "Flip the status upside down. If the service is reachable, it is DOWN.",
|
||||
"maxRedirectDescription": "Maximum number of redirects to follow. Set to 0 to disable redirects.",
|
||||
"Upside Down Mode": "Upside Down Mode",
|
||||
|
@ -441,6 +444,7 @@
|
|||
"backupOutdatedWarning": "Deprecated: Since a lot of features were added and this backup feature is a bit unmaintained, it cannot generate or restore a complete backup.",
|
||||
"backupRecommend": "Please backup the volume or the data folder (./data/) directly instead.",
|
||||
"Optional": "Optional",
|
||||
"and": "and",
|
||||
"or": "or",
|
||||
"sameAsServerTimezone": "Same as Server Timezone",
|
||||
"startDateTime": "Start Date/Time",
|
||||
|
@ -588,7 +592,7 @@
|
|||
"notificationDescription": "Notifications must be assigned to a monitor to function.",
|
||||
"keywordDescription": "Search keyword in plain HTML or JSON response. The search is case-sensitive.",
|
||||
"invertKeywordDescription": "Look for the keyword to be absent rather than present.",
|
||||
"jsonQueryDescription": "Do a json Query against the response and check for expected value (Return value will get converted into string for comparison). Check out {0} for the documentation about the query language. A playground can be found {1}.",
|
||||
"jsonQueryDescription": "Parse and extract specific data from the server's JSON response using JSON query or use \"$\" for the raw response, if not expecting JSON. The result is then compared to the expected value, as strings. See {0} for documentation and use {1} to experiment with queries.",
|
||||
"backupDescription": "You can backup all monitors and notifications into a JSON file.",
|
||||
"backupDescription2": "Note: history and event data is not included.",
|
||||
"backupDescription3": "Sensitive data such as notification tokens are included in the export file; please store export securely.",
|
||||
|
@ -876,6 +880,8 @@
|
|||
"nostrRecipientsHelp": "npub format, one per line",
|
||||
"showCertificateExpiry": "Show Certificate Expiry",
|
||||
"noOrBadCertificate": "No/Bad Certificate",
|
||||
"cacheBusterParam": "Add the {0} parameter",
|
||||
"cacheBusterParamDescription": "Randomly generated parameter to skip caches.",
|
||||
"gamedigGuessPort": "Gamedig: Guess Port",
|
||||
"gamedigGuessPortDescription": "The port used by Valve Server Query Protocol may be different from the client port. Try this if the monitor cannot connect to your server.",
|
||||
"Bitrix24 Webhook URL": "Bitrix24 Webhook URL",
|
||||
|
@ -942,5 +948,71 @@
|
|||
"Allow Long SMS": "Allow Long SMS",
|
||||
"cellsyntSplitLongMessages": "Split long messages into up to 6 parts. 153 x 6 = 918 characters.",
|
||||
"max 15 digits": "max 15 digits",
|
||||
"max 11 alphanumeric characters": "max 11 alphanumeric characters"
|
||||
"max 11 alphanumeric characters": "max 11 alphanumeric characters",
|
||||
"Community String": "Community String",
|
||||
"snmpCommunityStringHelptext": "This string functions as a password to authenticate and control access to SNMP-enabled devices. Match it with your SNMP device's configuration.",
|
||||
"OID (Object Identifier)": "OID (Object Identifier)",
|
||||
"snmpOIDHelptext": "Enter the OID for the sensor or status you want to monitor. Use network management tools like MIB browsers or SNMP software if you're unsure about the OID.",
|
||||
"Condition": "Condition",
|
||||
"SNMP Version": "SNMP Version",
|
||||
"Please enter a valid OID.": "Please enter a valid OID.",
|
||||
"wayToGetThreemaGateway": "You can register for Threema Gateway {0}.",
|
||||
"threemaRecipient": "Recipient",
|
||||
"threemaRecipientType": "Recipient Type",
|
||||
"threemaRecipientTypeIdentity": "Threema-ID",
|
||||
"threemaRecipientTypeIdentityFormat": "8 characters",
|
||||
"threemaRecipientTypePhone": "Phone Number",
|
||||
"threemaRecipientTypePhoneFormat": "E.164, without leading +",
|
||||
"threemaRecipientTypeEmail": "Email Address",
|
||||
"threemaSenderIdentity": "Gateway-ID",
|
||||
"threemaSenderIdentityFormat": "8 characters, usually starts with *",
|
||||
"threemaApiAuthenticationSecret": "Gateway-ID Secret",
|
||||
"threemaBasicModeInfo": "Note: This integration uses Threema Gateway in basic mode (server-based encryption). Further details can be found {0}.",
|
||||
"apiKeysDisabledMsg": "API keys are disabled because authentication is disabled.",
|
||||
"Host Onesender": "Host Onesender",
|
||||
"Token Onesender": "Token Onesender",
|
||||
"Recipient Type": "Recipient Type",
|
||||
"Private Number": "Private Number",
|
||||
"privateOnesenderDesc": "Make sure the number phone is valid. To send message into private number phone, ex: 628123456789",
|
||||
"groupOnesenderDesc": "Make sure the GroupID is valid. To send message into Group, ex: 628123456789-342345",
|
||||
"Group ID": "Group ID",
|
||||
"wayToGetOnesenderUrlandToken":"You can get the URL and Token by going to the Onesender website. More info {0}",
|
||||
"Add Remote Browser": "Add Remote Browser",
|
||||
"New Group": "New Group",
|
||||
"Group Name": "Group Name",
|
||||
"OAuth2: Client Credentials": "OAuth2: Client Credentials",
|
||||
"Authentication Method": "Authentication Method",
|
||||
"Authorization Header": "Authorization Header",
|
||||
"Form Data Body": "Form Data Body",
|
||||
"OAuth Token URL": "OAuth Token URL",
|
||||
"Client ID": "Client ID",
|
||||
"Client Secret": "Client Secret",
|
||||
"OAuth Scope": "OAuth Scope",
|
||||
"Optional: Space separated list of scopes": "Optional: Space separated list of scopes",
|
||||
"Go back to home page.": "Go back to home page.",
|
||||
"No tags found.": "No tags found.",
|
||||
"Lost connection to the socket server.": "Lost connection to the socket server.",
|
||||
"Cannot connect to the socket server.": "Cannot connect to the socket server.",
|
||||
"SIGNL4": "SIGNL4",
|
||||
"SIGNL4 Webhook URL": "SIGNL4 Webhook URL",
|
||||
"signl4Docs": "You can find more information about how to configure SIGNL4 and how to obtain the SIGNL4 webhook URL in the {0}.",
|
||||
"Conditions": "Conditions",
|
||||
"conditionAdd": "Add Condition",
|
||||
"conditionDelete": "Delete Condition",
|
||||
"conditionAddGroup": "Add Group",
|
||||
"conditionDeleteGroup": "Delete Group",
|
||||
"conditionValuePlaceholder": "Value",
|
||||
"equals": "equals",
|
||||
"not equals": "not equals",
|
||||
"contains": "contains",
|
||||
"not contains": "not contains",
|
||||
"starts with": "starts with",
|
||||
"not starts with": "not starts with",
|
||||
"ends with": "ends with",
|
||||
"not ends with": "not ends with",
|
||||
"less than": "less than",
|
||||
"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"
|
||||
}
|
||||
|
|
|
@ -558,7 +558,7 @@
|
|||
"affectedMonitorsDescription": "Selecciona los monitores que se ven afectados por el mantenimiento actual",
|
||||
"affectedStatusPages": "Muestra este mensaje de mantenimiento en las páginas de estado seleccionadas",
|
||||
"atLeastOneMonitor": "Selecciona al menos un monitor afectado",
|
||||
"endpoint": "endpoint",
|
||||
"endpoint": "punto final",
|
||||
"promosmsPassword": "Contraseña API",
|
||||
"pushoversounds pushover": "Pushover (predeterminado)",
|
||||
"pushoversounds bike": "Bicicleta",
|
||||
|
@ -655,7 +655,7 @@
|
|||
"gorush": "Gorush",
|
||||
"squadcast": "Squadcast",
|
||||
"Maintenance Time Window of a Day": "Ventana de tiempo de mantenimiento de un día",
|
||||
"Effective Date Range": "Rango de fecha efectivo (opcional)",
|
||||
"Effective Date Range": "Rango de Fechas Efectivas (Opcional)",
|
||||
"Free Mobile User Identifier": "Identificador de Usuario de Free Mobile",
|
||||
"Gateway Type": "Tipo de puerta de enlace",
|
||||
"SMSManager": "SMSManager",
|
||||
|
@ -771,7 +771,6 @@
|
|||
"Json Query": "Consulta Json",
|
||||
"invertKeywordDescription": "Comprobar si la palabra clave está ausente en vez de presente.",
|
||||
"enableNSCD": "Habilitar NSCD (Demonio de Caché de Servicio de Nombres) para almacenar en caché todas las solicitudes DNS",
|
||||
"jsonQueryDescription": "Realiza una consulta JSON contra la respuesta y verifica el valor esperado (el valor de retorno se convertirá a una cadena para la comparación). Consulta {0} para obtener documentación sobre el lenguaje de consulta. Puede encontrar un espacio de prueba {1}.",
|
||||
"Request Timeout": "Tiempo de espera máximo de petición",
|
||||
"timeoutAfter": "Expirar después de {0} segundos",
|
||||
"chromeExecutableDescription": "Para usuarios de Docker, si Chromium no está instalado, puede que tarde unos minutos en ser instalado y mostrar el resultado de la prueba. Usa 1GB de espacio.",
|
||||
|
@ -831,7 +830,7 @@
|
|||
"Badge value (For Testing only.)": "Valor de la insignia (Solo para pruebas.)",
|
||||
"Enable Kafka Producer Auto Topic Creation": "Habilitar la Creación Automática de Temas del Productor de Kafka",
|
||||
"noGroupMonitorMsg": "No disponible. Cree primero un monitor de grupo.",
|
||||
"wayToGetFlashDutyKey": "Puede ir a Canal -> (Seleccionar un canal) -> Integraciones -> Agregar una nueva página de integración, agregar un 'Evento personalizado' para obtener una dirección push, copiar la clave de integración en la dirección. Para mayor información por favor visite",
|
||||
"wayToGetFlashDutyKey": "Puedes ir a Canal -> (Seleccionar un Canal) -> Integraciones -> Agregar una nueva integración, agregar un 'Uptime Kuma' para obtener una dirección de envío, copiar la Clave de Integración en la dirección. Para más información, por favor visita",
|
||||
"gamedigGuessPort": "Gamedig: Adivinar el puerto",
|
||||
"gamedigGuessPortDescription": "El puerto utilizado por Valve Server Query Protocol puede ser diferente del puerto del cliente. Pruebe esto si el monitor no puede conectarse a su servidor.",
|
||||
"twilioApiKey": "Clave de la API (opcional)",
|
||||
|
@ -839,7 +838,7 @@
|
|||
"styleElapsedTimeShowNoLine": "Mostrar (sin línea)",
|
||||
"styleElapsedTimeShowWithLine": "Mostrar (Con línea)",
|
||||
"webhookCustomBodyDesc": "Define un cuerpo HTTP personalizado para la petición. Las variables que puedes usar como plantillas son {msg}, {heartbeat}, y {monitor}.",
|
||||
"webhookBodyPresetOption": "Preajuste- {0}",
|
||||
"webhookBodyPresetOption": "Preajuste - {0}",
|
||||
"tailscalePingWarning": "Para utilizar el monitor Tailscale Ping, debe instalar Uptime Kuma sin Docker y también instalar el cliente Tailscale en su servidor.",
|
||||
"Bark API Version": "Versión de la API Bark",
|
||||
"monitorToastMessagesDescription": "Las notificaciones Toast para monitores desaparecen después de un tiempo dado en segundos. Establecer a -1 desactiva el tiempo de espera. Si se establece en 0, se desactivan las notificaciones.",
|
||||
|
@ -847,7 +846,7 @@
|
|||
"monitorToastMessagesLabel": "Monitorizar las notificaciones Toast",
|
||||
"toastSuccessTimeout": "Tiempo de espera para notificaciones de éxito",
|
||||
"toastErrorTimeout": "Tiempo de espera para notificaciones de error",
|
||||
"setupDatabaseChooseDatabase": "¿Qué base de datos desea utilizar?",
|
||||
"setupDatabaseChooseDatabase": "¿Qué base de datos te gustaría usar?",
|
||||
"setupDatabaseEmbeddedMariaDB": "No necesitas configurar nada. Esta imagen de Docker tiene incorporado y configurado MariaDB para ti automáticamente. Uptime Kuma se conectará a esta base de datos a través de un socket Unix.",
|
||||
"setupDatabaseMariaDB": "Conectarse a una base de datos MariaDB externa. Debe configurar la información de conexión a la base de datos.",
|
||||
"setupDatabaseSQLite": "Un archivo de base de datos simple, recomendado para despliegues a pequeña escala. Antes de la versión 2.0.0, Uptime Kuma utilizaba SQLite como base de datos predeterminada.",
|
||||
|
@ -857,20 +856,20 @@
|
|||
"2faEnabled": "2FA habilitado.",
|
||||
"2faDisabled": "2FA deshabilitado.",
|
||||
"liquidIntroduction": "La plantilla se logra a través del lenguaje de plantillas Liquid. Consulte {0} para obtener instrucciones de uso. Estas son las variables disponibles:",
|
||||
"templateLimitedToUpDownCertNotifications": "sólo disponible para las notificaciones de LEVANTADO/CAÍDO/Caducidad de certificado",
|
||||
"templateLimitedToUpDownCertNotifications": "disponible solo para notificaciones de arriba/abajo/caducidad del certificado",
|
||||
"emailTemplateMsg": "mensaje de la notificación",
|
||||
"emailTemplateLimitedToUpDownNotification": "sólo disponible para pulsos LEVANTADO/CAÍDO, en caso contrario null",
|
||||
"emailTemplateLimitedToUpDownNotification": "disponible solo para latidos de arriba/abajo, de lo contrario, nulo",
|
||||
"setup a new monitor group": "configurar un nuevo grupo de monitores",
|
||||
"authUserInactiveOrDeleted": "El usuario está inactivo o eliminado.",
|
||||
"2faAlreadyEnabled": "2FA ya está activado.",
|
||||
"remoteBrowsersDescription": "Los navegadores remotos son una alternativa a la ejecución local de Chromium. Configúralos con un servicio como browserless.io o conéctate a uno propio",
|
||||
"remoteBrowsersDescription": "Los navegadores remotos son una alternativa a ejecutar Chromium localmente. Configúralos con un servicio como browserless.io o conéctalos a tu propio servidor",
|
||||
"successKeyword": "Palabra clave de éxito",
|
||||
"successKeywordExplanation": "MQTT Palabra clave que se considerará como éxito",
|
||||
"Remove the expiry notification": "Eliminar la notificación de vencimiento",
|
||||
"Browser Screenshot": "Captura de pantalla del navegador",
|
||||
"emailCustomisableContent": "Contenidos personalizables",
|
||||
"smtpLiquidIntroduction": "Los dos campos siguientes son planificables mediante el lenguaje de plantillas Liquid. Consulte las instrucciones de uso en {0}. Estas son las variables disponibles:",
|
||||
"leave blank for default subject": "dejar en blanco para el asunto por defecto",
|
||||
"emailCustomisableContent": "Contenido personalizable",
|
||||
"smtpLiquidIntroduction": "Los siguientes dos campos son personalizables a través del lenguaje de plantillas Liquid. Por favor, consulta {0} para las instrucciones de uso. Estas son las variables disponibles:",
|
||||
"leave blank for default subject": "dejar en blanco para asunto predeterminado",
|
||||
"emailCustomBody": "Cuerpo personalizado",
|
||||
"successAuthChangePassword": "La contraseña se ha actualizado correctamente.",
|
||||
"successDeleted": "Eliminado con éxito.",
|
||||
|
@ -895,11 +894,11 @@
|
|||
"programmingLanguages": "Lenguajes de Programación",
|
||||
"templateMsg": "mensaje de la notificación",
|
||||
"templateMonitorJSON": "objeto que describe el monitor",
|
||||
"templateLimitedToUpDownNotifications": "sólo disponible para notificaciones LEVANTADO/CAÍDO",
|
||||
"templateLimitedToUpDownNotifications": "disponible solo para notificaciones de arriba/abajo",
|
||||
"Add a new expiry notification day": "Añadir una nueva notificación de vencimiento",
|
||||
"leave blank for default body": "dejar en blanco para el cuerpo por defecto",
|
||||
"emailTemplateServiceName": "Nombre del servicio",
|
||||
"emailTemplateHostnameOrURL": "Nombre del host o URL",
|
||||
"emailTemplateServiceName": "Nombre del Servicio",
|
||||
"emailTemplateHostnameOrURL": "Nombre del Host o URL",
|
||||
"emailTemplateStatus": "Estado",
|
||||
"emailTemplateMonitorJSON": "objeto que describe el monitor",
|
||||
"openModalTo": "abrir modal a {0}",
|
||||
|
@ -913,10 +912,10 @@
|
|||
"settingUpDatabaseMSG": "Configurando la base de datos. Puede tomar un tiempo, sea paciente, por favor.",
|
||||
"Search monitored sites": "Buscar sitios monitoreados",
|
||||
"statusPageSpecialSlugDesc": "Identificador único especial {0}: esta página será mostrada cuando no se proporcione ningún identificador único",
|
||||
"emailTemplateHeartbeatJSON": "Objeto que describe la señal de vida",
|
||||
"ntfyPriorityHelptextAllEvents": "Todos los eventos son enviados con la máxima prioridad",
|
||||
"emailTemplateHeartbeatJSON": "objeto que describe el latido",
|
||||
"ntfyPriorityHelptextAllEvents": "Todos los eventos se envían con la máxima prioridad",
|
||||
"ntfyPriorityHelptextAllExceptDown": "Todos los eventos son enviados con esta prioridad, excepto los eventos {0}, que tienen una prioridad de {1}",
|
||||
"templateHeartbeatJSON": "Objeto que describe el latido",
|
||||
"templateHeartbeatJSON": "objeto que describe el latido",
|
||||
"What is a Remote Browser?": "¿Qué es un Navegador Remoto?",
|
||||
"Your User ID": "Tu ID de usuario",
|
||||
"Alphanumeric (recommended)": "Alfanumérico (recomendado)",
|
||||
|
@ -924,5 +923,71 @@
|
|||
"wayToGetWhapiUrlAndToken": "Puedes obtener la URL de la API y el token accediendo al canal que desee desde {0}",
|
||||
"API URL": "API URL",
|
||||
"Allow Long SMS": "Permitir SMS largo",
|
||||
"cellsyntDestination": "Número de teléfono del destinatario en formato internacional precedido de 00, seguido del código de país, ej. 00447920110000 para el número del Reino Unido 07920 110 000 (máximo 17 dígitos en total). Máximo 25.000 destinatarios, separados por comas, por solicitud HTTP."
|
||||
"cellsyntDestination": "Número de teléfono del destinatario en formato internacional precedido de 00, seguido del código de país, ej. 00447920110000 para el número del Reino Unido 07920 110 000 (máximo 17 dígitos en total). Máximo 25.000 destinatarios, separados por comas, por solicitud HTTP.",
|
||||
"Refresh Interval Description": "La página de estado se refrescará cada {0} segundos",
|
||||
"Refresh Interval": "Intervalo de refresco",
|
||||
"ignoreTLSErrorGeneral": "Ignorar errores SSL/TLS durante la conexión",
|
||||
"documentationOf": "Documentación de {0}",
|
||||
"wayToGetHeiiOnCallDetails": "Cómo obtener el ID del Disparador y las Claves API se explica en la {documentación}",
|
||||
"Command": "Comando",
|
||||
"wayToGetThreemaGateway": "Puedes registrarte para Threema Gateway {0}.",
|
||||
"threemaRecipient": "Destinatario",
|
||||
"threemaRecipientType": "Tipo de Destinatario",
|
||||
"threemaRecipientTypeIdentity": "ID de Threema",
|
||||
"threemaRecipientTypeIdentityFormat": "8 caracteres",
|
||||
"threemaRecipientTypePhone": "Número de Teléfono",
|
||||
"threemaRecipientTypeEmail": "Dirección de Correo Electrónico",
|
||||
"threemaSenderIdentity": "ID de Gateway",
|
||||
"threemaSenderIdentityFormat": "8 caracteres, generalmente comienza con *",
|
||||
"Host URL": "URL del anfitrión",
|
||||
"Either enter the hostname of the server you want to connect to or localhost if you intend to use a locally configured mail transfer agent": "Ingresa el nombre del host del servidor al que deseas conectarte o {localhost} si deseas usar un {local_mta}",
|
||||
"smspartnerPhoneNumberHelptext": "El número debe estar en el formato internacional {0}, {1}. Múltiples números deben estar separados por {2}",
|
||||
"smspartnerSenderName": "Nombre del remitente de SMS",
|
||||
"smspartnerApiurl": "Puedes encontrar tu clave API en tu panel de control en {0}",
|
||||
"smspartnerPhoneNumber": "Número(s) de teléfono",
|
||||
"max 11 alphanumeric characters": "máximo 11 caracteres alfanuméricos",
|
||||
"gtxMessagingFromHint": "En teléfonos móviles, tus destinatarios ven el TPOA mostrado como el remitente del mensaje. Se permiten hasta 11 caracteres alfanuméricos, un código corto, el código largo local o números internacionales ({e164}, {e212} o {e214})",
|
||||
"cellsyntOriginatortypeAlphanumeric": "Cadena alfanumérica (máximo 11 caracteres alfanuméricos). Los destinatarios no pueden responder al mensaje.",
|
||||
"cellsyntOriginatortypeNumeric": "Valor numérico (máximo 15 dígitos) con el número de teléfono en formato internacional sin el prefijo 00 (por ejemplo, el número del Reino Unido 07920 110 000 debe establecerse como 447920110000). Los destinatarios pueden responder al mensaje.",
|
||||
"Originator type": "Tipo de originador",
|
||||
"Telephone number": "Número de teléfono",
|
||||
"Mentioning": "Mencionando",
|
||||
"Don't mention people": "No mencionar a las personas",
|
||||
"Mention group": "Mencionar a {group}",
|
||||
"Bitrix24 Webhook URL": "URL del Webhook de Bitrix24",
|
||||
"wayToGetBitrix24Webhook": "Puedes crear un webhook siguiendo los pasos en {0}",
|
||||
"gtxMessagingApiKeyHint": "Puedes encontrar tu clave API en: My Routing Accounts > Show Account Information > API Credentials > REST API (v2.x)",
|
||||
"Originator": "Originador",
|
||||
"bitrix24SupportUserID": "Ingresa tu ID de usuario en Bitrix24. Puedes encontrar el ID en el enlace al ir al perfil del usuario.",
|
||||
"wayToWriteWhapiRecipient": "El número de teléfono con el prefijo internacional, pero sin el signo más al inicio ({0}), el ID de Contacto ({1}) o el ID de Grupo ({2}).",
|
||||
"From Phone Number / Transmission Path Originating Address (TPOA)": "Número de Teléfono del Remitente / Dirección de Origen de la Ruta de Transmisión (TPOA)",
|
||||
"To Phone Number": "Al Número de Teléfono",
|
||||
"Select message type": "Seleccionar tipo de mensaje",
|
||||
"Send to channel": "Enviar al canal",
|
||||
"max 15 digits": "máximo 15 dígitos",
|
||||
"mongodbCommandDescription": "Ejecuta un comando de MongoDB contra la base de datos. Para obtener información sobre los comandos disponibles, consulta la {documentación}",
|
||||
"whapiRecipient": "Número de Teléfono / ID de Contacto / ID de Grupo",
|
||||
"cellsyntSplitLongMessages": "Divide mensajes largos en hasta 6 partes. 153 x 6 = 918 caracteres.",
|
||||
"receiverSevenIO": "Número receptor",
|
||||
"apiKeySevenIO": "Clave API de SevenIO",
|
||||
"wayToGetSevenIOApiKey": "Visita el panel de control en app.seven.io > developer > api key > el botón verde de agregar",
|
||||
"senderSevenIO": "Número o nombre del remitente",
|
||||
"gtxMessagingToHint": "Formato internacional, con el signo \"+\" al inicio ({e164}, {e212} o {e214})",
|
||||
"locally configured mail transfer agent": "agente de transferencia de correo configurado localmente",
|
||||
"wayToGetDiscordThreadId": "Obtener un ID de hilo / publicación en el foro es similar a obtener un ID de canal. Lee más sobre cómo obtener IDs {0}",
|
||||
"smspartnerSenderNameInfo": "Debe tener entre 3..=11 caracteres normales",
|
||||
"receiverInfoSevenIO": "Si el número receptor no está ubicado en Alemania, debes agregar el código de país delante del número (por ejemplo, para el código de país 1 de EE. UU. usa 117612121212 en lugar de 017612121212)",
|
||||
"callMeBotGet": "Aquí puedes generar un endpoint para {0}, {1} y {2}. Ten en cuenta que podrías recibir limitaciones de tasa. Las limitaciones de tasa parecen ser: {3}",
|
||||
"cellsyntOriginator": "Visible en el teléfono móvil del destinatario como originador del mensaje. Los valores permitidos y la función dependen del parámetro originatortype.",
|
||||
"threemaRecipientTypePhoneFormat": "E.164, sin el signo + al inicio",
|
||||
"threemaApiAuthenticationSecret": "Clave Secreta del Gateway-ID",
|
||||
"threemaBasicModeInfo": "Nota: Esta integración utiliza Threema Gateway en modo básico (encriptación basada en servidor). Puedes encontrar más detalles en {0}.",
|
||||
"apiKeysDisabledMsg": "Las claves API están desactivadas porque la autenticación está desactivada.",
|
||||
"Channel access token (Long-lived)": "Token de acceso al canal (de larga duración)",
|
||||
"Create new forum post": "Crear nueva publicación en el foro",
|
||||
"postToExistingThread": "Publicar en hilo / publicación existente",
|
||||
"forumPostName": "Nombre de la publicación en el foro",
|
||||
"threadForumPostID": "ID del hilo / publicación en el foro",
|
||||
"e.g. {discordThreadID}": "por ejemplo, {discordThreadID}",
|
||||
"whatHappensAtForumPost": "Crear una nueva publicación en el foro. Esto NO publica mensajes en una publicación existente. Para publicar en una publicación existente usa \"{option}\""
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@
|
|||
"Accepted Status Codes": "وضعیتهای (Status Code) های قابل قبول",
|
||||
"Save": "ذخیره",
|
||||
"Notifications": "اطلاعرسانیها",
|
||||
"Not available, please setup.": "هیچ موردی موجود نیست، اولین مورد را راه اندازی کنید.",
|
||||
"Not available, please setup.": "هیچ موردی موجود نیست، اولین مورد را راهاندازی کنید.",
|
||||
"Setup Notification": "راه اندازی اطلاعرسانی",
|
||||
"Light": "روشن",
|
||||
"Dark": "تاریک",
|
||||
|
@ -386,7 +386,7 @@
|
|||
"Switch to Dark Theme": "تغییر به حالت تیره",
|
||||
"Show Tags": "نمایش تگ ها",
|
||||
"Hide Tags": "مخفی سازی تگ ها",
|
||||
"Description": "توضحیات",
|
||||
"Description": "توضیحات",
|
||||
"Custom CSS": "CSS اختصاصی",
|
||||
"deleteStatusPageMsg": "آیا بابت حذف این استاتوس پیچ مطمئن هستید؟",
|
||||
"Proxies": "پروکسی ها",
|
||||
|
@ -759,11 +759,10 @@
|
|||
"filterActive": "فعال",
|
||||
"webhookCustomBodyDesc": "یک بدنه HTTP سفارشی برای ریکوئست تعریف کنید. متغیر های قابل استفاده: {msg}, {heartbeat}, {monitor}.",
|
||||
"tailscalePingWarning": "برای استفاده از Tailscale Ping monitor، شما باید آپتایم کوما را بدون استفاده از داکر و همچنین Tailscale client را نیز بر روی سرور خود نصب داشته باشید.",
|
||||
"jsonQueryDescription": "یک کوئری json در برابر پاسخ انجام دهید و مقدار مورد انتظار را (مقدار برگشتی برای مقایسه به رشته تبدیل می شود). برای مستندات درباره زبان کوئری، {0} مشاهده کنید. همچنین محیط تست را میتوانید در {1} پیدا کنید.",
|
||||
"Enter the list of brokers": "لیست بروکر هارا وارد کنید",
|
||||
"Enable Kafka Producer Auto Topic Creation": "فعال سازی ایجاپ موضوع اتوماتیک تهیه کننده",
|
||||
"Secret AccessKey": "کلید محرمانه AccessKey",
|
||||
"wayToGetFlashDutyKey": "می توانید به کانال -> (انتخاب یک کانال) -> یکپارچه سازی -> صفحه یکپارچه سازی جدید بروید، یک \"رویداد سفارشی\" را برای دریافت یک آدرس فشار اضافه کنید، کلید یکپارچه سازی را در آدرس کپی کنید. برای اطلاعات بیشتر لطفا مراجعه کنید به",
|
||||
"wayToGetFlashDutyKey": "می توانید به کانال -> (انتخاب یک کانال) -> یکپارچه سازی -> یکپارچه سازی جدید بروید، یک \"Uptime Kuma\" را برای دریافت یک آدرس پوش اضافه کنید، کلید یکپارچه سازی را در آدرس کپی کنید. برای اطلاعات بیشتر لطفا مراجعه کنید به",
|
||||
"showCertificateExpiry": "نمایش زمان به پایان رسیدن اعتبار سرتیفیکیت",
|
||||
"gamedigGuessPortDescription": "پورت مورد استفاده توسط پروتکل Query Valve Server ممکن است با پورت مشتری متفاوت باشد. اگر مانیتور نمی تواند به سرور شما متصل شود، این را امتحان کنید.",
|
||||
"invertKeywordDescription": "دنبال کلمات کلیدی ناموجود باشید تا آنهایی که موجود است.",
|
||||
|
@ -812,8 +811,8 @@
|
|||
"Expected Value": "مقدار مورد انتظار",
|
||||
"Json Query": "کوئری جیسون",
|
||||
"Saved.": "ذخیره شده.",
|
||||
"setupDatabaseChooseDatabase": "از چه دیتابیسی میخواهید استفاده کنید؟",
|
||||
"setupDatabaseEmbeddedMariaDB": "شما نیازی نیست چیزی را تنظیم کنید . این Image داکر MariaDB را به طور خودکار برای شما جاسازی و پیکربندی کرده است. آپتایم کوما از طریق سوکت یونیکس به این دیتابیس متصل می شود.",
|
||||
"setupDatabaseChooseDatabase": "از چه دیتابیسی میخواهید استفاده کنید؟",
|
||||
"setupDatabaseEmbeddedMariaDB": "شما نیازی نیست چیزی را تنظیم کنید . این Image داکر، MariaDB را به طور خودکار برای شما جاسازی و پیکربندی کرده است. آپتایم کوما از طریق سوکت یونیکس به این دیتابیس متصل می شود.",
|
||||
"setupDatabaseSQLite": "یک فایل دیتابیس ساده که برای استقرار در مقیاس کوچک توصیه می شود. قبل از نسخه 2.0.0، Uptime Kuma از SQLite به عنوان دیتابیس پیش فرض استفاده می کرد.",
|
||||
"enableNSCD": "فعال سازی NSCD (Name Service Cache Daemon) برای کش کردن تمام ریکوئست های DNS",
|
||||
"setupDatabaseMariaDB": "به یک دیتابیس خارجی MariaDB متصل شوید. شما باید اطلاعات اتصال دیتابیس را تنظیم کنید.",
|
||||
|
@ -892,5 +891,74 @@
|
|||
"documentationOf": "کلید مطلب ها",
|
||||
"wayToGetHeiiOnCallDetails": "روش دریافت ایدی کلید و کلید ای پی آی درمطلب توضیح داده شده است",
|
||||
"Channel access token (Long-lived)": "توکن دسترسی به کانال",
|
||||
"Your User ID": "آیدی حساب کاربری شما"
|
||||
"Your User ID": "آیدی حساب کاربری شما",
|
||||
"ignoreTLSErrorGeneral": "نادیده گرفتن ارور TLS/SSL برای برقراری ارتباط",
|
||||
"Originator": "ایجاد کننده",
|
||||
"cellsyntOriginatortypeNumeric": "مقدار عددی (حداکثر 15 رقم) با شماره تلفن در قالب بین المللی بدون 00 اول (به عنوان مثال شماره 07920 110 000 بریتانیا باید به عنوان 447920110000 تنظیم شود). گیرندگان می توانند به پیام پاسخ دهند.",
|
||||
"threemaRecipient": "گیرنده",
|
||||
"threemaRecipientType": "نوع گیرنده",
|
||||
"threemaRecipientTypeIdentity": "شناسه Threema",
|
||||
"threemaRecipientTypeIdentityFormat": "8 کاراکتر",
|
||||
"threemaRecipientTypePhone": "شماره موبایل",
|
||||
"threemaRecipientTypePhoneFormat": "E.164، بدون پیشوند +",
|
||||
"threemaRecipientTypeEmail": "آدرس ایمیل",
|
||||
"threemaSenderIdentity": "شناسه ورودی",
|
||||
"threemaApiAuthenticationSecret": "کلید شناسه ورودی",
|
||||
"threemaBasicModeInfo": "توجه: این ادغام از ورودی Threema در حالت اولیه (رمزگذاری مبتنی بر سرور) استفاده می کند. جزئیات بیشتر را می توان در {0} یافت.",
|
||||
"smspartnerApiurl": "شما میتوانید کلید API را در داشبورد خود در {0} پیدا کنید",
|
||||
"smspartnerPhoneNumber": "شماره موبایل(ها)",
|
||||
"Host URL": "آدرس هاست",
|
||||
"gtxMessagingApiKeyHint": "شما میتوانید کلید API خود را در این آدرس پیدا کنید: My Routing Accounts > Show Account Information > API Credentials > REST API (v2.x)",
|
||||
"Bitrix24 Webhook URL": "آدرس وب هوک Bitrix24",
|
||||
"wayToGetBitrix24Webhook": "شما می توانید با دنبال کردن مراحل در {0} یک وب هوک ایجاد کنید",
|
||||
"whapiRecipient": "شماره تلفن / شناسه ارتباط / شناسه گروه",
|
||||
"gtxMessagingToHint": "قالب بینالمللی، با پیشوند \"+\" ({e164}، {e212} یا {e214})",
|
||||
"Mentioning": "منشن کردن",
|
||||
"Send to channel": "ارسال به کانال",
|
||||
"forumPostName": "نام پست انجمن",
|
||||
"threadForumPostID": "شناسه پست رشته / انجمن",
|
||||
"e.g. {discordThreadID}": "به عنوان مثال: {discordThreadID}",
|
||||
"wayToGetDiscordThreadId": "دریافت شناسه پست / انجمن شبیه به دریافت شناسه کانال است. درباره نحوه دریافت شناسه بیشتر بخوانید {0}",
|
||||
"senderSevenIO": "شماره یا نام ارسال کننده",
|
||||
"receiverSevenIO": "شماره گیرنده",
|
||||
"Don't mention people": "این آدم هارا منشن نکن",
|
||||
"Mention group": "منشن {group}",
|
||||
"smspartnerSenderName": "نام ارسال کننده اس ام اس",
|
||||
"apiKeySevenIO": "کلید SevenIO API",
|
||||
"API URL": "آدرس API",
|
||||
"callMeBotGet": "اینجا شما می توانید یک نقطه پایانی برای {0}، {1} و {2} ایجاد کنید. به خاطر داشته باشید که ممکن است ریت لیمیت (Rate Limit) دریافت کنید. به نظر می رسد ریت لیمیت ها عبارتند از: {3}",
|
||||
"To Phone Number": "به شماره موبایل",
|
||||
"Originator type": "نوع Originator",
|
||||
"Destination": "مقصد",
|
||||
"Allow Long SMS": "اجازه ارسال پیام بلند",
|
||||
"max 15 digits": "حداکثر 15 عدد",
|
||||
"max 11 alphanumeric characters": "حداکثر 11 کاراکتر الفبا و اعداد",
|
||||
"locally configured mail transfer agent": "نماینده انتقال نامه به صورت لوکال",
|
||||
"Either enter the hostname of the server you want to connect to or localhost if you intend to use a locally configured mail transfer agent": "یا آدرس هاست سروری که میخواهید به آن متصل شوید را وارد کنید یا {localhost} اگر میخواهید از یک {local_mta} استفاده کنید",
|
||||
"Refresh Interval": "وقفه بین بارگزاری مجدد",
|
||||
"Refresh Interval Description": "صفحه وضعیت هر {0} ثانیه یک بار سایت را بهروزرسانی میکند",
|
||||
"Select message type": "انتخاب نوع پیام",
|
||||
"Create new forum post": "ایحاد یک پست جدید در انجمن",
|
||||
"postToExistingThread": "پست در یک رشته / انجمن موجود",
|
||||
"whatHappensAtForumPost": "ایحاد یک پست جدید در انجمن. این گزینه در یک پست موجود پیامی را ارسال نمی کند. برای پست در یک پیام موجود از گزینه\"{option}\" استفاده کنید",
|
||||
"smspartnerPhoneNumberHelptext": "شماره باید در قالب بین المللی {0}، {1} باشد. شماره ها باید با {2} از هم جدا شوند",
|
||||
"smspartnerSenderNameInfo": "باید بین 3..=11 کاراکتر معمولی باشد",
|
||||
"bitrix24SupportUserID": "لطفا شناسه کاربری خود را در Bitrix24 وارد کنید. با رفتن به پروفایل کاربر می توانید شناسه را از لینک دریافت کنید.",
|
||||
"Command": "دستور",
|
||||
"mongodbCommandDescription": "یک دستور MongoDB را در پایگاه داده اجرا کنید. برای اطلاعات در مورد دستورات موجود، {documentation} را بررسی کنید",
|
||||
"wayToGetSevenIOApiKey": "دز داشبورد به آدرس app.seven.io > developer > api key بر روی دکمه سبز اضافه کلیک کنید",
|
||||
"receiverInfoSevenIO": "اگر شماره دریافت کننده در آلمان نیست، شما باید کد کشور را در مقابل شماره اضافه کنید (به عنوان مثال برای کد کشور 1 از ایالات متحده به جای 017612121212 از 117612121212 استفاده کنید)",
|
||||
"wayToWriteWhapiRecipient": "شماره تلفن با پیشوند بینالمللی، اما بدون علامت مثبت در شروع ({0})، شناسه تماس ({1}) یا شناسه گروه ({2}).",
|
||||
"wayToGetWhapiUrlAndToken": "شما با رفتن به کانال مورد نظر خود از {0} می توانید URL API و توکن را دریافت کنید",
|
||||
"gtxMessagingFromHint": "در موبایل ها، گیرندگان شما TPOA را که به عنوان فرستنده پیام نمایش داده می شود، می بینند. حداکثر 11 کاراکتر (فقط الفبا و اعداد)، یک کد کوتاه، کد طولانی محلی یا اعداد بینالمللی مجاز هستند ({e164}، {e212} یا {e214})",
|
||||
"cellsyntOriginator": "روی تلفن همراه گیرنده به عنوان Originator پیام قابل مشاهده است. مقادیر و تابع مجاز به پارامتر مبدا بستگی دارد.",
|
||||
"cellsyntDestination": "شماره تلفن گیرنده با استفاده از فرمت بینالمللی با 00 اصلی و به دنبال آن کد کشور، به عنوان مثال. 00447920110000 برای بریتانیا شماره 07920 110 000 (حداکثر 17 رقم در کل). حداکثر 25000 گیرنده جدا شده با کاما در هر درخواست HTTP.",
|
||||
"cellsyntSplitLongMessages": "پیام های طولانی را به حداکثر 6 قسمت تقسیم کنید. 153 x 6 = 918 کاراکتر.",
|
||||
"wayToGetThreemaGateway": "شما می توانید برای Threema Gateway {0} ثبت نام کنید.",
|
||||
"threemaSenderIdentityFormat": "8 کاراکتر، معمولا با * (ستاره) شروع می شود",
|
||||
"apiKeysDisabledMsg": "کلیدهای API غیرفعال هستند زیرا احراز هویت غیرفعال است.",
|
||||
"From Phone Number / Transmission Path Originating Address (TPOA)": "از شماره تلفن / آدرس مبدأ مسیر انتقال (TPOA)",
|
||||
"Alphanumeric (recommended)": "الفبا و اعداد (پیشنهاد شده)",
|
||||
"Telephone number": "شماره تلفن",
|
||||
"cellsyntOriginatortypeAlphanumeric": "الفبا و اعداد (حداکثر 11 کاراکتر). گیرندگان نمی توانند به پیام پاسخ دهند."
|
||||
}
|
||||
|
|
|
@ -328,7 +328,7 @@
|
|||
"Query": "Tiedustelu (Query)",
|
||||
"settingsCertificateExpiry": "TLS-varmenteen vanheneminen",
|
||||
"certificationExpiryDescription": "HTTPS-seuraimet käynnistävät ilmoituksen, kun TLS-varmenne vanhenee:",
|
||||
"Setup Docker Host": "Asenna Docker-isäntä",
|
||||
"Setup Docker Host": "Määritä Docker-isäntä",
|
||||
"Connection Type": "Yhteystyyppi",
|
||||
"tcp": "TCP / HTTP",
|
||||
"Docker Container": "Docker-kontti",
|
||||
|
@ -554,7 +554,7 @@
|
|||
"High": "Korkea",
|
||||
"Topic": "Aihe",
|
||||
"WeCom Bot Key": "WeCom-bottiavain",
|
||||
"Setup Proxy": "Asenna välityspalvelin",
|
||||
"Setup Proxy": "Määritä välityspalvelin",
|
||||
"Proxy Protocol": "Välityspalvelinprotokolla",
|
||||
"Proxy Server": "Välityspalvelin",
|
||||
"matrix": "Matriisi",
|
||||
|
@ -792,7 +792,6 @@
|
|||
"emailTemplateLimitedToUpDownNotification": "saatavilla vain YLÖS/ALAS sydämensykkeille, muulloin null",
|
||||
"Your User ID": "Käyttäjätunnuksesi",
|
||||
"invertKeywordDescription": "Etsi puuttuvaa avainsanaa.",
|
||||
"jsonQueryDescription": "Suorita JSON-kysely vastaukselle ja tarkista odotettu arvo (Paluuarvo muutetaan merkkijonoksi vertailua varten). Katso kyselykielen ohjeita osoitteesta {0}. Leikkikenttä löytyy osoitteesta {1}.",
|
||||
"Bark API Version": "Bark API-versio",
|
||||
"Notify Channel": "Ilmoitus kanavalle",
|
||||
"aboutNotifyChannel": "Ilmoitus kanavalle antaa työpöytä- tai mobiili-ilmoituksen kaikille kanavan jäsenille; riippumatta ovatko he paikalla vai poissa.",
|
||||
|
@ -971,5 +970,23 @@
|
|||
"threadForumPostID": "Langan / Keskustelualueketjun ID",
|
||||
"e.g. {discordThreadID}": "esim. {discordThreadID}",
|
||||
"whatHappensAtForumPost": "Luo uusi keskustelualueketju. Tämä EI jatka edellistä ketjua. Lähettääksesi olemassaolevaan ketjuun käytä toimintoa \"{option}\"",
|
||||
"Command": "Komento"
|
||||
"Command": "Komento",
|
||||
"smspartnerApiurl": "Löydät API-avaimesi kojelaudalta osoitteesta {0}",
|
||||
"smspartnerPhoneNumber": "Puhelinnumero(t)",
|
||||
"smspartnerPhoneNumberHelptext": "Numeron täytyy olla kansainvälisessä muodossa {0},{1}. Jos numeroita on useita, ne täytyy erottaa merkillä {2}",
|
||||
"smspartnerSenderName": "SMS-lähettäjän nimi",
|
||||
"smspartnerSenderNameInfo": "Täytyy sisältää 3..=11 tavallista merkkiä",
|
||||
"threemaRecipient": "Vastaanottaja",
|
||||
"threemaRecipientType": "Vastaanottajan tyyppi",
|
||||
"threemaRecipientTypeIdentityFormat": "8 merkkiä",
|
||||
"threemaRecipientTypePhone": "Puhelinnumero",
|
||||
"threemaRecipientTypePhoneFormat": "E.164, ilman etuliitettä +",
|
||||
"threemaRecipientTypeEmail": "Sähköpostiosoite",
|
||||
"threemaSenderIdentity": "Yhdyskäytävän tunnus (Gateway-ID)",
|
||||
"threemaApiAuthenticationSecret": "Gateway-ID salaisuus",
|
||||
"wayToGetThreemaGateway": "Voit rekisteröityä Threema Gateway:n käyttäjäksi {0}.",
|
||||
"threemaRecipientTypeIdentity": "Threema-tunnus (Threema-ID)",
|
||||
"threemaSenderIdentityFormat": "8 merkkiä, alkaa useimmiten merkillä *",
|
||||
"threemaBasicModeInfo": "Huomio: Tämä integraatio käyttää Threema Gateway:tä perustilassa (palvelinpohjainen salaus). Lisätietoja löytyy {0}.",
|
||||
"apiKeysDisabledMsg": "API-avaimet eivät ole käytössä koska tunnistautuminen ei ole käytössä."
|
||||
}
|
||||
|
|
|
@ -797,7 +797,6 @@
|
|||
"twilioApiKey": "Clé API (facultatif)",
|
||||
"Expected Value": "Valeur attendue",
|
||||
"Json Query": "Requête Json",
|
||||
"jsonQueryDescription": "Faites une requête json contre la réponse et vérifiez la valeur attendue (la valeur de retour sera convertie en chaîne pour comparaison). Consultez {0} pour la documentation sur le langage de requête. Une aire de jeux peut être trouvée {1}.",
|
||||
"Badge Duration (in hours)": "Durée du badge (en heures)",
|
||||
"Badge Preview": "Aperçu du badge",
|
||||
"aboutNotifyChannel": "Notifier le canal déclenchera une notification de bureau ou mobile pour tous les membres du canal, que leur disponibilité soit active ou absente.",
|
||||
|
@ -973,5 +972,23 @@
|
|||
"ignoreTLSErrorGeneral": "Ignorer l'erreur TLS/SSL pour la connexion",
|
||||
"Bitrix24 Webhook URL": "URL du Webhook Bitrix24",
|
||||
"wayToGetBitrix24Webhook": "Vous pouvez créer un webhook en suivant les étapes de {0}",
|
||||
"bitrix24SupportUserID": "Entrez votre identifiant d'utilisateur dans Bitrix24. Vous pouvez trouver l'ID à partir du lien en allant sur le profil de l'utilisateur."
|
||||
"bitrix24SupportUserID": "Entrez votre identifiant d'utilisateur dans Bitrix24. Vous pouvez trouver l'ID à partir du lien en allant sur le profil de l'utilisateur.",
|
||||
"smspartnerPhoneNumber": "Numéro(s) de téléphone",
|
||||
"smspartnerPhoneNumberHelptext": "Le numéro doit être au format international {0}, {1}. Plusieurs numéros doivent être séparés par {2}",
|
||||
"smspartnerSenderName": "Nom de l'expéditeur SMS",
|
||||
"smspartnerSenderNameInfo": "Doit être compris entre 3 et 11 caractères réguliers",
|
||||
"smspartnerApiurl": "Vous pouvez trouver votre clé API dans votre tableau de bord à {0}",
|
||||
"threemaRecipient": "Destinataire",
|
||||
"threemaRecipientType": "Type de destinataire",
|
||||
"threemaRecipientTypeIdentity": "Threema-ID",
|
||||
"threemaRecipientTypeIdentityFormat": "8 caractères",
|
||||
"threemaRecipientTypePhone": "Numéro de téléphone",
|
||||
"threemaRecipientTypePhoneFormat": "E.164, sans le signe + initial",
|
||||
"threemaRecipientTypeEmail": "Adresse email",
|
||||
"threemaSenderIdentity": "Identifiant de passerelle",
|
||||
"threemaSenderIdentityFormat": "8 caractères, commence généralement par *",
|
||||
"threemaApiAuthenticationSecret": "Secret de l'identifiant de la passerelle",
|
||||
"wayToGetThreemaGateway": "Vous pouvez vous inscrire à Threema Gateway {0}.",
|
||||
"threemaBasicModeInfo": "Note : Cette intégration utilise Threema Gateway en mode basique (chiffrement basé sur le serveur). Vous pouvez trouver plus de détails {0}.",
|
||||
"apiKeysDisabledMsg": "Les clés API sont désactivées car l'authentification est désactivée."
|
||||
}
|
||||
|
|
|
@ -89,7 +89,7 @@
|
|||
"programmingLanguages": "Teangacha Programála",
|
||||
"Save": "Sábháil",
|
||||
"Notifications": "Fógraí",
|
||||
"Setup Notification": "Fógraí Socruithe",
|
||||
"Setup Notification": "Socrú Fógra",
|
||||
"Light": "Solas",
|
||||
"Dark": "Dorcha",
|
||||
"Auto": "Uathoibríoch",
|
||||
|
@ -348,7 +348,7 @@
|
|||
"Connection String": "Teaghrán Ceangail",
|
||||
"Query": "Ceist",
|
||||
"certificationExpiryDescription": "Spreagann Monatóirí HTTPS fógra nuair a théann teastas TLS in éag i:",
|
||||
"Setup Docker Host": "Socrú Óstach Docker",
|
||||
"Setup Docker Host": "Socraigh Suas Óstach Docker",
|
||||
"Connection Type": "Cineál Ceangail",
|
||||
"Docker Daemon": "Deamhan Docker",
|
||||
"DockerHostRequired": "Socraigh an tÓstríomhaire Docker don mhonatóir seo.",
|
||||
|
@ -639,7 +639,7 @@
|
|||
"Channel Name": "Ainm Cainéal",
|
||||
"Notify Channel": "Cainéal a chur ar an eolas",
|
||||
"Uptime Kuma URL": "URL Uptime Kuma",
|
||||
"setup a new monitor group": "grúpa monatóireachta nua a shocrú",
|
||||
"setup a new monitor group": "grúpa monatóireachta nua a bhunú",
|
||||
"openModalTo": "módúil a oscailt go {0}",
|
||||
"Add a domain": "Cuir fearann leis",
|
||||
"Remove domain": "Bain an fearann '{0}'",
|
||||
|
@ -682,7 +682,6 @@
|
|||
"confirmDisableTwoFAMsg": "An bhfuil tú cinnte gur mhaith leat 2FA a dhíchumasú?",
|
||||
"affectedStatusPages": "Taispeáin an teachtaireacht cothabhála seo ar leathanaigh stádais roghnaithe",
|
||||
"keywordDescription": "Cuardaigh eochairfhocal i ngnáthfhreagra HTML nó JSON. Tá an cuardach cás-íogair.",
|
||||
"jsonQueryDescription": "Déan Iarratas json in aghaidh an fhreagra agus seiceáil an luach a bhfuiltear ag súil leis (Déanfar an luach fillte a thiontú ina theaghrán le haghaidh comparáide). Seiceáil {0} le haghaidh na gcáipéisí faoin teanga iarratais. Is féidir clós súgartha a aimsiú {1}.",
|
||||
"backupDescription": "Is féidir leat gach monatóir agus fógra a chúltaca isteach i gcomhad JSON.",
|
||||
"backupDescription2": "Nóta: níl sonraí staire agus imeachta san áireamh.",
|
||||
"octopushAPIKey": "\"Eochair API\" ó dhintiúir API HTTP sa phainéal rialaithe",
|
||||
|
@ -938,5 +937,23 @@
|
|||
"Alphanumeric (recommended)": "Alfa-uimhriúil (molta)",
|
||||
"Telephone number": "Uimhir theileafóin",
|
||||
"cellsyntSplitLongMessages": "Roinn teachtaireachtaí fada suas le 6 chuid. 153 x 6 = 918 carachtar.",
|
||||
"max 15 digits": "15 dhigit ar a mhéad"
|
||||
"max 15 digits": "15 dhigit ar a mhéad",
|
||||
"threemaRecipient": "Faighteoir",
|
||||
"threemaRecipientType": "Cineál Faighteoir",
|
||||
"threemaRecipientTypeIdentity": "Threema-ID",
|
||||
"threemaRecipientTypeIdentityFormat": "8 gcarachtar",
|
||||
"threemaRecipientTypePhone": "Uimhir teileafón",
|
||||
"threemaRecipientTypePhoneFormat": "E.164, gan tosaigh +",
|
||||
"threemaRecipientTypeEmail": "Seoladh ríomhphoist",
|
||||
"threemaSenderIdentity": "Geata-ID",
|
||||
"threemaApiAuthenticationSecret": "Rún Geata-ID",
|
||||
"smspartnerPhoneNumber": "Uimhir(neacha) fóin",
|
||||
"smspartnerSenderName": "Ainm Seoltóra SMS",
|
||||
"smspartnerSenderNameInfo": "Caithfidh sé a bheith idir 3..=11 charachtar rialta",
|
||||
"apiKeysDisabledMsg": "Tá eochracha API díchumasaithe toisc go bhfuil fíordheimhniú díchumasaithe.",
|
||||
"smspartnerApiurl": "Is féidir leat d'eochair API a aimsiú ar do dheais ag {0}",
|
||||
"smspartnerPhoneNumberHelptext": "Caithfidh an uimhir a bheith san fhormáid idirnáisiúnta {0}, {1}. Ní mór uimhreacha iolracha a dheighilt le {2}",
|
||||
"wayToGetThreemaGateway": "Is féidir leat clárú le haghaidh Threema Gateway {0}.",
|
||||
"threemaSenderIdentityFormat": "8 gcarachtar, a thosaíonn le * de ghnáth",
|
||||
"threemaBasicModeInfo": "Nóta: Úsáideann an comhtháthú seo Threema Gateway i mód bunúsach (criptiúchán freastalaí-bhunaithe). Is féidir tuilleadh sonraí a fháil {0}."
|
||||
}
|
||||
|
|
|
@ -799,7 +799,6 @@
|
|||
"affectedStatusPages": "Prikazuje poruku o održavanju na odabranim statusnim stranicama",
|
||||
"atLeastOneMonitor": "Odaberite barem jedan zahvaćeni Monitor",
|
||||
"invertKeywordDescription": "Postavi da ključna riječ mora biti odsutna umjesto prisutna.",
|
||||
"jsonQueryDescription": "Izvršite JSON upit nad primljenim odgovorom i provjerite očekivanu povrtanu vrijednost. Ona će se za usporedbu pretvoriti u niz znakova (string). Pogledajte stranicu {0} za dokumentaciju o jeziku upita. Testno okruženje možete pronaći {1}.",
|
||||
"Strategy": "Strategija",
|
||||
"Free Mobile User Identifier": "Besplatni mobilni korisnički identifikator",
|
||||
"Free Mobile API Key": "Besplatni mobilni ključ za API",
|
||||
|
@ -967,5 +966,23 @@
|
|||
"Mention group": "Spomeni {group}",
|
||||
"senderSevenIO": "Broj ili naziv pošiljatelja",
|
||||
"receiverSevenIO": "Broj primatelja",
|
||||
"receiverInfoSevenIO": "Ako broj primatelja nije registriran u Njemačkoj, potrebno je uključiti i pozivni broj države (primjerice, umjesto 023123123 potrebno je koristiti 36523123123 ako je riječ o hrvatskom broju)"
|
||||
"receiverInfoSevenIO": "Ako broj primatelja nije registriran u Njemačkoj, potrebno je uključiti i pozivni broj države (primjerice, umjesto 023123123 potrebno je koristiti 36523123123 ako je riječ o hrvatskom broju)",
|
||||
"threemaRecipient": "Primatelj",
|
||||
"threemaRecipientType": "Tip pošiljatelja",
|
||||
"threemaRecipientTypeIdentity": "Threema ID",
|
||||
"threemaRecipientTypeIdentityFormat": "8 znakova",
|
||||
"threemaRecipientTypeEmail": "Adresa e-pošte",
|
||||
"threemaSenderIdentity": "ID pristupnika",
|
||||
"threemaSenderIdentityFormat": "8 znakova, obično počinje znakom *",
|
||||
"threemaApiAuthenticationSecret": "Tajna pristupnika",
|
||||
"smspartnerApiurl": "Možete pronaći svoj API ključ na nadzornoj ploči: {0}",
|
||||
"smspartnerPhoneNumber": "Broj(evi) telefona",
|
||||
"smspartnerPhoneNumberHelptext": "Broj mora biti u međunarodnom formatu {0}, {1}. Višestruki unosi moraju biti odvojeni znakom {2}",
|
||||
"smspartnerSenderName": "Naziv SMS pošiljatelja",
|
||||
"apiKeysDisabledMsg": "API ključevi su onemogućeni jer je provjera autentičnosti onemogućena.",
|
||||
"wayToGetThreemaGateway": "Možete se registrirati za uslugu Threema Gateway {0}.",
|
||||
"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"
|
||||
}
|
||||
|
|
|
@ -632,7 +632,7 @@
|
|||
"Connection String": "Csatlakozási karakterlánc",
|
||||
"statusPageSpecialSlugDesc": "Speciális slug {0}: ez az oldal akkor jelenik meg, ha nincs slug megadva",
|
||||
"certificationExpiryDescription": "A HTTPS-monitorok értesítést váltanak ki, ha a TLS-tanúsítvány lejár:",
|
||||
"Setup Docker Host": "Docker hoszt beállítása",
|
||||
"Setup Docker Host": "Docker Gazda beállítása",
|
||||
"Connection Type": "Csatlakozás Típusa",
|
||||
"Docker Daemon": "Docker Daemon",
|
||||
"deleteDockerHostMsg": "Biztos, hogy törölni akarod ezt a docker hostot az összes monitorról?",
|
||||
|
@ -642,7 +642,7 @@
|
|||
"Docker Hosts": "Docket Hosztok",
|
||||
"Domain": "Domain",
|
||||
"Docker Host": "Docket Hoszt",
|
||||
"noDockerHostMsg": "Nem elérhető. Először állíts be egy Docker Hosztot.",
|
||||
"noDockerHostMsg": "Nem elérhető. Először állítson be egy Docker Gazdát.",
|
||||
"DockerHostRequired": "Kérlek állítsd be a Docker Hosztot ehhez a monitorhoz.",
|
||||
"tailscalePingWarning": "A Tailscale Ping monitor használatához telepítenie kell az Uptime Kuma-t Docker nélkül, és telepíteni kell a Tailscale klienst is a szerverre.",
|
||||
"Workstation": "Munkaállomás",
|
||||
|
@ -776,7 +776,7 @@
|
|||
"For safety, must use secret key": "Biztonsági okoból kötelező a titkos kulcs használata",
|
||||
"emailCustomisableContent": "Testreszabható tartalom",
|
||||
"leave blank for default subject": "hagyja üresen az alapértelmezett email tárgyát",
|
||||
"smtpLiquidIntroduction": "A következő két mező a Liquid sablonozási nyelven keresztül szerkezthető. Kérjük, olvassa el a következőt: {0} a használati utasításokért. Ezek az elérhető változók:",
|
||||
"smtpLiquidIntroduction": "A következő két mező a Liquid sablonozási nyelven keresztül szerkeszthető. Kérjük, olvassa el a következőt: {0} a használati utasításokért. Ezek az elérhető változók:",
|
||||
"Learn More": "Tudjon meg többet",
|
||||
"Notify Channel": "Csatorna értesítése",
|
||||
"install": "Telepítés",
|
||||
|
@ -797,7 +797,6 @@
|
|||
"emailTemplateHeartbeatJSON": "a szívverést leíró objektum",
|
||||
"emailTemplateMsg": "az értesítés üzenete",
|
||||
"emailTemplateLimitedToUpDownNotification": "csak FEL/LE szívverés esetén érhető el, egyébként null érték",
|
||||
"jsonQueryDescription": "Végezzen JSON-lekérdezést a válasz alapján, és ellenőrizze a várt értéket (a visszatérési értéket a rendszer karakterlánccá alakítja az összehasonlításhoz). Nézze meg a {0} webhelyet a lekérdezés paramétereivel kapcsolatos dokumentációért. A test környezet itt található: {1}.",
|
||||
"pushoverMessageTtl": "TTL üzenet (másodperc)",
|
||||
"Platform": "Platform",
|
||||
"aboutNotifyChannel": "A Csatorna értesítése opció, értesítést fog küldeni a csatorna összes tagjának, függetlenül a tagok elérhetőségétől.",
|
||||
|
@ -828,5 +827,28 @@
|
|||
"Mechanism": "Mechanizmus",
|
||||
"No API Keys": "Nincsenek API kulcsok",
|
||||
"Monitor Group": "Monitor csoport",
|
||||
"ntfyAuthenticationMethod": "Hitelesítési metódus"
|
||||
"ntfyAuthenticationMethod": "Hitelesítési metódus",
|
||||
"Host URL": "Gazda URL",
|
||||
"Select message type": "Válasszon üzenet típust",
|
||||
"Send to channel": "Küldés erre a csatornára",
|
||||
"Create new forum post": "Új fórum üzenet készítése",
|
||||
"Refresh Interval Description": "Az állapot megfigyelő {0} másodpercenként teljesen újra frissíti a weboldalt",
|
||||
"Refresh Interval": "Frissítési intervallum",
|
||||
"postToExistingThread": "Létező szál / fórum -ra küldés",
|
||||
"forumPostName": "Fórum üzenet neve",
|
||||
"threadForumPostID": "Szál / Fórum üzenet ID",
|
||||
"e.g. {discordThreadID}": "például. {discordThreadID}",
|
||||
"locally configured mail transfer agent": "Helyileg beállított email továbbító",
|
||||
"Either enter the hostname of the server you want to connect to or localhost if you intend to use a locally configured mail transfer agent": "Adja meg a csatlakoztatni kívánt szerver gazdagép nevét vagy {localhost} ha a {local_mta}-t szeretné használni",
|
||||
"ignoreTLSErrorGeneral": "Mellőzze a TLS/SSL hibákat a csatlakozáshoz",
|
||||
"threemaRecipientTypeIdentityFormat": "8 karakter",
|
||||
"threemaRecipientTypeEmail": "Email cím",
|
||||
"threemaSenderIdentity": "Gateway-azonosító",
|
||||
"threemaRecipientTypeIdentity": "Threema-azonosító",
|
||||
"threemaSenderIdentityFormat": "8 karakter, általában *-al kezdődik",
|
||||
"threemaRecipientTypePhone": "Telefonszám",
|
||||
"max 15 digits": "max 15 karakter",
|
||||
"cellsyntDestination": "A címzett telefonszáma nemzetközi formátumban megadva. A kezdő 00-t követően az országkód, pl. 003612127654 egy magyarországi 0612127654 szám esetében (max 17 karakter összesen). HTTP lekérdezésenként max 25000, vesszővel elválaszott címzett.",
|
||||
"Telephone number": "Telefonszám",
|
||||
"Allow Long SMS": "Hosszú SMS engedélyezve"
|
||||
}
|
||||
|
|
|
@ -805,7 +805,7 @@
|
|||
"emailTemplateLimitedToUpDownNotification": "hanya tersedia untuk detak jantung ATAS/BAWAH, jika tidak, null",
|
||||
"aboutNotifyChannel": "Beritahu saluran akan memicu pemberitahuan desktop atau seluler untuk semua anggota saluran, baik ketersediaannya diatur ke aktif atau tidak.",
|
||||
"monitorToastMessagesDescription": "Notifikasi Toast untuk monitor hilang setelah waktu tertentu dalam hitungan detik. Setel ke -1 akan menonaktifkan batas waktu. Setel ke 0 akan menonaktifkan notifikasi Toast.",
|
||||
"wayToGetFlashDutyKey": "Anda dapat membuka Saluran -> (Pilih Saluran) -> Integrasi -> Tambahkan halaman integrasi baru, tambahkan 'Acara Khusus' untuk mendapatkan alamat push, salin Kunci Integrasi di alamat tersebut. Untuk informasi lebih lanjut, silahkan kunjungi",
|
||||
"wayToGetFlashDutyKey": "Anda dapat membuka Saluran -> (Pilih Saluran) -> Integrasi -> Tambahkan halaman integrasi baru, tambahkan 'Uptime Kuma' untuk mendapatkan alamat push, salin Kunci Integrasi di alamat tersebut. Untuk informasi lebih lanjut, silakan kunjungi",
|
||||
"Saved.": "Tersimpan.",
|
||||
"authUserInactiveOrDeleted": "Pengguna tidak aktif atau dihapus.",
|
||||
"authInvalidToken": "Token Tidak Valid.",
|
||||
|
@ -838,7 +838,6 @@
|
|||
"emailTemplateHeartbeatJSON": "objek yang menggambarkan heartbeat",
|
||||
"emailTemplateMsg": "pesan pemberitahuan",
|
||||
"emailCustomBody": "Kustomisasi Body",
|
||||
"jsonQueryDescription": "Lakukan Query json terhadap respons dan periksa nilai yang diharapkan (Nilai yang dikembalikan akan diubah menjadi string untuk perbandingan). Lihat {0} untuk dokumentasi tentang bahasa kueri. Taman bermain dapat ditemukan {1}.",
|
||||
"Notify Channel": "Beritahu Saluran",
|
||||
"Server URL should not contain the nfty topic": "URL server tidak boleh berisi topik nfty",
|
||||
"PushDeer Server": "Server PushDeer",
|
||||
|
@ -897,7 +896,7 @@
|
|||
"Remote Browser": "Peramban Jarak Jauh",
|
||||
"Add a Remote Browser": "Tambahkan Remote Browser",
|
||||
"Remote Browser not found!": "Remote Browser tidak ditemukan!",
|
||||
"remoteBrowsersDescription": "Remote Browsers adalah alternatif untuk menjalankan Chromium secara lokal. Siapkan dengan layanan seperti browserless.io atau sambungkan ke layanan Anda sendiri",
|
||||
"remoteBrowsersDescription": "Remote Browsers adalah cara alternatif untuk menjalankan Chromium secara lokal. Siapkan dengan layanan seperti browserless.io atau sambungkan ke layanan Anda sendiri",
|
||||
"self-hosted container": "kontainer self-hosted",
|
||||
"remoteBrowserToggle": "Secara default, Chromium berjalan di dalam kontainer Uptime Kuma. Anda dapat menggunakan remote browser dengan menekan tombol ini.",
|
||||
"useRemoteBrowser": "Gunakan Remote Browser",
|
||||
|
@ -920,7 +919,7 @@
|
|||
"To Phone Number": "Untuk Nomor Telepon",
|
||||
"gtxMessagingToHint": "Format internasional, dimulai dengan tanda plus \"+\" ({e164}, {e212} or {e214})",
|
||||
"gtxMessagingApiKeyHint": "Anda dapat menemukan Kunci API Anda di: Perutean Akun Saya > Lihat Informasi Akun > Kredensial API > REST API (v2.x)",
|
||||
"Originator type": "Tipe Pencetus",
|
||||
"Originator type": "Tipe Originator",
|
||||
"Alphanumeric (recommended)": "Alfanumerik (direkomendasikan)",
|
||||
"Telephone number": "Nomor telepon",
|
||||
"cellsyntOriginatortypeAlphanumeric": "String alfanumerik (maks 11 karakter). Penerima tidak dapat membalas pesan tersebut.",
|
||||
|
@ -929,8 +928,8 @@
|
|||
"Allow Long SMS": "Izinkan SMS Panjang",
|
||||
"max 15 digits": "Maksimal 15 digit",
|
||||
"callMeBotGet": "Anda dapat membuat endpoint untuk {0}, {1} dan {2}. Ingatlah bahwa Anda mungkin akan terkena batas tarif. Batas tarif saat ini adalah: {3}",
|
||||
"Originator": "Pencetus",
|
||||
"cellsyntOriginator": "Terlihat di ponsen penerima sebagai pencetus pesan. Nilai dan fungsi yang diizinkan bergantung pada parameter tipe pencetus.",
|
||||
"Originator": "Originator",
|
||||
"cellsyntOriginator": "Terlihat di ponsel penerima sebagai pencetus pesan. Nilai dan fungsi yang diizinkan bergantung pada parameter tipe pencetus.",
|
||||
"Destination": "Tujuan",
|
||||
"wayToGetWhapiUrlAndToken": "Anda dapat menggunakan URL API dan token dengan bergabung ke saluran yang Anda inginkan dari {0}",
|
||||
"whapiRecipient": "Nomor Telepon / Kontak ID / Grup ID",
|
||||
|
@ -940,5 +939,51 @@
|
|||
"wayToWriteWhapiRecipient": "Nomor telepon dalam format internasional, tanpa tanda tambah ({0}), Kontak ID ({1}) atau Grup ID ({2}).",
|
||||
"documentationOf": "{0} Dokumentasi",
|
||||
"Channel access token (Long-lived)": "Token akses saluran (durasi panjang)",
|
||||
"Your User ID": "User ID Anda"
|
||||
"Your User ID": "User ID Anda",
|
||||
"mongodbCommandDescription": "Jalankan perintah MongoDB terhadap database. Untuk informasi tentang perintah yang tersedia, lihat {documentation}",
|
||||
"Command": "Perintah",
|
||||
"threemaRecipient": "Penerima",
|
||||
"threemaRecipientType": "Tipe Penerima",
|
||||
"threemaRecipientTypeIdentity": "Threem-ID",
|
||||
"threemaRecipientTypeIdentityFormat": "8 karakter",
|
||||
"threemaRecipientTypePhone": "Nomor Telepon",
|
||||
"threemaRecipientTypePhoneFormat": "E.164, tanpa awalan +",
|
||||
"threemaRecipientTypeEmail": "Alamat Email",
|
||||
"threemaSenderIdentity": "Gateway-ID",
|
||||
"threemaApiAuthenticationSecret": "Gateway-ID Secret",
|
||||
"smspartnerPhoneNumber": "Nomor telepon",
|
||||
"smspartnerSenderName": "Nama Pengirim SMS",
|
||||
"Mentioning": "Menyebut",
|
||||
"Don't mention people": "Jangan menyebut orang",
|
||||
"bitrix24SupportUserID": "Masukkan ID pengguna Anda di Bitrix24. Anda dapat mengetahui ID dari tautan dengan membuka profil pengguna.",
|
||||
"Bitrix24 Webhook URL": "Bitrix24 Webhook URL",
|
||||
"wayToGetBitrix24Webhook": "Anda dapat membuat webhook dengan mengikuti langkah-langkah di {0}",
|
||||
"receiverSevenIO": "Menerima nomor",
|
||||
"receiverInfoSevenIO": "Jika nomor penerima tidak berlokasi di Jerman, Anda harus menambahkan kode negara di depan nomor tersebut (misalnya untuk kode negara 1 dari AS gunakan 117612121212, bukan 017612121212)",
|
||||
"Host URL": "Host URL",
|
||||
"senderSevenIO": "Mengirim nomor atau nama",
|
||||
"apiKeysDisabledMsg": "Kunci API dinonaktifkan karena autentikasi dinonaktifkan.",
|
||||
"forumPostName": "Nama postingan forum",
|
||||
"threadForumPostID": "Thread / ID postingan Forum",
|
||||
"e.g. {discordThreadID}": "misalnya {discordThreadID}",
|
||||
"Select message type": "Pilih jenis pesan",
|
||||
"Send to channel": "Kirim ke saluran",
|
||||
"Create new forum post": "Buat postingan forum baru",
|
||||
"postToExistingThread": "Posting ke thread/postingan forum yang ada",
|
||||
"whatHappensAtForumPost": "Buat postingan forum baru. Ini TIDAK mempublikasikan pesan di postingan yang sudah ada. Untuk mempublikasikan di postingan yang sudah ada gunakan '{option}'",
|
||||
"smspartnerApiurl": "Anda dapat menemukan kunci API di dasbor Anda di {0}",
|
||||
"smspartnerPhoneNumberHelptext": "Nomor harus dalam format internasional {0}, {1}. Beberapa angka harus dipisahkan dengan {2}",
|
||||
"smspartnerSenderNameInfo": "Harus di antara 3..=11 karakter biasa",
|
||||
"wayToGetSevenIOApiKey": "Kunjungi dasbor di bawah app.seven.io > pengembang > api key > tombol yang berwarna hijau",
|
||||
"wayToGetThreemaGateway": "Anda dapat mendaftar ke Threema Gateway {0}.",
|
||||
"threemaSenderIdentityFormat": "8 karakter, biasanya diawali dengan *",
|
||||
"threemaBasicModeInfo": "Catatan: Integrasi ini menggunakan Threema Gateway dalam mode dasar (enkripsi berbasis server). Detail lebih lanjut dapat ditemukan {0}.",
|
||||
"wayToGetDiscordThreadId": "Mendapatkan id postingan thread/forum mirip dengan mendapatkan id saluran. Baca selengkapnya tentang cara mendapatkan ID {0}",
|
||||
"Mention group": "Sebutkan {grup}",
|
||||
"Refresh Interval Description": "Laman status akan melakukan penyegaran situs secara penuh setiap {0} detik",
|
||||
"Refresh Interval": "Interval muat ulang",
|
||||
"apiKeySevenIO": "Kunci API SevenIO",
|
||||
"locally configured mail transfer agent": "transfer email agent yang dikonfigurasi secara lokal",
|
||||
"Either enter the hostname of the server you want to connect to or localhost if you intend to use a locally configured mail transfer agent": "Masukkan nama host server yang ingin Anda sambungkan atau {localhost} jika Anda ingin menggunakan {local_mta}",
|
||||
"ignoreTLSErrorGeneral": "Abaikan kesalahan TLS/SSL untuk koneksi"
|
||||
}
|
||||
|
|
|
@ -423,7 +423,7 @@
|
|||
"styleElapsedTimeShowWithLine": "Mostra (con linea)",
|
||||
"webhookBodyPresetOption": "Predefinito - {0}",
|
||||
"webhookBodyCustomOption": "Corpo personalizzato",
|
||||
"topic": "Topic",
|
||||
"topic": "Argomento",
|
||||
"selectedMonitorCount": "Selezionato: {0}",
|
||||
"Check/Uncheck": "Seleziona/Deseleziona",
|
||||
"Proxies": "Proxy",
|
||||
|
@ -445,7 +445,7 @@
|
|||
"Subject:": "Soggetto:",
|
||||
"Valid To:": "Valido per:",
|
||||
"Days Remaining:": "Giorni rimanenti:",
|
||||
"Issuer:": "Emittente",
|
||||
"Issuer:": "Emittente:",
|
||||
"Fingerprint:": "Impronta digitale:",
|
||||
"No status pages": "Nessuna pagina di stato",
|
||||
"Domain Name Expiry Notification": "Notifica di scadenza del nome di dominio",
|
||||
|
@ -535,10 +535,10 @@
|
|||
"Server Timezone": "Fuso orario del server",
|
||||
"statusPageMaintenanceEndDate": "Fine",
|
||||
"IconUrl": "URL dell'icona",
|
||||
"Enable DNS Cache": "(Obsoleto) Abilita la cache DNS per i monitor HTTP(s).",
|
||||
"Enable DNS Cache": "(Obsoleto) Abilita la cache DNS per i monitor HTTP(s)",
|
||||
"Enable": "Abilitare",
|
||||
"Disable": "Disattivare",
|
||||
"chromeExecutableAutoDetect": "Trovato automaticamente",
|
||||
"chromeExecutableAutoDetect": "Trova automaticamente",
|
||||
"dnsCacheDescription": "Potrebbe non funzionare in alcuni ambienti IPv6, disabilitalo in caso di problemi.",
|
||||
"Single Maintenance Window": "Singola finestra di manutenzione",
|
||||
"Maintenance Time Window of a Day": "Finestra temporale di manutenzione di un giorno",
|
||||
|
@ -554,7 +554,7 @@
|
|||
"uninstall": "Disinstalla",
|
||||
"confirmUninstallPlugin": "Sei sicuro di voler disinstallare questo plugin?",
|
||||
"notificationRegional": "Regionale",
|
||||
"Clone": "Clone",
|
||||
"Clone": "Duplica",
|
||||
"cloneOf": "Clone di {0}",
|
||||
"wayToGetZohoCliqURL": "Puoi scoprire come creare un URL webhook {0}.",
|
||||
"dataRetentionTimeError": "Il periodo di conservazione deve essere pari o superiore a 0",
|
||||
|
@ -586,7 +586,7 @@
|
|||
"invalidCronExpression": "Espressione Cron non valida: {0}",
|
||||
"lastDay2": "2° ultimo giorno del mese",
|
||||
"maintenanceStatus-under-maintenance": "In manutenzione",
|
||||
"chromeExecutable": "Cromo/cromo eseguibile",
|
||||
"chromeExecutable": "Eseguibile Chrome/Chromium",
|
||||
"chromeExecutableDescription": "Per gli utenti Docker, se Chromium non è ancora installato, potrebbero essere necessari alcuni minuti per l'installazione e la visualizzazione del risultato del test. Richiede 1 GB di spazio su disco.",
|
||||
"uninstalling": "Disinstallazione",
|
||||
"confirmDeleteTagMsg": "Sei sicuro di voler eliminare questo tag? I monitor associati a questo tag non verranno eliminati.",
|
||||
|
@ -620,7 +620,6 @@
|
|||
"enableNSCD": "Abilita NSCD (Name Service Cache Daemon) per abilitare la cache su tutte le richieste DNS",
|
||||
"recurringIntervalMessage": "Esegui una volta al giorno | Esegui una volta ogni {0} giorni",
|
||||
"affectedMonitorsDescription": "Seleziona i monitoraggi che sono influenzati da questa manutenzione",
|
||||
"jsonQueryDescription": "Fai una query JSON verso la risposta e controlla se è presente il valore richiesto. (Il valore di ritorno verrà convertito in stringa ai fini della comparazione). Puoi controllare la documentazione su <a href='https://jsonata.org/'>jsonata.org</a> per conoscere come scrivere una query. Un area dimostrativa può essere trovata <a href='https://try.jsonata.org/'>qui</a>.",
|
||||
"For safety, must use secret key": "Per sicurezza, devi usare una chiave segreta",
|
||||
"Proxy server has authentication": "Il server Proxy ha una autenticazione",
|
||||
"smseaglePriority": "Priorità messaggio (0-9, default = 0)",
|
||||
|
@ -669,12 +668,35 @@
|
|||
"templateMsg": "Messaggio della notifica",
|
||||
"Search monitored sites": "Ricerca tra i siti monitorati",
|
||||
"settingUpDatabaseMSG": "Configurazione del database in corso. Potrebbe richiedere un po' di tempo...",
|
||||
"templateHeartbeatJSON": "oggetto che descrive il battito cardiaco",
|
||||
"templateHeartbeatJSON": "oggetto che descrive il controllo",
|
||||
"templateLimitedToUpDownNotifications": "disponibile solo per le notifiche UP/DOWN",
|
||||
"Add a new expiry notification day": "Aggiungi un nuovo giorno di notifica di scadenza",
|
||||
"DockerHostRequired": "Per favore, imposta l'host Docker per questo monitor.",
|
||||
"smtpLiquidIntroduction": "I due campi seguenti sono adattabili tramite il linguaggio di templating Liquid. Consulta {0} per le istruzioni d'uso. Queste sono le variabili disponibili:",
|
||||
"emailCustomisableContent": "Contenuto personalizzabile",
|
||||
"noDockerHostMsg": "Non disponibile. Configura prima un host Docker.",
|
||||
"Remove the expiry notification": "Rimuovi il giorno di notifica di scadenza"
|
||||
"Remove the expiry notification": "Rimuovi il giorno di notifica di scadenza",
|
||||
"emailTemplateHeartbeatJSON": "oggetto che descrive il controllo",
|
||||
"leave blank for default subject": "Lasciare vuoto per oggetto predefinito",
|
||||
"emailCustomBody": "Messaggio personalizzato",
|
||||
"Select message type": "Seleziona il tipo di messaggio",
|
||||
"Send to channel": "Invia al canale",
|
||||
"Create new forum post": "Crea un nuovo post sul forum",
|
||||
"postToExistingThread": "Scrivi in una conversazione / post esistente",
|
||||
"forumPostName": "Nome forum del post",
|
||||
"threadForumPostID": "ID della conversazione / Forum del post",
|
||||
"e.g. {discordThreadID}": "es. {discordThreadID}",
|
||||
"Either enter the hostname of the server you want to connect to or localhost if you intend to use a locally configured mail transfer agent": "Inserisci il nome host del server a cui vuoi connetterti oppure {localhost} se intendi utilizzare un {local_mta}",
|
||||
"templateLimitedToUpDownCertNotifications": "disponibile solo per le notifiche UP/DOWN/Scadenza certificato",
|
||||
"emailTemplateMsg": "Messaggio della notifica",
|
||||
"emailTemplateLimitedToUpDownNotification": "disponibile solo per controlli UP/DOWN, altrimenti nullo",
|
||||
"Refresh Interval": "Intervallo di aggiornamento",
|
||||
"Refresh Interval Description": "La pagina di stato si aggiornerà completamente ogni {0} secondi",
|
||||
"emailTemplateServiceName": "Nome servizio",
|
||||
"leave blank for default body": "Lasciare vuoto per messaggio predefinito",
|
||||
"emailTemplateHostnameOrURL": "Nome host o URL",
|
||||
"emailTemplateStatus": "Stato",
|
||||
"ignoreTLSErrorGeneral": "Ignora gli errori TLS/SSL per la connessione",
|
||||
"statusPageSpecialSlugDesc": "Slug speciale {0}: questa pagina verrà mostrata quando non viene fornito alcuno slug",
|
||||
"successKeywordExplanation": "Parola chiave MQTT che verrà considerata come corretta"
|
||||
}
|
||||
|
|
124
src/lang/ja.json
124
src/lang/ja.json
|
@ -92,7 +92,7 @@
|
|||
"No": "いいえ",
|
||||
"Username": "ユーザー名",
|
||||
"Password": "パスワード",
|
||||
"Remember me": "パスワードを忘れた場合",
|
||||
"Remember me": "ログインしたままにする",
|
||||
"Login": "ログイン",
|
||||
"No Monitors, please": "監視がありません",
|
||||
"add one": "add one",
|
||||
|
@ -131,7 +131,7 @@
|
|||
"confirmDisableTwoFAMsg": "2段階認証を「無効」にします。よろしいですか?",
|
||||
"Apply on all existing monitors": "既存のすべてのモニターに適用する",
|
||||
"Verify Token": "認証する",
|
||||
"Setup 2FA": "2段階認証の設定",
|
||||
"Setup 2FA": "二要素認証 (2FA) の設定",
|
||||
"Enable 2FA": "2段階認証を有効にする",
|
||||
"Disable 2FA": "2段階認証を無効にする",
|
||||
"2FA Settings": "2段階認証の設定",
|
||||
|
@ -225,7 +225,7 @@
|
|||
"Pink": "ピンク",
|
||||
"Required": "必須",
|
||||
"Select status pages...": "ステータスページを選択してください…",
|
||||
"webhookAdditionalHeadersDesc": "Webhook で送信される追加ヘッダーを設定します。",
|
||||
"webhookAdditionalHeadersDesc": "Webhook で送信される追加ヘッダーを設定します。各ヘッダーは JSON の key/value 型で定義されている必要があります。",
|
||||
"Webhook URL": "Webhook URL",
|
||||
"Application Token": "Application Token",
|
||||
"steamApiKeyDescription": "Steam Game Server を監視するためには、Steam Web-API キーが必要です。APIキーの登録はこちらから行えます。 ",
|
||||
|
@ -315,8 +315,8 @@
|
|||
"signedInDisp": "{0}としてログイン中",
|
||||
"RadiusSecret": "Radius シークレット",
|
||||
"RadiusSecretDescription": "クライアントとサーバー間の共有シークレット",
|
||||
"RadiusCalledStationId": "Called Station Id",
|
||||
"RadiusCallingStationId": "Calling Station Id",
|
||||
"RadiusCalledStationId": "Called-Station-Id",
|
||||
"RadiusCallingStationId": "Calling-Station-Id",
|
||||
"Subject:": "サブジェクト:",
|
||||
"trustProxyDescription": "ヘッダー「X-Forwarded-*」を信頼します。Uptime Kumaがリバースプロキシの中にあり、正しいクライアントIPを取得する場合は、有効化してください。",
|
||||
"Home Assistant URL": "ホームアシスタントURL",
|
||||
|
@ -454,14 +454,14 @@
|
|||
"maintenanceStatus-unknown": "不明",
|
||||
"Server Timezone": "サーバータイムゾーン",
|
||||
"IconUrl": "アイコンURL",
|
||||
"Enable DNS Cache": "DNSキャッシュを有効にする",
|
||||
"Enable DNS Cache": "(非推奨) HTTP(s)の監視に対しDNSキャッシュを有効にする",
|
||||
"Enable": "有効",
|
||||
"Disable": "無効",
|
||||
"Schedule Maintenance": "スケジュールメンテナンス",
|
||||
"loadingError": "データを取得できません。後でもう一度試してください。",
|
||||
"uninstall": "アンインストール",
|
||||
"installing": "インストール中",
|
||||
"Ignore TLS Error": "TLSエラーを無視",
|
||||
"Ignore TLS Error": "TLS エラーを無視",
|
||||
"smtpCC": "CC",
|
||||
"secureOptionTLS": "TLS (465)",
|
||||
"smtpBCC": "BCC",
|
||||
|
@ -555,7 +555,7 @@
|
|||
"A list of Notification Services can be found in Home Assistant under \"Developer Tools > Services\" search for \"notification\" to find your device/phone name.": "通知サービスの一覧からデバイス名を見つけるには、Home Assistantの「開発者ツール > サービス」から通知で検索してください。",
|
||||
"Notify Channel": "通知チャンネル",
|
||||
"Icon Emoji": "絵文字アイコン",
|
||||
"setupDatabaseChooseDatabase": "どのデータベースを利用しますか?",
|
||||
"setupDatabaseChooseDatabase": "どのデータベースを使いたいですか?",
|
||||
"setupDatabaseEmbeddedMariaDB": "何も設定する必要はありません。この Docker イメージは設定済みの MariaDB が組み込まれています。Uptime Kuma はこのデータベースに unix ソケットを通じて接続します。",
|
||||
"setupDatabaseSQLite": "小規模な開発のために推奨される小さなデータベースファイルです。v2.0.0 以前は、Uptime Kuma は SQLite を標準のデータベースとして使用していました。",
|
||||
"tailscalePingWarning": "Tailscale Ping モニターを使用するためには、Uptime Kuma を Docker を利用せずインストールし、そのうえで Tailscale Client をサーバーにインストールしてください。",
|
||||
|
@ -567,5 +567,111 @@
|
|||
"selectedMonitorCount": "選択済み: {0}",
|
||||
"Long-Lived Access Token": "長期アクセストークン",
|
||||
"Invert Keyword": "「含まない」キーワード",
|
||||
"Expected Value": "期待値"
|
||||
"Expected Value": "期待値",
|
||||
"dataRetentionTimeError": "保持期間は 0 か、それ以上である必要があります",
|
||||
"settingUpDatabaseMSG": "データベースをセットアップしています。しばらくお待ちください。",
|
||||
"Check/Uncheck": "選択あり/なし",
|
||||
"Alphanumeric (recommended)": "半角英数字(推奨)",
|
||||
"GrafanaOncallUrl": "Grafana オンコール URL",
|
||||
"Command": "コマンド",
|
||||
"Browser Screenshot": "ブラウザ・スクリーンショット",
|
||||
"mongodbCommandDescription": "データベースに対して MongoDB コマンドを実行します。使用できるコマンドについては {documentation} を参照ください",
|
||||
"2faDisabled": "二要素認証 (2FA) を無効にしました。",
|
||||
"Telephone number": "電話番号",
|
||||
"remoteBrowsersDescription": "リモートブラウザは Chromium を実行するための代替手段です。ローカルで実行する代わりに、browserless.io のようなサービスや、独自のサービスに接続します",
|
||||
"max 15 digits": "最大15桁",
|
||||
"What is a Remote Browser?": "リモート・ブラウザとは何ですか?",
|
||||
"Remote Browsers": "リモート・ブラウザ",
|
||||
"Remote Browser": "リモート・ブラウザ",
|
||||
"wayToGetSevenIOApiKey": "app.seven.io > developer > api key > 緑色の追加ボタンの下にあるダッシュボードをご覧ください",
|
||||
"senderSevenIO": "番号もしくは名前を送信しています",
|
||||
"receiverSevenIO": "番号を受信しています",
|
||||
"Search monitored sites": "監視対象サイトの検索",
|
||||
"liquidIntroduction": "Templatability は Liquid テンプレート言語を利用します。使い方は {0} をご覧ください。以下は利用可能な変数です:",
|
||||
"templateMsg": "通知の内容",
|
||||
"templateHeartbeatJSON": "死活監視 (heartbeat) を記述している JSON オブジェクト",
|
||||
"templateMonitorJSON": "監視対象を記述している JSON オブジェクト",
|
||||
"Remove the expiry notification": "有効期限の通知日を削除する",
|
||||
"authInvalidToken": "無効なトークンです。",
|
||||
"authIncorrectCreds": "無効なユーザー名かパスワードです。",
|
||||
"self-hosted container": "セルフ・ホストのコンテナ",
|
||||
"useRemoteBrowser": "リモート・ブラウザを使う",
|
||||
"Add a Remote Browser": "リモート・ブラウザを追加する",
|
||||
"remoteBrowserToggle": "デフォルトでは、Chromium は Uptime Kuma コンテナ内で実行されます。このスイッチを切り替えることで、リモート・ブラウザを使用できます。",
|
||||
"Remote Browser not found!": "リモート・ブラウザがありません!",
|
||||
"Select message type": "メッセージ・タイプを選択してください",
|
||||
"Send to channel": "チャンネルに送信",
|
||||
"Create new forum post": "新規フォーラムに投稿",
|
||||
"needSignalAPI": "REST API に対応したシグナリング用のクライアントが必要です。",
|
||||
"aboutIconURL": "「アイコン URL」に画像へのリンクを指定することで、デフォルトのプロフィール画像を上書きすることができます。「アイコン絵文字」が指定されている場合は使用されません。",
|
||||
"octopushAPIKey": "コントロールパネルの HTTP API 認証情報にある「API キー」",
|
||||
"2faEnabled": "二要素認証 (2FA) を有効にしました。",
|
||||
"successAdded": "正常に追加されました。",
|
||||
"successResumed": "正常に再開しました。",
|
||||
"2faAlreadyEnabled": "二要素認証 (2FA) は、すでに有効になっています。",
|
||||
"successDeleted": "正常に削除しました。",
|
||||
"successPaused": "正常に一時停止しました。",
|
||||
"successAuthChangePassword": "正常にパスワードを更新しました。",
|
||||
"successEdited": "正常に編集できました。",
|
||||
"successBackupRestored": "正常にバックアップから復元しました。",
|
||||
"successEnabled": "正常に有効化しました。",
|
||||
"tagNotFound": "タグが見つかりませんでした。",
|
||||
"foundChromiumVersion": "Chromium/Chrome を見つけました。 Version: {0}",
|
||||
"Select": "選択",
|
||||
"Add a new expiry notification day": "有効期限の通知日を追加する",
|
||||
"emailCustomSubject": "カスタム・トピック",
|
||||
"emailTemplateMsg": "通知の内容",
|
||||
"To Email": "Email へ",
|
||||
"emailTemplateLimitedToUpDownNotification": "UP/DOWN 死活監視 (heartbeat) の場合のみ有効",
|
||||
"deleteRemoteBrowserMessage": "本当にこのリモートブラウザをすべての監視から削除しますか?",
|
||||
"Refresh Interval": "更新間隔",
|
||||
"Refresh Interval Description": "ステータスページは{0}秒ごとに全面更新されます",
|
||||
"wayToGetLineChannelToken": "まず {0} にアクセスし、プロバイダーとチャネル(Messaging API)を作成します。次に、上記のメニューからアクセストークンとチャネルのユーザー ID を取得します。",
|
||||
"aboutMattermostChannelName": "「チャンネル名」フィールドにチャンネル名を入力することで、Webhook から送信されるデフォルトのチャンネル名を上書きできます。これは Mattermost の Webhook 設定で有効にしておく必要があります。例 #other-channel",
|
||||
"Saved.": "保存しました。",
|
||||
"Either enter the hostname of the server you want to connect to or localhost if you intend to use a locally configured mail transfer agent": "接続するサーバーのホスト名を入力するか、{local_mta} を使用する場合は {localhost} を入力します",
|
||||
"e.g. {discordThreadID}": "例 {discordThreadID}",
|
||||
"postToExistingThread": "既存のスレッド/フォーラムに投稿",
|
||||
"forumPostName": "投稿先のフォーラム名",
|
||||
"threadForumPostID": "投稿先のスレッド/フォーラム ID",
|
||||
"whatHappensAtForumPost": "新規フォーラム投稿。既存の投稿がある場合は投稿しません。既存の投稿の中に投稿を作成するには「{option}」を使用してください",
|
||||
"emailTemplateHeartbeatJSON": "死活監視 (heartbeat) を記述している JSON オブジェクト",
|
||||
"leave blank for default subject": "空の場合はデフォルトの件名を使用します",
|
||||
"cellsyntSplitLongMessages": "長文メッセージは最大 6 つに分割してください。153 x 6 = 918文字です。",
|
||||
"Allow Long SMS": "ロングコード SMS を許可する",
|
||||
"cellsyntDestination": "受信者の電話番号。00 に続けて国番号を付ける国際形式である必要があります。たとえば、英国の番号 07920 110 000 の場合は 00447920110000(合計17桁まで)になります。HTTP リクエストごとに指定できる受信者数は、カンマ区切りで最大 25,000 件です。",
|
||||
"emailTemplateMonitorJSON": "監視対象を記述している JSON オブジェクト",
|
||||
"successDisabled": "正常に無効化しました。",
|
||||
"Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "長期アクセストークンは、プロフィール名(左下)をクリックし、下にスクロールして「トークンを作成」をクリックすると作成できます。 ",
|
||||
"Effective Date Range": "有効範囲(オプション)",
|
||||
"DateTime Range": "日付範囲",
|
||||
"Maintenance Time Window of a Day": "今日のメンテナンス時間のウィンドウ",
|
||||
"octopushLogin": "コントロールパネルの HTTP API 認証情報にある「ログイン」",
|
||||
"notificationRegional": "特殊地域",
|
||||
"pushOthers": "その他",
|
||||
"Host URL": "ホストURL",
|
||||
"enableNSCD": "NSCD (Name Service Cache Daemon) を有効にして、すべての DNS クエリをキャッシュする",
|
||||
"successKeywordExplanation": "成功としてカウントされる MQTT のキーワード",
|
||||
"DockerHostRequired": "この監視の Docker ホストを設定してください。",
|
||||
"noDockerHostMsg": "利用できません。Docker ホストを先にセットアップしてください。",
|
||||
"styleElapsedTime": "経過時間を監視バーの下に表示",
|
||||
"styleElapsedTimeShowNoLine": "表示(改行なし)",
|
||||
"styleElapsedTimeShowWithLine": "表示(改行あり)",
|
||||
"lastDay": "最終日",
|
||||
"lastDay1": "月末",
|
||||
"lastDay2": "月末の 2 日前",
|
||||
"lastDay3": "月末の 3 日前",
|
||||
"lastDay4": "月末の 4 日前",
|
||||
"chromeExecutable": "Chrome/Chromium の実行ファイル",
|
||||
"chromeExecutableDescription": "Docker を使用しており、Chromium がインストールされていない場合、インストールしてからテスト結果の表示するのに数分かかることがあります。1 GB の容量を必要とします。",
|
||||
"jsonQueryDescription": "応答に対して json クエリを実行し、値が期待されたものであるか確認します(戻り値は比較の際文字列に変換されます)。 クエリ言語のドキュメントについては、{0}を参照してください。{1}にプレイグラウンドがあります。",
|
||||
"leave blank for default body": "デフォルトの本文を使用するには空のままにしてください",
|
||||
"apprise": "Apprise (50以上の通知サービスをサポートしています)",
|
||||
"Apprise URL": "AppriseのURL",
|
||||
"emailTemplateHostnameOrURL": "ホスト名またはURL",
|
||||
"emailCustomisableContent": "カスタマイズ可能なコンテンツ",
|
||||
"emailCustomBody": "カスタム本文",
|
||||
"emailTemplateServiceName": "サービス名",
|
||||
"smtpLiquidIntroduction": "次の 2 つのテンプレート・フィールドは Liquid テンプレート言語で記述できます。これらの使い方は {0} にあります。以下は利用可能な変数です:",
|
||||
"emailTemplateStatus": "ステータス"
|
||||
}
|
||||
|
|
222
src/lang/lt.json
Normal file
222
src/lang/lt.json
Normal file
|
@ -0,0 +1,222 @@
|
|||
{
|
||||
"languageName": "Lietuvių",
|
||||
"settingUpDatabaseMSG": "Vyksta duomenų bazės sąranka. Tai gali užtrukti, būkite kantrūs.",
|
||||
"Settings": "Parametrai",
|
||||
"Dashboard": "Skydelis",
|
||||
"Help": "Pagalba",
|
||||
"New Update": "Naujas atnaujinimas",
|
||||
"Language": "Kalba",
|
||||
"Appearance": "Išvaizda",
|
||||
"General": "Bendra",
|
||||
"Version": "Versija",
|
||||
"List": "Sąrašas",
|
||||
"Home": "Pradžia",
|
||||
"Add": "Pridėti",
|
||||
"Add New Monitor": "Įtraukti naują monitorių",
|
||||
"Quick Stats": "Greita statistika",
|
||||
"Up": "Veikia",
|
||||
"statusMaintenance": "Profilaktika",
|
||||
"Unknown": "Nežinomas",
|
||||
"Cannot connect to the socket server": "Nepavyksta prisijungti prie lizdo serverio",
|
||||
"Reconnecting...": "Atkuriamas ryšys...",
|
||||
"General Monitor Type": "Bendrasis monitoriaus tipas",
|
||||
"Passive Monitor Type": "Pasyvusis monitoriaus tipas",
|
||||
"markdownSupported": "Palaikoma Markdown sintaksė",
|
||||
"Pause": "Pauzė",
|
||||
"Name": "Pavadinimas",
|
||||
"Status": "Būsena",
|
||||
"DateTime": "DataLaikas",
|
||||
"Message": "Žinutė",
|
||||
"Resume": "Tęsti",
|
||||
"Edit": "Redaguoti",
|
||||
"Delete": "Ištrinti",
|
||||
"Current": "Dabartinis",
|
||||
"Uptime": "Veikimo trukmė",
|
||||
"Cert Exp.": "Sertifikato baig.",
|
||||
"day": "diena | dienų",
|
||||
"-day": "-dienų",
|
||||
"hour": "valanda",
|
||||
"-hour": "-valandos",
|
||||
"Ping": "Ping",
|
||||
"Monitor Type": "Monitoriaus tipas",
|
||||
"Keyword": "Raktažodis",
|
||||
"Invert Keyword": "Invertuoti raktažodį",
|
||||
"Json Query": "Json užklausa",
|
||||
"Friendly Name": "Draugiškas pavadinimas",
|
||||
"URL": "URL adresas",
|
||||
"locally configured mail transfer agent": "lokaliai sukonfigūruotas pašto perdavimo agentas",
|
||||
"Request Timeout": "Užklausos trukmės limitas",
|
||||
"timeoutAfter": "Trukmės limitas po {0} sekundžių",
|
||||
"Retries": "Pakartotiniai bandymai",
|
||||
"Heartbeat Retry Interval": "Širdies ritmo kartojimo intervalas",
|
||||
"Advanced": "Išplėstinis",
|
||||
"checkEverySecond": "Tikrinti kas {0} sekundžių",
|
||||
"retryCheckEverySecond": "Pakartotinai bandyti kas {0} sekundžių",
|
||||
"resendEveryXTimes": "Pakartotinai siųsti kas {0} kartų",
|
||||
"resendDisabled": "Pakartotinis siuntimas neįgalintas",
|
||||
"ignoreTLSError": "TLS/SSL klaidų ignoravimas HTTPS svetainėse",
|
||||
"ignoreTLSErrorGeneral": "Ignoruoti TLS/SSL ryšio klaidą",
|
||||
"upsideDownModeDescription": "Apversti būseną aukštyn kojomis. Jei paslauga yra pasiekiama, ji NEVEIKIA.",
|
||||
"Upside Down Mode": "Apverstas režimas",
|
||||
"Max. Redirects": "Maksimalūs peradresavimai",
|
||||
"pushOptionalParams": "Neprivalomi parametrai: {0}",
|
||||
"pushViewCode": "Kaip naudoti Push monitorių? (Žiūrėti kodą)",
|
||||
"pushOthers": "Kiti",
|
||||
"programmingLanguages": "Programavimo kalbos",
|
||||
"Save": "Išsaugoti",
|
||||
"Notifications": "Pranešimai",
|
||||
"Not available, please setup.": "Negalima, prašome nustatyti.",
|
||||
"Setup Notification": "Nustatyti pranešimą",
|
||||
"Light": "Šviesus",
|
||||
"Theme - Heartbeat Bar": "Tema - Heartbeat Bar",
|
||||
"styleElapsedTime": "Praėjęs laikas po širdies ritmo juosta",
|
||||
"styleElapsedTimeShowNoLine": "Rodyti (be linijos)",
|
||||
"styleElapsedTimeShowWithLine": "Rodyti (su linija)",
|
||||
"Normal": "Normalus",
|
||||
"Bottom": "Apačia",
|
||||
"None": "Nėra",
|
||||
"Search Engine Visibility": "Matomumas paieškos sistemose",
|
||||
"Allow indexing": "Leisti indeksuoti",
|
||||
"Change Password": "Keisti slaptažodį",
|
||||
"Current Password": "Dabartinis slaptažodis",
|
||||
"New Password": "Naujas slaptažodis",
|
||||
"Repeat New Password": "Naujas slaptažodis",
|
||||
"Disable Auth": "Išjungti autentifikavimą",
|
||||
"Enable Auth": "Įgalinti autentifikavimą",
|
||||
"disableauth.message1": "Ar tikrai norite {disableAuth}?",
|
||||
"setupDatabaseChooseDatabase": "Kurią duomenų bazę norėtumėte naudoti?",
|
||||
"setupDatabaseMariaDB": "Prisijunkite prie išorinės MariaDB duomenų bazės. Reikia nustatyti duomenų bazės prisijungimo informaciją.",
|
||||
"setupDatabaseSQLite": "Paprastas duomenų bazės failas, rekomenduojamas nedidelės apimties diegimui. Iki versijos 2.0.0, Uptime Kuma, kaip numatytąją duomenų bazę, naudojo SQLite.",
|
||||
"setupDatabaseEmbeddedMariaDB": "Nereikia nieko nustatyti. Šiame Docker atvaizde MariaDB įdiegta ir sukonfigūruota automatiškai. Uptime Kuma prie šios duomenų bazės prisijungs per Unix lizdą.",
|
||||
"dbName": "Duomenų bazės pavadinimas",
|
||||
"Theme": "Tema",
|
||||
"Game": "Žaidimo",
|
||||
"Primary Base URL": "Pradinis bazės URL adresas",
|
||||
"Check Update On GitHub": "Patikrinkite atnaujinimą GitHub",
|
||||
"Down": "Neveikia",
|
||||
"Pending": "Laukiama",
|
||||
"Maintenance": "Profilaktikos darbai",
|
||||
"Specific Monitor Type": "Konkretus monitoriaus tipas",
|
||||
"pauseDashboardHome": "Pauzė",
|
||||
"No important events": "Jokių svarbių įvykių",
|
||||
"Monitor": "Monitorius | Monitorių",
|
||||
"Response": "Atsakymas",
|
||||
"Expected Value": "Tikėtina vertė",
|
||||
"Hostname": "Pagrindinio kompiuterio vardas",
|
||||
"Host URL": "Priimančiojo URL adresas",
|
||||
"Either enter the hostname of the server you want to connect to or localhost if you intend to use a locally configured mail transfer agent": "Įveskite serverio, prie kurio norite prisijungti, pagrindinį kompiuterio vardą arba {localhost}, jei ketinate naudoti {local_mta}",
|
||||
"Port": "Prievadas",
|
||||
"Heartbeat Interval": "Širdies ritmo intervalas",
|
||||
"Resend Notification if Down X times consecutively": "Pakartotinis pranešimo siuntimas, jei neveikia X kartų iš eilės",
|
||||
"retriesDescription": "Didžiausias pakartotinių bandymų skaičius, kol paslauga pažymėta, kaip neveikianti, ir išsiųstas pranešimas",
|
||||
"maxRedirectDescription": "Didžiausias galimų nukreipimų skaičius. Nustatykite 0, jei norite išjungti nukreipimus.",
|
||||
"Accepted Status Codes": "Priimtini būsenos kodai",
|
||||
"Push URL": "Push URL adresas",
|
||||
"needPushEvery": "Šį URL adresą turėtumėte iškviesti kas {0} sekundžių.",
|
||||
"Dark": "Tamsus",
|
||||
"Auto": "Automatinis",
|
||||
"Timezone": "Laiko juosta",
|
||||
"Discourage search engines from indexing site": "Neskatinti paieškos sistemų indeksuoti svetainę",
|
||||
"Update Password": "Atnaujinti slaptažodį",
|
||||
"disable authentication": "išjungti autentifikavimą",
|
||||
"where you intend to implement third-party authentication": "kur ketinate įdiegti trečiosios šalies autentifikavimą",
|
||||
"Please use this option carefully!": "Atsargiai naudokite šią parinktį!",
|
||||
"Logout": "Atsijungti",
|
||||
"Leave": "Palikti",
|
||||
"Confirm": "Patvirtinti",
|
||||
"Yes": "Taip",
|
||||
"No": "Ne",
|
||||
"Password": "Slaptažodis",
|
||||
"Remember me": "Prisiminti mane",
|
||||
"Login": "Prisijungti",
|
||||
"add one": "pridėti vieną",
|
||||
"Notification Type": "Pranešimo tipas",
|
||||
"Email": "El. paštas",
|
||||
"Test": "Testas",
|
||||
"Resolver Server": "Resolverio serveris",
|
||||
"Last Result": "Paskutinis rezultatas",
|
||||
"Create your admin account": "Susikurkite administratoriaus paskyrą",
|
||||
"Repeat Password": "Pakartoti slaptažodį",
|
||||
"Export Backup": "Eksportuoti atsarginę kopiją",
|
||||
"Export": "Eksportuoti",
|
||||
"Import": "Importuoti",
|
||||
"respTime": "Reakcijos laikas (ms)",
|
||||
"notAvailableShort": "N/A",
|
||||
"Create": "Sukurti",
|
||||
"Clear Data": "Išvalyti duomenis",
|
||||
"Events": "Įvykiai",
|
||||
"Heartbeats": "Širdies ritmas",
|
||||
"Affected Monitors": "Paveikti monitoriai",
|
||||
"Pick Affected Monitors...": "Pasirinkite paveiktus monitorius…",
|
||||
"All Status Pages": "Visi būsenų puslapiai",
|
||||
"alertWrongFileType": "Pasirinkite JSON failą.",
|
||||
"Clear all statistics": "Išvalyti visą statistiką",
|
||||
"Overwrite": "Perrašyti",
|
||||
"Options": "Parinktys",
|
||||
"Keep both": "Palikti abu",
|
||||
"Verify Token": "Paikrinti žetoną",
|
||||
"Enable 2FA": "Įgalinti 2FA",
|
||||
"Disable 2FA": "Išjungti 2FA",
|
||||
"2FA Settings": "2FA nustatymai",
|
||||
"filterActive": "Aktyvus",
|
||||
"filterActivePaused": "Sustabdytas",
|
||||
"Active": "Aktyvus",
|
||||
"Inactive": "Neaktyvus",
|
||||
"Token": "Žetonas",
|
||||
"Show URI": "Rodyti URL",
|
||||
"Tags": "Žymės",
|
||||
"Add New Tag": "Pridėti naują žymą",
|
||||
"Add New below or Select...": "Pridėti naują žemiau arba pasirinkti…",
|
||||
"color": "Spalva",
|
||||
"value (optional)": "reikšmė (neprivaloma)",
|
||||
"Gray": "Pilka",
|
||||
"Red": "Raudona",
|
||||
"Green": "Žalia",
|
||||
"Blue": "Mėlyna",
|
||||
"Indigo": "Indigo",
|
||||
"Purple": "Violetinė",
|
||||
"Custom": "Pasirinktina",
|
||||
"Search monitored sites": "Stebimų svetainių paieška",
|
||||
"Avg. Ping": "Vidutinis Ping",
|
||||
"Entry Page": "Puslapis įėjus",
|
||||
"statusPageRefreshIn": "Perkrauti per: {0}",
|
||||
"No Services": "Paslaugų nėra",
|
||||
"All Systems Operational": "Visos sistemos veikia",
|
||||
"Partially Degraded Service": "Dalinai pablogėjusi paslauga",
|
||||
"Degraded Service": "Pablogėjusi paslauga",
|
||||
"Add Group": "Pridėti grupę",
|
||||
"Add a monitor": "Pridėti monitorių",
|
||||
"Edit Status Page": "Redaguoti būsenos puslapį",
|
||||
"Status Page": "Būsenos puslapis",
|
||||
"here": "čia",
|
||||
"Required": "Privaloma",
|
||||
"Post URL": "Post URL",
|
||||
"Content Type": "Turinio tipas",
|
||||
"disableauth.message2": "Jis skirtas scenarijams {intendThirdPartyAuth} priešais Uptime Kuma, pavyzdžiui, Cloudflare Access, Authelia ar kitus autentifikavimo mechanizmus.",
|
||||
"I understand, please disable": "Aš suprantu, prašau išjungti",
|
||||
"Username": "Vartotojo vardas",
|
||||
"No Monitors, please": "Jokių monitorių, prašau",
|
||||
"Certificate Info": "Sertifikato informacija",
|
||||
"Resource Record Type": "Išteklių įrašo tipas",
|
||||
"Import Backup": "Importuoti atsarginę kopiją",
|
||||
"Default enabled": "Pagal nutylėjimą įgalinta",
|
||||
"Apply on all existing monitors": "Taikyti visiems esamiems monitoriams",
|
||||
"Auto Get": "Automatinis gavimas",
|
||||
"Schedule maintenance": "Profilaktikos tvarkaraštis",
|
||||
"Start of maintenance": "Profilaktikos pradžia",
|
||||
"Select status pages...": "Pasirinkite būsenos puslapius…",
|
||||
"alertNoFile": "Pasirinkite importuojamą failą.",
|
||||
"Skip existing": "Praleisti esamus",
|
||||
"Setup 2FA": "Nustatyti 2FA",
|
||||
"Two Factor Authentication": "Dviejų veiksnių autentifikavimas",
|
||||
"Tag with this name already exist.": "Žyma tokiu pavadinimu jau egzistuoja.",
|
||||
"Tag with this value already exist.": "Žyma tokia reikšme jau egzistuoja.",
|
||||
"Orange": "Oranžinė",
|
||||
"Pink": "Rožinė",
|
||||
"Search...": "Ieškoti…",
|
||||
"Avg. Response": "Vidutinė reakcija",
|
||||
"statusPageNothing": "Čia nieko nėra, pridėkite grupę arba monitorių.",
|
||||
"Go to Dashboard": "Eiti į skydelį",
|
||||
"Status Pages": "Būsenos puslapiai",
|
||||
"defaultNotificationName": "Mano {notification} įspėjimas nr. ({number})"
|
||||
}
|
1
src/lang/lzh.json
Normal file
1
src/lang/lzh.json
Normal file
|
@ -0,0 +1 @@
|
|||
{}
|
92
src/lang/mk.json
Normal file
92
src/lang/mk.json
Normal file
|
@ -0,0 +1,92 @@
|
|||
{
|
||||
"setupDatabaseMariaDB": "Поврзи надворешна MariaDB датабаза. Мораш да ги внесеш податоците за најава од датабазата.",
|
||||
"dbName": "Име од Датабаза",
|
||||
"Settings": "Поставки",
|
||||
"Dashboard": "Контролна табла",
|
||||
"Help": "Помош",
|
||||
"Language": "Јазик",
|
||||
"Theme": "Тема",
|
||||
"General": "Генерално",
|
||||
"Game": "Игра",
|
||||
"Version": "Верзија",
|
||||
"Check Update On GitHub": "Проверете го ажурирањето на GitHub",
|
||||
"List": "Листа",
|
||||
"Home": "Дома",
|
||||
"Add": "Додај",
|
||||
"Add New Monitor": "Додај нов монитор",
|
||||
"Quick Stats": "Брзи статистика",
|
||||
"Up": "Горе",
|
||||
"Down": "Доле",
|
||||
"Pending": "Во очекување",
|
||||
"Maintenance": "Одржување",
|
||||
"Unknown": "Непознато",
|
||||
"Reconnecting...": "Повторно поврзување...",
|
||||
"Specific Monitor Type": "Специфичен тип на монитор",
|
||||
"pauseDashboardHome": "Пауза",
|
||||
"Name": "Име",
|
||||
"Status": "Статус",
|
||||
"Message": "Порака",
|
||||
"No important events": "Нема важни настани",
|
||||
"Resume": "Продолжи",
|
||||
"Edit": "Измени",
|
||||
"Current": "Сегашен",
|
||||
"Cert Exp.": "Истек Серт.",
|
||||
"Monitor": "Монитор | Монитори",
|
||||
"day": "ден | денови",
|
||||
"-day": "-ден",
|
||||
"hour": "час",
|
||||
"-hour": "-час",
|
||||
"Ping": "Пинг",
|
||||
"Monitor Type": "Мониторски тип",
|
||||
"Invert Keyword": "Инвертирајте клучни зборови",
|
||||
"Expected Value": "Очекувана Вредност",
|
||||
"Json Query": "Json Query",
|
||||
"URL": "УРЛ",
|
||||
"Hostname": "Hostname",
|
||||
"Port": "Порт",
|
||||
"Heartbeat Interval": "Интервал на отчукување на срцето",
|
||||
"Request Timeout": "Истекување на барањето",
|
||||
"Retries": "Обиди",
|
||||
"Advanced": "Напредно",
|
||||
"checkEverySecond": "Провери секоја {0} секунда",
|
||||
"retryCheckEverySecond": "Повтори секои {0} секунди",
|
||||
"resendEveryXTimes": "Препрати на секои {0} пати",
|
||||
"resendDisabled": "Повторно испраќање е оневозможено",
|
||||
"ignoreTLSError": "Игнорирај TLS/SSL errors за HTTPS websites",
|
||||
"ignoreTLSErrorGeneral": "Игнорирај TLS/SSL грешки при конекција",
|
||||
"upsideDownModeDescription": "Превртете го статусот наопаку. Ако услугата е достапна, таа е ДОЛУ.",
|
||||
"Upside Down Mode": "Режим наопаку",
|
||||
"Max. Redirects": "Макс. Пренасочувања",
|
||||
"Accepted Status Codes": "Прифатливи Статус Кодови",
|
||||
"Push URL": "Push URL",
|
||||
"pushOptionalParams": "Опционални параметри: {0}",
|
||||
"languageName": "Македонски",
|
||||
"setupDatabaseChooseDatabase": "Која датабаза би сакал да ја користиш?",
|
||||
"setupDatabaseEmbeddedMariaDB": "Не треба ништо да поставувате. Оваа докер-слика автоматски ја вгради и конфигурираше MariaDB за вас. Uptime Kuma ќе се поврзе со оваа база на податоци преку уникс приклучок.",
|
||||
"setupDatabaseSQLite": "Едноставна датотека со база на податоци, препорачана за распоредувања во мали размери. Пред верзијата 2.0.0, Uptime Kuma користеше SQLite како стандардна база на податоци.",
|
||||
"settingUpDatabaseMSG": "Ја сетираме Датабазата за вас, ве молам почекајте.",
|
||||
"New Update": "Ново ажурирање",
|
||||
"Appearance": "Изглед",
|
||||
"Primary Base URL": "Примарна основна URL",
|
||||
"statusMaintenance": "Одржување",
|
||||
"Cannot connect to the socket server": "Не може да се поврзе со сокет-серверот",
|
||||
"General Monitor Type": "Општ тип на монитор",
|
||||
"Passive Monitor Type": "Пасивен тип на монитор",
|
||||
"markdownSupported": "Поддржана е синтакса Markdown",
|
||||
"Pause": "Пауза",
|
||||
"DateTime": "ДатаВреме",
|
||||
"Delete": "Избриши",
|
||||
"Uptime": "Време на работа",
|
||||
"Response": "Одговор",
|
||||
"Keyword": "Клучен збор",
|
||||
"Friendly Name": "Пријателско Име",
|
||||
"Host URL": "УРЛ адреса од сервер",
|
||||
"locally configured mail transfer agent": "локално конфигуриран агент за пренос на пошта",
|
||||
"Either enter the hostname of the server you want to connect to or localhost if you intend to use a locally configured mail transfer agent": "Или внесете го името на домаќинот на серверот на кој сакате да се поврзете или {localhost} ако имате намера да користите {local_mta}",
|
||||
"timeoutAfter": "Истекување на времето по {0} секунди",
|
||||
"Heartbeat Retry Interval": "Интервал на повторно обиди за отчукување на срцето",
|
||||
"Resend Notification if Down X times consecutively": "Повторно испратете известување ако е долу X пати последователно",
|
||||
"retriesDescription": "Максимално повторување пред услугата да биде означена како неискористена и да се испрати известување",
|
||||
"maxRedirectDescription": "Максимален број пренасочувања што треба да се следат. Поставете на 0 за да ги оневозможите пренасочувањата.",
|
||||
"needPushEvery": "Треба да ја повикувате оваа URL на секои {0} секунди."
|
||||
}
|
|
@ -135,7 +135,7 @@
|
|||
"alertNoFile": "Selecteer een bestand om te importeren.",
|
||||
"alertWrongFileType": "Selecteer een JSON-bestand.",
|
||||
"Verify Token": "Controleer token",
|
||||
"Setup 2FA": "2FA instellingen",
|
||||
"Setup 2FA": "Stel 2FA in",
|
||||
"Enable 2FA": "Schakel 2FA in",
|
||||
"Disable 2FA": "Schakel 2FA uit",
|
||||
"2FA Settings": "2FA-instellingen",
|
||||
|
@ -411,7 +411,7 @@
|
|||
"Retry": "Opnieuw",
|
||||
"Topic": "Onderwerp",
|
||||
"WeCom Bot Key": "WeCom Bot Key",
|
||||
"Setup Proxy": "Proxy instellen",
|
||||
"Setup Proxy": "Stel Proxy in",
|
||||
"Proxy Protocol": "Proxy Protocol",
|
||||
"Proxy Server": "Proxy Server",
|
||||
"Proxy server has authentication": "Proxy server heeft authenticatie",
|
||||
|
@ -535,7 +535,7 @@
|
|||
"Connection Type": "Verbindingstype",
|
||||
"Docker Daemon": "Docker Daemon",
|
||||
"Trust Proxy": "Trust Proxy",
|
||||
"Setup Docker Host": "Docker Host instellen",
|
||||
"Setup Docker Host": "Stel Docker Host in",
|
||||
"tcp": "TCP / HTTP",
|
||||
"Optional": "Optioneel",
|
||||
"socket": "Socket",
|
||||
|
@ -804,7 +804,6 @@
|
|||
"Reconnecting...": "Opnieuw verbinden...",
|
||||
"Expected Value": "Verwachte waarde",
|
||||
"Json Query": "Json zoekopdracht",
|
||||
"jsonQueryDescription": "Voer een JSON-query uit op de respons en controleer de verwachte waarde (De retourwaarde wordt omgezet naar een string voor vergelijking). Bekijk {0} voor de documentatie over de querytaal. Een speelplaats is beschikbaar {1}.",
|
||||
"pushViewCode": "Hoe gebruik je Push monitor?(View Code)",
|
||||
"setupDatabaseChooseDatabase": "Welke database wil je gebruiken?",
|
||||
"setupDatabaseEmbeddedMariaDB": "Je hoeft niks in te stellen. Dit docker image heeft een ingebouwde en geconfigureerde MariaDB instantie. Uptime Kuma verbindt met deze database via een unix socket.",
|
||||
|
@ -855,7 +854,7 @@
|
|||
"Check/Uncheck": "Vink/Ontvink",
|
||||
"tailscalePingWarning": "Om de Tailscale Ping-monitor te kunnen gebruiken, moet u Uptime Kuma zonder Docker installeren en ook de Tailscale-client op uw server installeren.",
|
||||
"selectedMonitorCount": "Geselecteerd: {0}",
|
||||
"wayToGetFlashDutyKey": "Ga naar Kanaal -> (Kies een kanaal) -> Integraties -> Voeg een nieuwe integratie toe, voeg een 'Custom Event' toe om een push-adres te verkrijgen, kopieer de integratiesleutel in het adres-veld. Voor meer informatie, zie",
|
||||
"wayToGetFlashDutyKey": "Ga naar Kanaal -> (Kies een kanaal) -> Integraties -> Voeg een nieuwe integratie toe, voeg een nieuwe 'Uptime Kuma' toe om een push-adres te verkrijgen, kopieer de integratiesleutel in het adres-veld. Voor meer informatie, zie",
|
||||
"gamedigGuessPortDescription": "De poort die wordt gebruikt door het Valve Server Query Protocol kan verschillen van de clientpoort. Probeer dit als de monitor geen verbinding kan maken met je server.",
|
||||
"authUserInactiveOrDeleted": "De gebruiker is inactief of verwijderd.",
|
||||
"authInvalidToken": "Ongeldig token.",
|
||||
|
@ -957,5 +956,40 @@
|
|||
"Mentioning": "Vermelden",
|
||||
"receiverSevenIO": "Nummer ontvangen",
|
||||
"receiverInfoSevenIO": "Als het ontvangende nummer zich niet in Duitsland bevindt, moet u de landcode vóór het nummer zetten (bijvoorbeeld voor de landcode 1 uit de VS gebruikt u 117612121212 in plaats van 017612121212)",
|
||||
"apiKeySevenIO": "SevenIO API Sleutel"
|
||||
"apiKeySevenIO": "SevenIO API Sleutel",
|
||||
"wayToGetThreemaGateway": "Je kunt je registreren voor Threema Gateway {0}.",
|
||||
"threemaRecipient": "Ontvanger",
|
||||
"threemaRecipientType": "Type ontvanger",
|
||||
"threemaRecipientTypeIdentity": "Threema-ID",
|
||||
"threemaRecipientTypeIdentityFormat": "8 Karakters",
|
||||
"threemaRecipientTypePhone": "Telefoonnummer",
|
||||
"threemaRecipientTypePhoneFormat": "E.164, zonder voorgaande +",
|
||||
"threemaSenderIdentityFormat": "8 karakters, begint normaliter met een *",
|
||||
"smspartnerPhoneNumber": "Telefoonnummer(s)",
|
||||
"smspartnerSenderNameInfo": "Moet tussen 3..=11 reguliere karakters zijn",
|
||||
"Bitrix24 Webhook URL": "Bitrix24 Webhook URL",
|
||||
"wayToGetBitrix24Webhook": "Je kunt een webhook maken door de stappen bij {0} te volgen",
|
||||
"bitrix24SupportUserID": "Voer je gebruikers ID van Bitrix25 in. Je kunt dit achterhalen uit de link naar je gebruikersprofiel.",
|
||||
"apiKeysDisabledMsg": "API-Sleutels zijn uitgeschakeld omdat authenticatie niet is ingeschakeld.",
|
||||
"smspartnerApiurl": "Je kunt je API-sleutel vinden in je dashboard bij {0}",
|
||||
"smspartnerPhoneNumberHelptext": "Het nummer moet in het internationale format {0}, {1} zijn. Meerdere nummers moeten gescheiden zijn door {2}",
|
||||
"Command": "Commando",
|
||||
"mongodbCommandDescription": "Draai een MongoDB commando tegen de database. Voor meer informatie over beschikbare commando's, raadpleeg de {documentation}",
|
||||
"threemaBasicModeInfo": "Notitie: Deze integratie gebruikt Threema Gateway in de basis modus (server gebaseerde versleuteling). Meer details vind je hier {0}.",
|
||||
"Select message type": "Selecteer bericht type",
|
||||
"Send to channel": "Stuur naar kanaal",
|
||||
"Create new forum post": "Maak nieuw forum bericht",
|
||||
"Refresh Interval": "Ververs interval",
|
||||
"Refresh Interval Description": "De status pagina zal elke {0} seconden een volledige website verversing doen",
|
||||
"ignoreTLSErrorGeneral": "Negeer TLS/SSL-fout voor verbinding",
|
||||
"forumPostName": "Forum bericht naam",
|
||||
"threadForumPostID": "Thread / Forum bericht ID",
|
||||
"e.g. {discordThreadID}": "bijv. {discordThreadID}",
|
||||
"whatHappensAtForumPost": "Maak een nieuw forum bericht. Dit stuurt GEEN berichten in bestaande forum berichten. Om naar een bestaand forum een nieuw bericht te sturen, gebruik \"{option}\"",
|
||||
"postToExistingThread": "Plaats op bestaande thread / forum bericht",
|
||||
"wayToGetDiscordThreadId": "Verkrijgen van een thread / forum bericht id is vergelijkbaar met het verkrijgen van een kanaal id. Lees meer over het verkrijgen van ids {0}",
|
||||
"threemaRecipientTypeEmail": "E-mailadres",
|
||||
"threemaSenderIdentity": "Gateway ID",
|
||||
"threemaApiAuthenticationSecret": "Gateway ID Secret",
|
||||
"smspartnerSenderName": "SMS afzender naam"
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue