Merge branch 'master' into feature-fix-5120

This commit is contained in:
Mohit Nagaraj 2024-10-18 08:08:29 +05:30 committed by GitHub
commit 6d25d6d772
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 1237 additions and 100 deletions

988
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -154,6 +154,7 @@
"@fortawesome/vue-fontawesome": "~3.0.0-5", "@fortawesome/vue-fontawesome": "~3.0.0-5",
"@playwright/test": "~1.39.0", "@playwright/test": "~1.39.0",
"@popperjs/core": "~2.10.2", "@popperjs/core": "~2.10.2",
"@testcontainers/hivemq": "^10.13.1",
"@types/bootstrap": "~5.1.9", "@types/bootstrap": "~5.1.9",
"@types/node": "^20.8.6", "@types/node": "^20.8.6",
"@typescript-eslint/eslint-plugin": "^6.7.5", "@typescript-eslint/eslint-plugin": "^6.7.5",
@ -189,6 +190,7 @@
"stylelint-config-standard": "~25.0.0", "stylelint-config-standard": "~25.0.0",
"terser": "~5.15.0", "terser": "~5.15.0",
"test": "~3.3.0", "test": "~3.3.0",
"testcontainers": "^10.13.1",
"typescript": "~4.4.4", "typescript": "~4.4.4",
"v-pagination-3": "~0.1.7", "v-pagination-3": "~0.1.7",
"vite": "~5.2.8", "vite": "~5.2.8",

View file

@ -1508,10 +1508,8 @@ class Monitor extends BeanModel {
return await R.getAll(` return await R.getAll(`
SELECT monitor_notification.monitor_id, monitor_notification.notification_id SELECT monitor_notification.monitor_id, monitor_notification.notification_id
FROM monitor_notification FROM monitor_notification
WHERE monitor_notification.monitor_id IN (?) WHERE monitor_notification.monitor_id IN (${monitorIDs.map((_) => "?").join(",")})
`, [ `, monitorIDs);
monitorIDs,
]);
} }
/** /**
@ -1521,13 +1519,11 @@ class Monitor extends BeanModel {
*/ */
static async getMonitorTag(monitorIDs) { static async getMonitorTag(monitorIDs) {
return await R.getAll(` return await R.getAll(`
SELECT monitor_tag.monitor_id, tag.name, tag.color SELECT monitor_tag.monitor_id, monitor_tag.tag_id, tag.name, tag.color
FROM monitor_tag FROM monitor_tag
JOIN tag ON monitor_tag.tag_id = tag.id JOIN tag ON monitor_tag.tag_id = tag.id
WHERE monitor_tag.monitor_id IN (?) WHERE monitor_tag.monitor_id IN (${monitorIDs.map((_) => "?").join(",")})
`, [ `, monitorIDs);
monitorIDs,
]);
} }
/** /**
@ -1567,6 +1563,7 @@ class Monitor extends BeanModel {
tagsMap.set(row.monitor_id, []); tagsMap.set(row.monitor_id, []);
} }
tagsMap.get(row.monitor_id).push({ tagsMap.get(row.monitor_id).push({
tag_id: row.tag_id,
name: row.name, name: row.name,
color: row.color color: row.color
}); });

View file

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

View file

@ -32,7 +32,7 @@ class Slack extends NotificationProvider {
* @param {object} monitorJSON The monitor config * @param {object} monitorJSON The monitor config
* @returns {Array} The relevant action objects * @returns {Array} The relevant action objects
*/ */
static buildActions(baseURL, monitorJSON) { buildActions(baseURL, monitorJSON) {
const actions = []; const actions = [];
if (baseURL) { if (baseURL) {
@ -73,7 +73,7 @@ class Slack extends NotificationProvider {
* @param {string} msg The message body * @param {string} msg The message body
* @returns {Array<object>} The rich content blocks for the Slack message * @returns {Array<object>} The rich content blocks for the Slack message
*/ */
static buildBlocks(baseURL, monitorJSON, heartbeatJSON, title, msg) { buildBlocks(baseURL, monitorJSON, heartbeatJSON, title, msg) {
//create an array to dynamically add blocks //create an array to dynamically add blocks
const blocks = []; const blocks = [];
@ -150,7 +150,7 @@ class Slack extends NotificationProvider {
data.attachments.push( data.attachments.push(
{ {
"color": (heartbeatJSON["status"] === UP) ? "#2eb886" : "#e01e5a", "color": (heartbeatJSON["status"] === UP) ? "#2eb886" : "#e01e5a",
"blocks": Slack.buildBlocks(baseURL, monitorJSON, heartbeatJSON, title, msg), "blocks": this.buildBlocks(baseURL, monitorJSON, heartbeatJSON, title, msg),
} }
); );
} else { } else {

View file

@ -68,6 +68,7 @@ const GtxMessaging = require("./notification-providers/gtx-messaging");
const Cellsynt = require("./notification-providers/cellsynt"); const Cellsynt = require("./notification-providers/cellsynt");
const Onesender = require("./notification-providers/onesender"); const Onesender = require("./notification-providers/onesender");
const Wpush = require("./notification-providers/wpush"); const Wpush = require("./notification-providers/wpush");
const SendGrid = require("./notification-providers/send-grid");
class Notification { class Notification {
@ -153,6 +154,7 @@ class Notification {
new GtxMessaging(), new GtxMessaging(),
new Cellsynt(), new Cellsynt(),
new Wpush(), new Wpush(),
new SendGrid()
]; ];
for (let item of list) { for (let item of list) {
if (! item.name) { if (! item.name) {

View file

@ -165,6 +165,7 @@ export default {
"whapi": "WhatsApp (Whapi)", "whapi": "WhatsApp (Whapi)",
"gtxmessaging": "GtxMessaging", "gtxmessaging": "GtxMessaging",
"Cellsynt": "Cellsynt", "Cellsynt": "Cellsynt",
"SendGrid": "SendGrid"
}; };
// Put notifications here if it's not supported in most regions or its documentation is not in English // Put notifications here if it's not supported in most regions or its documentation is not in English

View file

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

View file

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

View file

@ -66,6 +66,7 @@ import Whapi from "./Whapi.vue";
import Cellsynt from "./Cellsynt.vue"; import Cellsynt from "./Cellsynt.vue";
import WPush from "./WPush.vue"; import WPush from "./WPush.vue";
import SIGNL4 from "./SIGNL4.vue"; import SIGNL4 from "./SIGNL4.vue";
import SendGrid from "./SendGrid.vue";
/** /**
* Manage all notification form. * Manage all notification form.
@ -139,7 +140,8 @@ const NotificationFormList = {
"whapi": Whapi, "whapi": Whapi,
"gtxmessaging": GtxMessaging, "gtxmessaging": GtxMessaging,
"Cellsynt": Cellsynt, "Cellsynt": Cellsynt,
"WPush": WPush "WPush": WPush,
"SendGrid": SendGrid,
}; };
export default NotificationFormList; export default NotificationFormList;

1
src/lang/ab.json Normal file
View file

@ -0,0 +1 @@
{}

View file

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

View file

@ -1,7 +1,7 @@
{ {
"languageName": "Deutsch (Schweiz)", "languageName": "Deutsch (Schweiz)",
"Settings": "Einstellungen", "Settings": "Einstellungen",
"Dashboard": "Dashboard", "Dashboard": "Überblick",
"New Update": "Update verfügbar", "New Update": "Update verfügbar",
"Language": "Sprache", "Language": "Sprache",
"Appearance": "Erscheinungsbild", "Appearance": "Erscheinungsbild",
@ -1047,5 +1047,6 @@
"greater than": "mehr als", "greater than": "mehr als",
"less than or equal to": "kleiner als oder gleich", "less than or equal to": "kleiner als oder gleich",
"greater than or equal to": "grösser als oder gleich", "greater than or equal to": "grösser als oder gleich",
"record": "Eintrag" "record": "Eintrag",
"shrinkDatabaseDescriptionSqlite": "Datenbank {vacuum} für SQLite auslösen. {auto_vacuum} ist bereits aktiviert, aber dies defragmentiert die Datenbank nicht und packt auch nicht einzelne Datenbankseiten neu, wie es der Befehl {vacuum} tut."
} }

View file

@ -1,7 +1,7 @@
{ {
"languageName": "Deutsch", "languageName": "Deutsch",
"Settings": "Einstellungen", "Settings": "Einstellungen",
"Dashboard": "Dashboard", "Dashboard": "Überblick",
"New Update": "Aktualisierung verfügbar", "New Update": "Aktualisierung verfügbar",
"Language": "Sprache", "Language": "Sprache",
"Appearance": "Erscheinungsbild", "Appearance": "Erscheinungsbild",
@ -1050,5 +1050,6 @@
"less than": "weniger als", "less than": "weniger als",
"less than or equal to": "kleiner als oder gleich", "less than or equal to": "kleiner als oder gleich",
"greater than or equal to": "größer als oder gleich", "greater than or equal to": "größer als oder gleich",
"record": "Eintrag" "record": "Eintrag",
"shrinkDatabaseDescriptionSqlite": "Datenbank {vacuum} für SQLite auslösen. {auto_vacuum} ist bereits aktiviert, aber dies defragmentiert die Datenbank nicht und packt auch nicht einzelne Datenbankseiten neu, wie es der Befehl {vacuum} tut."
} }

View file

@ -1052,5 +1052,7 @@
"Can be found on:": "Can be found on: {0}", "Can be found on:": "Can be found on: {0}",
"The phone number of the recipient in E.164 format.": "The phone number of the recipient in E.164 format.", "The phone number of the recipient in E.164 format.": "The phone number of the recipient in E.164 format.",
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.":"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.", "Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.":"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.",
"nextCheckIn": "Next in " "nextCheckIn": "Next in ",
"SendGrid API Key": "SendGrid API Key",
"Separate multiple email addresses with commas": "Separate multiple email addresses with commas"
} }

1
src/lang/enm.json Normal file
View file

@ -0,0 +1 @@
{}

View file

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

View file

@ -1050,5 +1050,6 @@
"greater than": "supérieur à", "greater than": "supérieur à",
"less than or equal to": "inférieur ou égal à", "less than or equal to": "inférieur ou égal à",
"greater than or equal to": "supérieur ou égal à", "greater than or equal to": "supérieur ou égal à",
"record": "enregistrer" "record": "enregistrer",
"shrinkDatabaseDescriptionSqlite": "Déclencher la commande {vacuum} pour la base de données SQLite. {auto_vacuum} est déjà activé, mais cela ne défragmente pas la base de données ni ne réorganise les pages individuelles de la base de données de la même manière que la commande {vacuum}."
} }

View file

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

View file

@ -1045,5 +1045,6 @@
"New Group": "Grup Baru", "New Group": "Grup Baru",
"Group Name": "Nama Grup", "Group Name": "Nama Grup",
"OAuth2: Client Credentials": "OAuth2: Kredensial Klien", "OAuth2: Client Credentials": "OAuth2: Kredensial Klien",
"Authentication Method": "Metode Autentikasi" "Authentication Method": "Metode Autentikasi",
"shrinkDatabaseDescriptionSqlite": "Memicu pangkalan data {vacuum} untuk SQLite. {auto_vacuum} sudah diaktifkan, tetapi tidak mendefragmentasi pangkalan data atau mengemas ulang halaman individual dari pangkalan data seperti yang dilakukan oleh perintah {vacuum}."
} }

View file

@ -989,5 +989,9 @@
"wayToGetThreemaGateway": "Możesz zarejestrować się w Threema Gateway {0}.", "wayToGetThreemaGateway": "Możesz zarejestrować się w Threema Gateway {0}.",
"threemaSenderIdentityFormat": "8 znaków, zwykle zaczyna się od *", "threemaSenderIdentityFormat": "8 znaków, zwykle zaczyna się od *",
"threemaBasicModeInfo": "Uwaga: Ta integracja korzysta z Threema Gateway w trybie podstawowym (szyfrowanie po stronie serwera). Więcej szczegółów można znaleźć {0}.", "threemaBasicModeInfo": "Uwaga: Ta integracja korzysta z Threema Gateway w trybie podstawowym (szyfrowanie po stronie serwera). Więcej szczegółów można znaleźć {0}.",
"apiKeysDisabledMsg": "Klucze API są wyłączone, ponieważ wyłączone jest uwierzytelnianie." "apiKeysDisabledMsg": "Klucze API są wyłączone, ponieważ wyłączone jest uwierzytelnianie.",
"-year": "-rok",
"and": "i",
"now": "teraz",
"cacheBusterParam": "Dodaj parametr {0}"
} }

View file

@ -1050,5 +1050,6 @@
"less than": "daha küçük", "less than": "daha küçük",
"greater than or equal to": "büyük veya eşit", "greater than or equal to": "büyük veya eşit",
"record": "kayıt", "record": "kayıt",
"jsonQueryDescription": "JSON sorgusunu kullanarak sunucunun JSON yanıtından belirli verileri ayrıştırın ve çıkarın. JSON beklemiyorsanız ham yanıt için \"$\" sembolünü kullanın. Sonuç daha sonra metin olarak beklenen değerle karşılaştırılır. Belgeler için {0}'a bakın ve sorgularla denemeler yapmak için {1}'i kullanın." "jsonQueryDescription": "JSON sorgusunu kullanarak sunucunun JSON yanıtından belirli verileri ayrıştırın ve çıkarın. JSON beklemiyorsanız ham yanıt için \"$\" sembolünü kullanın. Sonuç daha sonra metin olarak beklenen değerle karşılaştırılır. Belgeler için {0}'a bakın ve sorgularla denemeler yapmak için {1}'i kullanın.",
"shrinkDatabaseDescriptionSqlite": "SQLite için {vacuum} veritabanını tetikle. {auto_vacuum} zaten etkin ancak bu, {vacuum} komutunun yaptığı gibi veritabanını birleştirmez veya tek tek veritabanı sayfalarını yeniden paketlemez."
} }

1
src/lang/ug.json Normal file
View file

@ -0,0 +1 @@
{}

View file

@ -1056,5 +1056,6 @@
"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": "запис",
"shrinkDatabaseDescriptionSqlite": "Запускає команду {vacuum} для бази даних SQLite. Команда {auto_vacuum} вже увімкнена, але вона не дефрагментує базу даних і не перепаковує окремі сторінки бази даних так, як це робить команда {vacuum}."
} }

View file

@ -1052,5 +1052,6 @@
"less than or equal to": "不多于", "less than or equal to": "不多于",
"greater than or equal to": "不少于", "greater than or equal to": "不少于",
"record": "记录", "record": "记录",
"jsonQueryDescription": "使用 JSON 查询解析并提取服务器 JSON 响应中的特定数据,或者,如果不期望得到 JSON 响应,则可使用 \"$\" 获取原始响应。然后将结果转为字符串并与期望值进行字符串比较。有关更多文档,请参阅 {0},亦可使用 {1} 来尝试查询。" "jsonQueryDescription": "使用 JSON 查询解析并提取服务器 JSON 响应中的特定数据,或者,如果不期望得到 JSON 响应,则可使用 \"$\" 获取原始响应。然后将结果转为字符串并与期望值进行字符串比较。有关更多文档,请参阅 {0},亦可使用 {1} 来尝试查询。",
"shrinkDatabaseDescriptionSqlite": "触发 SQLite 数据库的 {vacuum} 命令。{auto_vacuum} 已经启用,但它不会像 {vacuum} 命令那样对数据库进行碎片整理,也不会重新打包各个数据库页面。"
} }

View file

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