feat: Notification provider Teltonika RUTxxx SMS gateway (#6952)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Frank Elsinga <frank@elsinga.de>
This commit is contained in:
Unixerius
2026-02-16 23:32:51 +01:00
committed by GitHub
parent 28489803cf
commit 593435c3cb
6 changed files with 212 additions and 1 deletions

View File

@@ -0,0 +1,95 @@
// This notification provider is only compatible with Teltonika RMS >= 7.14.0 devices.
// See: https://community.teltonika.lt/t/implementation-of-read-only-system-files-and-mobile-and-i-o-post-get-service-removal-with-rutos-7-14/12470
// API reference https://developers.teltonika-networks.com/reference/rut241/7.19.4/v1.11.1/messages
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const https = require("https");
class Teltonika extends NotificationProvider {
name = "Teltonika";
/**
* @inheritdoc
*/
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
const okMsg = "Sent Successfully.";
// baseUrl is passed via the configuration screen.
// Must be limited to _just_ the full origin, so: proto://host:port.
// Everything else should be stripped. Best way to validate is to use URL().
let passedUrl = "";
try {
passedUrl = new URL(notification.teltonikaUrl);
} catch (error) {
throw Error("Invalid URL: " + notification.teltonikaUrl);
}
const baseUrl = passedUrl.origin;
const loginUrl = baseUrl + "/api/login";
const smsUrl = baseUrl + "/api/messages/actions/send";
// Teltonika SMS gateway supports a max of 160 chars for its messages.
const cleanMsg = msg.substring(0, 159);
// Starting communications with the API from here on out.
try {
let axiosConfig = {
headers: {
"Content-Type": "application/json",
"cache-control": "no-cache",
Accept: "application/json",
},
};
// In many cases, Teltonika routers will be setup using a self-signed
// certificate. Here we give them an option to disable certificate
// validation. It's not desirable, but sometimes the only option.
if (notification.teltonikaUnsafeTls) {
axiosConfig.httpsAgent = new https.Agent({
rejectUnauthorized: false, // Danger! Disables SSL verification
});
}
axiosConfig = this.getAxiosConfigWithProxy(axiosConfig);
// Logging in, to get an access token.
// API reference https://developers.teltonika-networks.com/reference/rut241/7.19.4/v1.11.1/authentication
// Teltonika's API access tokens expire in 5 minutes, so we always get a new one.
let loginData = {
username: notification.teltonikaUsername,
password: notification.teltonikaPassword,
};
let loginResp = await axios.post(loginUrl, loginData, axiosConfig);
if (loginResp.data.success !== true) {
throw Error("Login failed: " + loginResp.data.errors.error);
}
// Sending the SMS.
let smsData = {
data: {
modem: notification.teltonikaModem,
number: notification.teltonikaPhoneNumber,
message: cleanMsg,
},
};
axiosConfig.headers.Authorization = "Bearer " + loginResp.data.data.token;
let smsResp = await axios.post(smsUrl, smsData, axiosConfig);
if (smsResp.data.success !== true) {
throw Error("Api returned: ", smsResp.data.errors.error);
}
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
}
module.exports = Teltonika;

View File

@@ -60,6 +60,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 Teltonika = require("./notification-providers/teltonika");
const Threema = require("./notification-providers/threema");
const Twilio = require("./notification-providers/twilio");
const Splunk = require("./notification-providers/splunk");
@@ -165,6 +166,7 @@ class Notification {
new Teams(),
new TechulusPush(),
new Telegram(),
new Teltonika(),
new Threema(),
new Twilio(),
new Splunk(),

View File

@@ -272,6 +272,7 @@ export default {
SevenIO: "SevenIO",
SMSEagle: "SMSEagle",
SMSPartner: "SMS Partner",
Teltonika: this.$t("Teltonika SMS Gateway"),
twilio: "Twilio",
};

View File

@@ -0,0 +1,97 @@
<template>
<div class="mb-3">
<i18n-t keypath="teltonikaVersionWarning" tag="div" class="form-text"></i18n-t>
</div>
<div class="mb-3">
<label for="teltonika-url" class="form-label">{{ $t("teltonikaUrl") }}</label>
<input
id="teltonika-url"
v-model="$parent.notification.teltonikaUrl"
type="url"
minlength="10"
placeholder="192.168.100.1"
class="form-control"
required
/>
<i18n-t keypath="teltonikaUrlHelptext" tag="div" class="form-text">
<code>https://192.168.100.1</code>
<code>http://teltonika.domain.com:8080</code>
</i18n-t>
</div>
<div class="mb-3">
<div class="form-check form-switch">
<input v-model="$parent.notification.teltonikaUnsafeTls" class="form-check-input" type="checkbox" />
<label class="form-check-label">{{ $t("teltonikaUnsafeTls") }}</label>
</div>
<i18n-t keypath="teltonikaUnsafeTlsDescription" tag="div" class="form-text"></i18n-t>
</div>
<div class="mb-3">
<label for="teltonika-username" class="form-label">{{ $t("teltonikaUsername") }}</label>
<input
id="teltonika-username"
v-model="$parent.notification.teltonikaUsername"
type="text"
minlength="3"
maxlength="20"
pattern="^[a-zA-Z0-9]*$"
class="form-control"
required
/>
<i18n-t keypath="teltonikaUsernameHelptext" tag="div" class="form-text"></i18n-t>
</div>
<div class="mb-3">
<label for="teltonika-password" class="form-label">{{ $t("teltonikaPassword") }}</label>
<HiddenInput
id="teltonika-password"
v-model="$parent.notification.teltonikaPassword"
:required="true"
autocomplete="new-password"
></HiddenInput>
<i18n-t keypath="teltonikaPasswordHelptext" tag="div" class="form-text">
<code>https://192.168.100.1/system/admin/multiusers/users_configuration</code>
</i18n-t>
</div>
<div class="mb-3">
<label for="teltonika-modem" class="form-label">{{ $t("teltonikaModem") }}</label>
<input
id="teltonika-modem"
v-model="$parent.notification.teltonikaModem"
type="text"
minlength="3"
maxlength="5"
pattern="^[0-9]-[0-9]"
class="form-control"
required
/>
<i18n-t keypath="teltonikaModemHelptext" tag="div" class="form-text">
<code>1-1</code>
</i18n-t>
</div>
<div class="mb-3">
<label for="teltonika-phone-number" class="form-label">{{ $t("teltonikaPhoneNumber") }}</label>
<input
id="teltonika-phone-number"
v-model="$parent.notification.teltonikaPhoneNumber"
type="text"
minlength="10"
maxlength="20"
pattern="^[\d+,]+$"
class="form-control"
required
/>
<i18n-t keypath="teltonikaPhoneNumberHelptext" tag="div" class="form-text">
<code>+336xxxxxxxx</code>
<code>+496xxxxxxxx</code>
</i18n-t>
</div>
</template>
<script>
import HiddenInput from "../HiddenInput.vue";
export default {
components: {
HiddenInput,
},
};
</script>

View File

@@ -61,6 +61,7 @@ import STMP from "./SMTP.vue";
import Teams from "./Teams.vue";
import TechulusPush from "./TechulusPush.vue";
import Telegram from "./Telegram.vue";
import Teltonika from "./Teltonika.vue";
import Threema from "./Threema.vue";
import Twilio from "./Twilio.vue";
import Webhook from "./Webhook.vue";
@@ -152,6 +153,7 @@ const NotificationFormList = {
stackfield: Stackfield,
teams: Teams,
telegram: Telegram,
Teltonika: Teltonika,
threema: Threema,
twilio: Twilio,
Splunk: Splunk,

View File

@@ -1457,5 +1457,19 @@
"halopsa_field_timestamp": "Event timestamp in ISO 8601 format",
"halopsa_field_uptime_kuma_version": "Uptime Kuma version number",
"halopsa_id_usage_hint": "💡 Tip: Use monitor_id to reliably match alerts to tickets, and heartbeat_id to track event history",
"halopsa_setup_step5": "Configure runbook to use monitor_id for matching alerts to existing tickets"
"halopsa_setup_step5": "Configure runbook to use monitor_id for matching alerts to existing tickets",
"Teltonika SMS Gateway": "Teltonika SMS Gateway",
"teltonikaVersionWarning": "This notification provider requires that your Teltonika device runs RMS version 7.14.0, or higher.",
"teltonikaUrl": "Your Teltonika device URL",
"teltonikaUrlHelptext": "URL should be specified as full origin, e.g. {0}, or {1}.",
"teltonikaUnsafeTls": "Ignore certificate validation",
"teltonikaUnsafeTlsDescription": "Turning off TLS certificate validation opens you up to on-path (man-in-the-middle) attacks, potentially leading to data leaks and systems take-over. Do not turn off certificate validation unless you accept this attack vector. We recomend using LetsEncrypt with automatic renewal.",
"teltonikaUsername": "API username",
"teltonikaUsernameHelptext": "Recommendation: Create a separate account which is restricted to only sending SMS messages and enter its username here",
"teltonikaPassword": "API password",
"teltonikaPasswordHelptext": "You can define the API user's password in your Teltonika router, e.g. {0}",
"teltonikaModem": "Modem Id",
"teltonikaModemHelptext": "The id of the SMS modem, must be in the format {0}. Refer to https://developers.teltonika-networks.com/reference/ for guidance.",
"teltonikaPhoneNumber": "Phone number",
"teltonikaPhoneNumberHelptext": "The number must be in the international format {0}, {1}. Only one number is allowed."
}