mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-11-23 14:54:05 +00:00
feat: Add SNMP Monitor (#4717)
This commit is contained in:
commit
4aef6780ed
34 changed files with 364 additions and 66 deletions
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");
|
||||
});
|
||||
};
|
31
package-lock.json
generated
31
package-lock.json
generated
|
@ -54,6 +54,7 @@
|
|||
"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",
|
||||
|
@ -5525,6 +5526,11 @@
|
|||
"safer-buffer": "~2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/asn1-ber": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/asn1-ber/-/asn1-ber-1.2.2.tgz",
|
||||
"integrity": "sha512-CbNem/7hxrjSiOAOOTX4iZxu+0m3jiLqlsERQwwPM1IDR/22M8IPpA1VVndCLw5KtjRYyRODbvAEIfuTogNDng=="
|
||||
},
|
||||
"node_modules/astral-regex": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
|
||||
|
@ -11285,6 +11291,31 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/net-snmp": {
|
||||
"version": "3.11.2",
|
||||
"resolved": "https://registry.npmjs.org/net-snmp/-/net-snmp-3.11.2.tgz",
|
||||
"integrity": "sha512-QKy2JQHIBsSK344dUxYRZv7tU0ANk8f8fzKD/Mmq/cCxm/cPbtiT7009QEgxdViW/gGjqGIOiLHxkCc+JhZltg==",
|
||||
"dependencies": {
|
||||
"asn1-ber": "^1.2.1",
|
||||
"smart-buffer": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-abi": {
|
||||
"version": "3.62.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.62.0.tgz",
|
||||
"integrity": "sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==",
|
||||
"dependencies": {
|
||||
"semver": "^7.3.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/node-abort-controller": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz",
|
||||
"integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ=="
|
||||
},
|
||||
"node_modules/node-cloudflared-tunnel": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/node-cloudflared-tunnel/-/node-cloudflared-tunnel-1.0.10.tgz",
|
||||
|
|
|
@ -119,6 +119,7 @@
|
|||
"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",
|
||||
|
|
|
@ -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");
|
||||
|
@ -161,6 +160,9 @@ class Monitor extends BeanModel {
|
|||
kafkaProducerMessage: this.kafkaProducerMessage,
|
||||
screenshot,
|
||||
remote_browser: this.remote_browser,
|
||||
snmpOid: this.snmpOid,
|
||||
jsonPathOperator: this.jsonPathOperator,
|
||||
snmpVersion: this.snmpVersion,
|
||||
};
|
||||
|
||||
if (includeSensitiveData) {
|
||||
|
@ -598,25 +600,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") {
|
||||
|
|
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,
|
||||
};
|
|
@ -831,6 +831,10 @@ let needSetup = false;
|
|||
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.validate();
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -802,7 +802,6 @@
|
|||
"twilioApiKey": "API ключ (по избор)",
|
||||
"Expected Value": "Очаквана стойност",
|
||||
"Json Query": "Заявка тип JSON",
|
||||
"jsonQueryDescription": "Прави JSON заявка срещу отговора и проверява за очаквана стойност (Върнатата стойност ще бъде преобразувана в низ за сравнение). Разгледайте {0} за документация относно езика на заявката. Имате възможност да тествате {1}.",
|
||||
"Badge Duration (in hours)": "Времетраене на баджа (в часове)",
|
||||
"Badge Preview": "Преглед на баджа",
|
||||
"Notify Channel": "Канал за известяване",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
"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",
|
||||
|
@ -588,7 +588,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.",
|
||||
|
@ -943,6 +943,13 @@
|
|||
"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",
|
||||
"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",
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -759,7 +759,6 @@
|
|||
"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",
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)",
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -793,7 +793,6 @@
|
|||
"styleElapsedTime": "Czas, który upłynął pod paskiem bicia serca",
|
||||
"tailscalePingWarning": "Aby korzystać z monitora Tailscale Ping, należy zainstalować Uptime Kuma bez Dockera, a także zainstalować klienta Tailscale na serwerze.",
|
||||
"invertKeywordDescription": "Słowo kluczowe powinno być raczej nieobecne niż obecne.",
|
||||
"jsonQueryDescription": "Wykonaj zapytanie json względem odpowiedzi i sprawdź oczekiwaną wartość (wartość zwracana zostanie przekonwertowana na ciąg znaków do porównania). Sprawdź {0}, aby uzyskać dokumentację dotyczącą języka zapytań. Plac zabaw można znaleźć {1}.",
|
||||
"Server URL should not contain the nfty topic": "Adres URL serwera nie powinien zawierać tematu nfty",
|
||||
"Badge Duration (in hours)": "Czas trwania odznaki (w godzinach)",
|
||||
"Enter the list of brokers": "Wprowadź listę brokerów",
|
||||
|
|
|
@ -605,7 +605,6 @@
|
|||
"wayToGetLineChannelToken": "Primeiro acesse o {0}, crie um provedor e um canal (API de Mensagens), então você pode obter o token de acesso do canal e o ID do usuário nos itens de menu mencionados acima.",
|
||||
"aboutMattermostChannelName": "Você pode substituir o canal padrão para o qual o Webhook envia postagens, inserindo o nome do canal no campo \"Nome do Canal\". Isso precisa ser habilitado nas configurações do Webhook do Mattermost. Por exemplo: #outro-canal",
|
||||
"invertKeywordDescription": "Procure pela palavra-chave estar ausente em vez de presente.",
|
||||
"jsonQueryDescription": "Faça uma consulta JSON na resposta e verifique o valor esperado (o valor de retorno será convertido em uma string para comparação). Confira {0} para a documentação sobre a linguagem de consulta. Você pode encontrar um playground {1}.",
|
||||
"octopushTypePremium": "Premium (Rápido - recomendado para alertas)",
|
||||
"octopushTypeLowCost": "Baixo Custo (Lento - às vezes bloqueado pelo operador)",
|
||||
"octopushSMSSender": "Nome do Remetente de SMS: 3-11 caracteres alfanuméricos e espaço (a-zA-Z0-9)",
|
||||
|
|
|
@ -689,7 +689,6 @@
|
|||
"emailTemplateLimitedToUpDownNotification": "disponibil numai pentru heartbeat-uri UP/DOWN, altfel nul",
|
||||
"emailTemplateStatus": "Stare",
|
||||
"invertKeywordDescription": "Căutați după cuvântul cheie să fie absent și nu prezent.",
|
||||
"jsonQueryDescription": "Efectuați o interogare json după răspuns și verificați valoarea așteptată (valoarea returnată va fi convertită în șir pentru comparație). Consultați {0} pentru documentația despre limbajul de interogare. Un playground poate fi găsit {1}.",
|
||||
"goAlertInfo": "GoAlert este o aplicație open source pentru programarea apelurilor, escalări automate și notificări (cum ar fi SMS-uri sau apeluri vocale). Angajați automat persoana potrivită, în modul potrivit și la momentul potrivit! {0}",
|
||||
"goAlertIntegrationKeyInfo": "Obțineți cheia generică de integrare API pentru serviciu în formatul \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\" de obicei valoarea parametrului token al URL-ului copiat.",
|
||||
"SecretAccessKey": "Secret AccessKey",
|
||||
|
|
|
@ -812,7 +812,6 @@
|
|||
"AccessKey Id": "AccessKey Id",
|
||||
"Secret AccessKey": "Секретный ключ доступа",
|
||||
"Session Token": "Токен сессии",
|
||||
"jsonQueryDescription": "Выполните json-запрос к ответу и проверьте наличие ожидаемого значения (возвращаемое значение будет преобразовано в строку для сравнения). Посмотрите {0} для получения документации по языку запросов. A Потренироваться вы можете {1}.",
|
||||
"Notify Channel": "Канал оповещений",
|
||||
"aboutNotifyChannel": "Уведомление о канале вызовет настольное или мобильное уведомление для всех участников канала, независимо от того, установлена ли их доступность как активная или отсутствующая.",
|
||||
"Enter the list of brokers": "Введите список брокеров",
|
||||
|
|
|
@ -795,7 +795,6 @@
|
|||
"pushoverDesc1": "Nödprioritet (2) har 30 sekunders timeout mellan försök och löper ut efter 1 timme som standard.",
|
||||
"octopushTypePremium": "Premium (Snabb - rekommenderas för varningar)",
|
||||
"octopushTypeLowCost": "Låg kostnad (långsam - blockeras ibland av operatören)",
|
||||
"jsonQueryDescription": "Gör en json-förfrågan mot svaret och kontrollera det förväntade värdet (returvärde konverteras till en sträng för jämförelse). Se {0} för dokumentation angående frågespråket. En lekplats kan hittas här {1}.",
|
||||
"Check octopush prices": "Kontrollera octopush priser {0}.",
|
||||
"octopushSMSSender": "SMS avsändarnamn: 3-11 alfanumeriska tecken och mellanslag (a-zA-Z0-9)",
|
||||
"LunaSea Device ID": "LunaSea enhetsid",
|
||||
|
|
|
@ -794,7 +794,6 @@
|
|||
"webhookBodyPresetOption": "Ön ayar - {0}",
|
||||
"webhookBodyCustomOption": "Özel Gövde",
|
||||
"Request Body": "İstek Gövdesi",
|
||||
"jsonQueryDescription": "Yanıta karşı bir json sorgusu yapın ve beklenen değeri kontrol edin (Dönüş değeri, karşılaştırma için dizgeye dönüştürülür). Sorgu diliyle ilgili belgeler için {0}'a bakın. Bir oyun alanı {1} bulunabilir.",
|
||||
"twilioApiKey": "Api Anahtarı (isteğe bağlı)",
|
||||
"Expected Value": "Beklenen Değer",
|
||||
"Json Query": "Json Sorgusu",
|
||||
|
|
|
@ -802,7 +802,6 @@
|
|||
"Request Body": "Тіло запиту",
|
||||
"Badge Preview": "Попередній перегляд бейджа",
|
||||
"Badge Duration (in hours)": "Тривалість бейджа (у годинах)",
|
||||
"jsonQueryDescription": "Виконувати json-запит до відповіді та перевірити очікуване значення (значення, що повертається, буде перетворено в рядок для порівняння). Зверніться до {0} щоб ознайомитися з документацією про мову запитів. Навчальний майданчик можна знайти {1}.",
|
||||
"twilioApiKey": "Api ключ (необов'язково)",
|
||||
"Expected Value": "Очікуване значення",
|
||||
"Json Query": "Json-запит",
|
||||
|
|
|
@ -802,7 +802,6 @@
|
|||
"webhookCustomBodyDesc": "为 webhook 设定一个自定义 HTTP 请求体。可在模板内使用 {msg}、{heartbeat}和{monitor} 变量。",
|
||||
"webhookBodyPresetOption": "预设 - {0}",
|
||||
"Request Body": "请求体",
|
||||
"jsonQueryDescription": "对响应结果执行一次 JSON 查询,其返回值将会被转换为字符串,再与期望值进行比较。可访问 {0} 阅读 JSON 查询语言的文档,或在{1}测试查询语句。",
|
||||
"Json Query": "JSON 查询",
|
||||
"twilioApiKey": "API Key(可选)",
|
||||
"Expected Value": "预期值",
|
||||
|
|
|
@ -772,7 +772,6 @@
|
|||
"Check/Uncheck": "選中/取消選中",
|
||||
"tailscalePingWarning": "如需使用 Tailscale Ping 客戶端,您需要以非 docker 方式安裝 Uptime Kuma,並同時安裝 Tailscale 客戶端。",
|
||||
"invertKeywordDescription": "出現關鍵詞將令檢測結果設為失敗,而非成功。",
|
||||
"jsonQueryDescription": "對回應結果執行一次 JSON 查詢,其返回值將會被轉換為字串,再與期望值進行比較。可造訪{0}閱讀JSON 查詢語言的文件,或在{1}測試查詢語句。",
|
||||
"wayToGetKookGuildID": "在 Kook 設定中打開“開發者模式”,然後右鍵點選頻道可取得其 ID",
|
||||
"Notify Channel": "通知該頻道",
|
||||
"aboutNotifyChannel": "勾選“通知該頻道”,會令該頻道內所有成員都收到一條桌面端或移動端通知,無論其狀態是在線或離開。",
|
||||
|
|
|
@ -24,6 +24,9 @@
|
|||
<option value="ping">
|
||||
Ping
|
||||
</option>
|
||||
<option value="snmp">
|
||||
SNMP
|
||||
</option>
|
||||
<option value="keyword">
|
||||
HTTP(s) - {{ $t("Keyword") }}
|
||||
</option>
|
||||
|
@ -168,21 +171,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Json Query -->
|
||||
<div v-if="monitor.type === 'json-query'" class="my-3">
|
||||
<label for="jsonPath" class="form-label">{{ $t("Json Query") }}</label>
|
||||
<input id="jsonPath" v-model="monitor.jsonPath" type="text" class="form-control" required>
|
||||
|
||||
<i18n-t tag="div" class="form-text" keypath="jsonQueryDescription">
|
||||
<a href="https://jsonata.org/">jsonata.org</a>
|
||||
<a href="https://try.jsonata.org/">{{ $t('here') }}</a>
|
||||
</i18n-t>
|
||||
<br>
|
||||
|
||||
<label for="expectedValue" class="form-label">{{ $t("Expected Value") }}</label>
|
||||
<input id="expectedValue" v-model="monitor.expectedValue" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<!-- Game -->
|
||||
<!-- GameDig only -->
|
||||
<div v-if="monitor.type === 'gamedig'" class="my-3">
|
||||
|
@ -246,19 +234,79 @@
|
|||
</template>
|
||||
|
||||
<!-- Hostname -->
|
||||
<!-- TCP Port / Ping / DNS / Steam / MQTT / Radius / Tailscale Ping only -->
|
||||
<div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' || monitor.type === 'steam' || monitor.type === 'gamedig' ||monitor.type === 'mqtt' || monitor.type === 'radius' || monitor.type === 'tailscale-ping'" class="my-3">
|
||||
<!-- TCP Port / Ping / DNS / Steam / MQTT / Radius / Tailscale Ping / SNMP only -->
|
||||
<div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' || monitor.type === 'steam' || monitor.type === 'gamedig' || monitor.type === 'mqtt' || monitor.type === 'radius' || monitor.type === 'tailscale-ping' || monitor.type === 'snmp'" class="my-3">
|
||||
<label for="hostname" class="form-label">{{ $t("Hostname") }}</label>
|
||||
<input id="hostname" v-model="monitor.hostname" type="text" class="form-control" :pattern="`${monitor.type === 'mqtt' ? mqttIpOrHostnameRegexPattern : ipOrHostnameRegexPattern}`" required>
|
||||
</div>
|
||||
|
||||
<!-- Port -->
|
||||
<!-- For TCP Port / Steam / MQTT / Radius Type -->
|
||||
<div v-if="monitor.type === 'port' || monitor.type === 'steam' || monitor.type === 'gamedig' || monitor.type === 'mqtt' || monitor.type === 'radius'" class="my-3">
|
||||
<!-- For TCP Port / Steam / MQTT / Radius Type / SNMP -->
|
||||
<div v-if="monitor.type === 'port' || monitor.type === 'steam' || monitor.type === 'gamedig' || monitor.type === 'mqtt' || monitor.type === 'radius' || monitor.type === 'snmp'" class="my-3">
|
||||
<label for="port" class="form-label">{{ $t("Port") }}</label>
|
||||
<input id="port" v-model="monitor.port" type="number" class="form-control" required min="0" max="65535" step="1">
|
||||
</div>
|
||||
|
||||
<!-- SNMP Monitor Type -->
|
||||
<div v-if="monitor.type === 'snmp'" class="my-3">
|
||||
<label for="snmp_community_string" class="form-label">{{ $t("Community String") }}</label>
|
||||
<!-- TODO: Rename monitor.radiusPassword to monitor.password for general use -->
|
||||
<HiddenInput id="snmp_community_string" v-model="monitor.radiusPassword" autocomplete="false" required="true" placeholder="public"></HiddenInput>
|
||||
|
||||
<div class="form-text">{{ $t('snmpCommunityStringHelptext') }}</div>
|
||||
</div>
|
||||
|
||||
<div v-if="monitor.type === 'snmp'" class="my-3">
|
||||
<label for="snmp_oid" class="form-label">{{ $t("OID (Object Identifier)") }}</label>
|
||||
<input id="snmp_oid" v-model="monitor.snmpOid" :title="$t('Please enter a valid OID.') + ' ' + $t('Example:', ['1.3.6.1.4.1.9.6.1.101'])" type="text" class="form-control" pattern="^([0-2])((\.0)|(\.[1-9][0-9]*))*$" placeholder="1.3.6.1.4.1.9.6.1.101" required>
|
||||
<div class="form-text">{{ $t('snmpOIDHelptext') }} </div>
|
||||
</div>
|
||||
|
||||
<div v-if="monitor.type === 'snmp'" class="my-3">
|
||||
<label for="snmp_version" class="form-label">{{ $t("SNMP Version") }}</label>
|
||||
<select id="snmp_version" v-model="monitor.snmpVersion" class="form-select">
|
||||
<option value="1">
|
||||
SNMPv1
|
||||
</option>
|
||||
<option value="2c">
|
||||
SNMPv2c
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Json Query -->
|
||||
<!-- For Json Query / SNMP -->
|
||||
<div v-if="monitor.type === 'json-query' || monitor.type === 'snmp'" class="my-3">
|
||||
<div class="my-2">
|
||||
<label for="jsonPath" class="form-label mb-0">{{ $t("Json Query Expression") }}</label>
|
||||
<i18n-t tag="div" class="form-text mb-2" keypath="jsonQueryDescription">
|
||||
<a href="https://jsonata.org/">jsonata.org</a>
|
||||
<a href="https://try.jsonata.org/">{{ $t('playground') }}</a>
|
||||
</i18n-t>
|
||||
<input id="jsonPath" v-model="monitor.jsonPath" type="text" class="form-control" placeholder="$" required>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-start">
|
||||
<div class="me-2">
|
||||
<label for="json_path_operator" class="form-label">{{ $t("Condition") }}</label>
|
||||
<select id="json_path_operator" v-model="monitor.jsonPathOperator" class="form-select me-3" required>
|
||||
<option value=">">></option>
|
||||
<option value=">=">>=</option>
|
||||
<option value="<"><</option>
|
||||
<option value="<="><=</option>
|
||||
<option value="!=">!=</option>
|
||||
<option value="==">==</option>
|
||||
<option value="contains">contains</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<label for="expectedValue" class="form-label">{{ $t("Expected Value") }}</label>
|
||||
<input v-if="monitor.jsonPathOperator !== 'contains' && monitor.jsonPathOperator !== '==' && monitor.jsonPathOperator !== '!='" id="expectedValue" v-model="monitor.expectedValue" type="number" class="form-control" required step=".01">
|
||||
<input v-else id="expectedValue" v-model="monitor.expectedValue" type="text" class="form-control" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DNS Resolver Server -->
|
||||
<!-- For DNS Type -->
|
||||
<template v-if="monitor.type === 'dns'">
|
||||
|
@ -483,8 +531,8 @@
|
|||
<input id="retry-interval" v-model="monitor.retryInterval" type="number" class="form-control" required :min="minInterval" step="1">
|
||||
</div>
|
||||
|
||||
<!-- Timeout: HTTP / Keyword only -->
|
||||
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query'" class="my-3">
|
||||
<!-- Timeout: HTTP / Keyword / SNMP only -->
|
||||
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query' || monitor.type === 'snmp'" class="my-3">
|
||||
<label for="timeout" class="form-label">{{ $t("Request Timeout") }} ({{ $t("timeoutAfter", [ monitor.timeout || clampTimeout(monitor.interval) ]) }})</label>
|
||||
<input id="timeout" v-model="monitor.timeout" type="number" class="form-control" required min="0" step="0.1">
|
||||
</div>
|
||||
|
@ -946,7 +994,6 @@ const monitorDefaults = {
|
|||
retryInterval: 60,
|
||||
resendInterval: 0,
|
||||
maxretries: 0,
|
||||
timeout: 48,
|
||||
notificationIDList: {},
|
||||
ignoreTls: false,
|
||||
upsideDown: false,
|
||||
|
@ -1158,8 +1205,8 @@ message HealthCheckResponse {
|
|||
// Only groups, not itself, not a decendant
|
||||
result = result.filter(
|
||||
monitor => monitor.type === "group" &&
|
||||
monitor.id !== this.monitor.id &&
|
||||
!this.monitor.childrenIDs?.includes(monitor.id)
|
||||
monitor.id !== this.monitor.id &&
|
||||
!this.monitor.childrenIDs?.includes(monitor.id)
|
||||
);
|
||||
|
||||
// Filter result by active state, weight and alphabetical
|
||||
|
@ -1291,11 +1338,35 @@ message HealthCheckResponse {
|
|||
this.monitor.port = "53";
|
||||
} else if (this.monitor.type === "radius") {
|
||||
this.monitor.port = "1812";
|
||||
} else if (this.monitor.type === "snmp") {
|
||||
this.monitor.port = "161";
|
||||
} else {
|
||||
this.monitor.port = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.monitor.type === "snmp") {
|
||||
// snmp is not expected to be executed via the internet => we can choose a lower default timeout
|
||||
this.monitor.timeout = 5;
|
||||
} else {
|
||||
this.monitor.timeout = 48;
|
||||
}
|
||||
|
||||
// Set default SNMP version
|
||||
if (!this.monitor.snmpVersion) {
|
||||
this.monitor.snmpVersion = "2c";
|
||||
}
|
||||
|
||||
// Set default jsonPath
|
||||
if (!this.monitor.jsonPath) {
|
||||
this.monitor.jsonPath = "$";
|
||||
}
|
||||
|
||||
// Set default condition for for jsonPathOperator
|
||||
if (!this.monitor.jsonPathOperator) {
|
||||
this.monitor.jsonPathOperator = "==";
|
||||
}
|
||||
|
||||
// Get the game list from server
|
||||
if (this.monitor.type === "gamedig") {
|
||||
this.$root.getSocket().emit("getGameList", (res) => {
|
||||
|
|
59
src/util.js
59
src/util.js
|
@ -14,8 +14,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|||
var _a;
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.sleep = exports.flipStatus = exports.badgeConstants = exports.CONSOLE_STYLE_BgGray = exports.CONSOLE_STYLE_BgWhite = exports.CONSOLE_STYLE_BgCyan = exports.CONSOLE_STYLE_BgMagenta = exports.CONSOLE_STYLE_BgBlue = exports.CONSOLE_STYLE_BgYellow = exports.CONSOLE_STYLE_BgGreen = exports.CONSOLE_STYLE_BgRed = exports.CONSOLE_STYLE_BgBlack = exports.CONSOLE_STYLE_FgPink = exports.CONSOLE_STYLE_FgBrown = exports.CONSOLE_STYLE_FgViolet = exports.CONSOLE_STYLE_FgLightBlue = exports.CONSOLE_STYLE_FgLightGreen = exports.CONSOLE_STYLE_FgOrange = exports.CONSOLE_STYLE_FgGray = exports.CONSOLE_STYLE_FgWhite = exports.CONSOLE_STYLE_FgCyan = exports.CONSOLE_STYLE_FgMagenta = exports.CONSOLE_STYLE_FgBlue = exports.CONSOLE_STYLE_FgYellow = exports.CONSOLE_STYLE_FgGreen = exports.CONSOLE_STYLE_FgRed = exports.CONSOLE_STYLE_FgBlack = exports.CONSOLE_STYLE_Hidden = exports.CONSOLE_STYLE_Reverse = exports.CONSOLE_STYLE_Blink = exports.CONSOLE_STYLE_Underscore = exports.CONSOLE_STYLE_Dim = exports.CONSOLE_STYLE_Bright = exports.CONSOLE_STYLE_Reset = exports.MIN_INTERVAL_SECOND = exports.MAX_INTERVAL_SECOND = exports.SQL_DATETIME_FORMAT_WITHOUT_SECOND = exports.SQL_DATETIME_FORMAT = exports.SQL_DATE_FORMAT = exports.STATUS_PAGE_MAINTENANCE = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.MAINTENANCE = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isNode = exports.isDev = void 0;
|
||||
exports.evaluateJsonQuery = exports.intHash = exports.localToUTC = exports.utcToLocal = exports.utcToISODateTime = exports.isoToUTCDateTime = exports.parseTimeFromTimeObject = exports.parseTimeObject = exports.getMaintenanceRelativeURL = exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.log = exports.debug = exports.ucfirst = void 0;
|
||||
exports.intHash = exports.localToUTC = exports.utcToLocal = exports.utcToISODateTime = exports.isoToUTCDateTime = exports.parseTimeFromTimeObject = exports.parseTimeObject = exports.getMaintenanceRelativeURL = exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.log = exports.debug = exports.ucfirst = void 0;
|
||||
const dayjs_1 = __importDefault(require("dayjs"));
|
||||
const dayjs = require("dayjs");
|
||||
const jsonata = require("jsonata");
|
||||
exports.isDev = process.env.NODE_ENV === "development";
|
||||
exports.isNode = typeof process !== "undefined" && ((_a = process === null || process === void 0 ? void 0 : process.versions) === null || _a === void 0 ? void 0 : _a.node);
|
||||
exports.appName = "Uptime Kuma";
|
||||
|
@ -399,3 +402,59 @@ function intHash(str, length = 10) {
|
|||
return (hash % length + length) % length;
|
||||
}
|
||||
exports.intHash = intHash;
|
||||
async function evaluateJsonQuery(data, jsonPath, jsonPathOperator, expectedValue) {
|
||||
let response;
|
||||
try {
|
||||
response = JSON.parse(data);
|
||||
}
|
||||
catch (_a) {
|
||||
response = (typeof data === "object" || typeof data === "number") && !Buffer.isBuffer(data) ? data : data.toString();
|
||||
}
|
||||
try {
|
||||
response = (jsonPath) ? await jsonata(jsonPath).evaluate(response) : response;
|
||||
if (response === null || response === undefined) {
|
||||
throw new Error("Empty or undefined response. Check query syntax and response structure");
|
||||
}
|
||||
if (typeof response === "object" || response instanceof Date || typeof response === "function") {
|
||||
throw new Error(`The post-JSON query evaluated response from the server is of type ${typeof response}, which cannot be directly compared to the expected value`);
|
||||
}
|
||||
let jsonQueryExpression;
|
||||
switch (jsonPathOperator) {
|
||||
case ">":
|
||||
case ">=":
|
||||
case "<":
|
||||
case "<=":
|
||||
jsonQueryExpression = `$number($.value) ${jsonPathOperator} $number($.expected)`;
|
||||
break;
|
||||
case "!=":
|
||||
jsonQueryExpression = "$.value != $.expected";
|
||||
break;
|
||||
case "==":
|
||||
jsonQueryExpression = "$.value = $.expected";
|
||||
break;
|
||||
case "contains":
|
||||
jsonQueryExpression = "$contains($.value, $.expected)";
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Invalid condition ${jsonPathOperator}`);
|
||||
}
|
||||
const expression = jsonata(jsonQueryExpression);
|
||||
const status = await expression.evaluate({
|
||||
value: response.toString(),
|
||||
expected: expectedValue.toString()
|
||||
});
|
||||
if (status === undefined) {
|
||||
throw new Error("Query evaluation returned undefined. Check query syntax and the structure of the response data");
|
||||
}
|
||||
return {
|
||||
status,
|
||||
response
|
||||
};
|
||||
}
|
||||
catch (err) {
|
||||
response = JSON.stringify(response);
|
||||
response = (response && response.length > 50) ? `${response.substring(0, 100)}… (truncated)` : response;
|
||||
throw new Error(`Error evaluating JSON query: ${err.message}. Response from server was: ${response}`);
|
||||
}
|
||||
}
|
||||
exports.evaluateJsonQuery = evaluateJsonQuery;
|
||||
|
|
75
src/util.ts
75
src/util.ts
|
@ -17,6 +17,8 @@ import * as timezone from "dayjs/plugin/timezone";
|
|||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import * as utc from "dayjs/plugin/utc";
|
||||
|
||||
import * as jsonata from "jsonata";
|
||||
|
||||
export const isDev = process.env.NODE_ENV === "development";
|
||||
export const isNode = typeof process !== "undefined" && process?.versions?.node;
|
||||
export const appName = "Uptime Kuma";
|
||||
|
@ -643,3 +645,76 @@ export function intHash(str : string, length = 10) : number {
|
|||
return (hash % length + length) % length; // Ensure the result is non-negative
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a JSON query expression against the provided data.
|
||||
* @param data The data to evaluate the JSON query against.
|
||||
* @param jsonPath The JSON path or custom JSON query expression.
|
||||
* @param jsonPathOperator The operator to use for comparison.
|
||||
* @param expectedValue The expected value to compare against.
|
||||
* @returns An object containing the status and the evaluation result.
|
||||
* @throws Error if the evaluation returns undefined.
|
||||
*/
|
||||
export async function evaluateJsonQuery(data: any, jsonPath: string, jsonPathOperator: string, expectedValue: any): Promise<{ status: boolean; response: any }> {
|
||||
// Attempt to parse data as JSON; if unsuccessful, handle based on data type.
|
||||
let response: any;
|
||||
try {
|
||||
response = JSON.parse(data);
|
||||
} catch {
|
||||
response = (typeof data === "object" || typeof data === "number") && !Buffer.isBuffer(data) ? data : data.toString();
|
||||
}
|
||||
|
||||
try {
|
||||
// If a JSON path is provided, pre-evaluate the data using it.
|
||||
response = (jsonPath) ? await jsonata(jsonPath).evaluate(response) : response;
|
||||
|
||||
if (response === null || response === undefined) {
|
||||
throw new Error("Empty or undefined response. Check query syntax and response structure");
|
||||
}
|
||||
|
||||
if (typeof response === "object" || response instanceof Date || typeof response === "function") {
|
||||
throw new Error(`The post-JSON query evaluated response from the server is of type ${typeof response}, which cannot be directly compared to the expected value`);
|
||||
}
|
||||
|
||||
// Perform the comparison logic using the chosen operator
|
||||
let jsonQueryExpression;
|
||||
switch (jsonPathOperator) {
|
||||
case ">":
|
||||
case ">=":
|
||||
case "<":
|
||||
case "<=":
|
||||
jsonQueryExpression = `$number($.value) ${jsonPathOperator} $number($.expected)`;
|
||||
break;
|
||||
case "!=":
|
||||
jsonQueryExpression = "$.value != $.expected";
|
||||
break;
|
||||
case "==":
|
||||
jsonQueryExpression = "$.value = $.expected";
|
||||
break;
|
||||
case "contains":
|
||||
jsonQueryExpression = "$contains($.value, $.expected)";
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Invalid condition ${jsonPathOperator}`);
|
||||
}
|
||||
|
||||
// Evaluate the JSON Query Expression
|
||||
const expression = jsonata(jsonQueryExpression);
|
||||
const status = await expression.evaluate({
|
||||
value: response.toString(),
|
||||
expected: expectedValue.toString()
|
||||
});
|
||||
|
||||
if (status === undefined) {
|
||||
throw new Error("Query evaluation returned undefined. Check query syntax and the structure of the response data");
|
||||
}
|
||||
|
||||
return {
|
||||
status, // The evaluation of the json query
|
||||
response // The response from the server or result from initial json-query evaluation
|
||||
};
|
||||
} catch (err: any) {
|
||||
response = JSON.stringify(response); // Ensure the response is treated as a string for the console
|
||||
response = (response && response.length > 50) ? `${response.substring(0, 100)}… (truncated)` : response;// Truncate long responses to the console
|
||||
throw new Error(`Error evaluating JSON query: ${err.message}. Response from server was: ${response}`);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue