diff --git a/db/patch-add-radius-monitor.sql b/db/patch-add-radius-monitor.sql new file mode 100644 index 00000000..1fd5b44f --- /dev/null +++ b/db/patch-add-radius-monitor.sql @@ -0,0 +1,18 @@ +BEGIN TRANSACTION; + +ALTER TABLE monitor + ADD radius_username VARCHAR(255); + +ALTER TABLE monitor + ADD radius_password VARCHAR(255); + +ALTER TABLE monitor + ADD radius_calling_station_id VARCHAR(50); + +ALTER TABLE monitor + ADD radius_called_station_id VARCHAR(50); + +ALTER TABLE monitor + ADD radius_secret VARCHAR(255); + +COMMIT diff --git a/db/patch-monitor-add-resend-interval.sql b/db/patch-monitor-add-resend-interval.sql new file mode 100644 index 00000000..8e28bf69 --- /dev/null +++ b/db/patch-monitor-add-resend-interval.sql @@ -0,0 +1,10 @@ +-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. +BEGIN TRANSACTION; + +ALTER TABLE monitor + ADD resend_interval INTEGER default 0 not null; + +ALTER TABLE heartbeat + ADD down_count INTEGER default 0 not null; + +COMMIT; diff --git a/docker/alpine-base.dockerfile b/docker/alpine-base.dockerfile index cde65bb6..1d74de05 100644 --- a/docker/alpine-base.dockerfile +++ b/docker/alpine-base.dockerfile @@ -4,5 +4,5 @@ WORKDIR /app # Install apprise, iputils for non-root ping, setpriv RUN apk add --no-cache iputils setpriv dumb-init python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib && \ - pip3 --no-cache-dir install apprise==0.9.9 && \ + pip3 --no-cache-dir install apprise==1.0.0 && \ rm -rf /root/.cache diff --git a/docker/debian-base.dockerfile b/docker/debian-base.dockerfile index f90968a8..20bef3dd 100644 --- a/docker/debian-base.dockerfile +++ b/docker/debian-base.dockerfile @@ -11,7 +11,7 @@ WORKDIR /app RUN apt update && \ apt --yes --no-install-recommends install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \ sqlite3 iputils-ping util-linux dumb-init && \ - pip3 --no-cache-dir install apprise==0.9.9 && \ + pip3 --no-cache-dir install apprise==1.0.0 && \ rm -rf /var/lib/apt/lists/* && \ apt --yes autoremove diff --git a/package-lock.json b/package-lock.json index c297455c..902c3126 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "uptime-kuma", - "version": "1.17.1", + "version": "1.18.0-beta.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "uptime-kuma", - "version": "1.17.1", + "version": "1.18.0-beta.0", "license": "MIT", "dependencies": { "@fortawesome/fontawesome-svg-core": "~1.2.36", @@ -44,6 +44,7 @@ "mqtt": "^4.2.8", "mssql": "^8.1.0", "node-cloudflared-tunnel": "~1.0.9", + "node-radius-client": "^1.0.0", "nodemailer": "~6.6.5", "notp": "~2.0.3", "password-hash": "~1.2.2", @@ -8326,6 +8327,12 @@ "readable-stream": "^3.6.0" } }, + "node_modules/hoek": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.3.tgz", + "integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==", + "deprecated": "This module has moved and is now available at @hapi/hoek. Please update your dependencies as this version is no longer maintained an may contain bugs and security issues." + }, "node_modules/homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -9026,6 +9033,17 @@ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "devOptional": true }, + "node_modules/isemail": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz", + "integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==", + "dependencies": { + "punycode": "2.x.x" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -12272,6 +12290,32 @@ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", "dev": true }, + "node_modules/node-radius-client": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-radius-client/-/node-radius-client-1.0.0.tgz", + "integrity": "sha512-FkR9cMV5hNoX+kKDUTzuagvEixlLiaEJQ1/ywOdhahsihKrGDhVZmnCvmrCStA589MT3yuC/J2eKc6z68IGdBw==", + "dependencies": { + "joi": "^14.3.1", + "node-radius-utils": "^1.2.0", + "radius": "^1.1.4" + } + }, + "node_modules/node-radius-client/node_modules/joi": { + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/joi/-/joi-14.3.1.tgz", + "integrity": "sha512-LQDdM+pkOrpAn4Lp+neNIFV3axv1Vna3j38bisbQhETPMANYRbFJFUyOZcOClYvM/hppMhGWuKSFEK9vjrB+bQ==", + "deprecated": "This module has moved and is now available at @hapi/joi. Please update your dependencies as this version is no longer maintained an may contain bugs and security issues.", + "dependencies": { + "hoek": "6.x.x", + "isemail": "3.x.x", + "topo": "3.x.x" + } + }, + "node_modules/node-radius-utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/node-radius-utils/-/node-radius-utils-1.2.0.tgz", + "integrity": "sha512-i3Sf6khnenl0aXumo0whAlfPWTaBqHxEnVBBxpu3dZ7q69NkPPv71rvPjlDZ5wkeKCTNNUTECljerS5kcYQxRw==" + }, "node_modules/node-releases": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz", @@ -13579,6 +13623,14 @@ "node": ">=8" } }, + "node_modules/radius": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/radius/-/radius-1.1.4.tgz", + "integrity": "sha512-UWuzdF6xf3NpsXFZZmUEkxtEalDXj8hdmMXgbGzn7vOk6zXNsiIY2I6SJ1euHt7PTQuMoz2qDEJB+AfJDJgQYw==", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -15410,6 +15462,15 @@ "node": ">=0.6" } }, + "node_modules/topo": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.3.tgz", + "integrity": "sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ==", + "deprecated": "This module has moved and is now available at @hapi/topo. Please update your dependencies as this version is no longer maintained an may contain bugs and security issues.", + "dependencies": { + "hoek": "6.x.x" + } + }, "node_modules/toposort": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", @@ -22878,6 +22939,11 @@ "readable-stream": "^3.6.0" } }, + "hoek": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.3.tgz", + "integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==" + }, "homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -23360,6 +23426,14 @@ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "devOptional": true }, + "isemail": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz", + "integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==", + "requires": { + "punycode": "2.x.x" + } + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -25865,6 +25939,33 @@ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", "dev": true }, + "node-radius-client": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-radius-client/-/node-radius-client-1.0.0.tgz", + "integrity": "sha512-FkR9cMV5hNoX+kKDUTzuagvEixlLiaEJQ1/ywOdhahsihKrGDhVZmnCvmrCStA589MT3yuC/J2eKc6z68IGdBw==", + "requires": { + "joi": "^14.3.1", + "node-radius-utils": "^1.2.0", + "radius": "^1.1.4" + }, + "dependencies": { + "joi": { + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/joi/-/joi-14.3.1.tgz", + "integrity": "sha512-LQDdM+pkOrpAn4Lp+neNIFV3axv1Vna3j38bisbQhETPMANYRbFJFUyOZcOClYvM/hppMhGWuKSFEK9vjrB+bQ==", + "requires": { + "hoek": "6.x.x", + "isemail": "3.x.x", + "topo": "3.x.x" + } + } + } + }, + "node-radius-utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/node-radius-utils/-/node-radius-utils-1.2.0.tgz", + "integrity": "sha512-i3Sf6khnenl0aXumo0whAlfPWTaBqHxEnVBBxpu3dZ7q69NkPPv71rvPjlDZ5wkeKCTNNUTECljerS5kcYQxRw==" + }, "node-releases": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz", @@ -26806,6 +26907,11 @@ "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", "dev": true }, + "radius": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/radius/-/radius-1.1.4.tgz", + "integrity": "sha512-UWuzdF6xf3NpsXFZZmUEkxtEalDXj8hdmMXgbGzn7vOk6zXNsiIY2I6SJ1euHt7PTQuMoz2qDEJB+AfJDJgQYw==" + }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -28240,6 +28346,14 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, + "topo": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.3.tgz", + "integrity": "sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ==", + "requires": { + "hoek": "6.x.x" + } + }, "toposort": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", diff --git a/package.json b/package.json index 71c363b1..7adeeeaf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uptime-kuma", - "version": "1.17.1", + "version": "1.18.0-beta.0", "license": "MIT", "repository": { "type": "git", @@ -96,6 +96,7 @@ "mqtt": "^4.2.8", "mssql": "^8.1.0", "node-cloudflared-tunnel": "~1.0.9", + "node-radius-client": "^1.0.0", "nodemailer": "~6.6.5", "notp": "~2.0.3", "password-hash": "~1.2.2", diff --git a/server/database.js b/server/database.js index 2ac01d2e..bee7fb96 100644 --- a/server/database.js +++ b/server/database.js @@ -63,6 +63,8 @@ class Database { "patch-add-sqlserver-monitor.sql": true, "patch-add-other-auth.sql": { parents: [ "patch-monitor-basic-auth.sql" ] }, "patch-grpc-monitor.sql": true, + "patch-add-radius-monitor.sql": true, + "patch-monitor-add-resend-interval.sql": true, }; /** @@ -149,6 +151,9 @@ class Database { await R.exec("PRAGMA cache_size = -12000"); await R.exec("PRAGMA auto_vacuum = FULL"); + // Avoid error "SQLITE_BUSY: database is locked" by allowing SQLITE to wait up to 5 seconds to do a write + await R.exec("PRAGMA busy_timeout = 5000"); + // This ensures that an operating system crash or power failure will not corrupt the database. // FULL synchronous is very safe, but it is also slower. // Read more: https://sqlite.org/pragma.html#pragma_synchronous diff --git a/server/model/monitor.js b/server/model/monitor.js index 216ec3bd..09ed97e3 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -7,7 +7,7 @@ dayjs.extend(timezone); const axios = require("axios"); const { Prometheus } = require("../prometheus"); const { log, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util"); -const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mqttAsync, setSetting, httpNtlm, grpcQuery } = require("../util-server"); +const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mqttAsync, setSetting, httpNtlm, radius, grpcQuery } = require("../util-server"); const { R } = require("redbean-node"); const { BeanModel } = require("redbean-node/dist/bean-model"); const { Notification } = require("../notification"); @@ -79,6 +79,7 @@ class Monitor extends BeanModel { type: this.type, interval: this.interval, retryInterval: this.retryInterval, + resendInterval: this.resendInterval, keyword: this.keyword, expiryNotification: this.isEnabledExpiryNotification(), ignoreTls: this.getIgnoreTls(), @@ -108,6 +109,11 @@ class Monitor extends BeanModel { grpcMethod: this.grpcMethod, grpcServiceName: this.grpcServiceName, grpcEnableTls: this.getGrpcEnableTls(), + radiusUsername: this.radiusUsername, + radiusPassword: this.radiusPassword, + radiusCalledStationId: this.radiusCalledStationId, + radiusCallingStationId: this.radiusCallingStationId, + radiusSecret: this.radiusSecret, }; if (includeSensitiveData) { @@ -224,6 +230,7 @@ class Monitor extends BeanModel { bean.monitor_id = this.id; bean.time = R.isoDateTimeMillis(dayjs.utc()); bean.status = DOWN; + bean.downCount = previousBeat?.downCount || 0; if (this.isUpsideDown()) { bean.status = flipStatus(bean.status); @@ -570,6 +577,30 @@ class Monitor extends BeanModel { bean.msg = ""; bean.status = UP; bean.ping = dayjs().valueOf() - startTime; + } else if (this.type === "radius") { + let startTime = dayjs().valueOf(); + try { + const resp = await radius( + this.hostname, + this.radiusUsername, + this.radiusPassword, + this.radiusCalledStationId, + this.radiusCallingStationId, + this.radiusSecret + ); + if (resp.code) { + bean.msg = resp.code; + } + bean.status = UP; + } catch (error) { + bean.status = DOWN; + if (error.response?.code) { + bean.msg = error.response.code; + } else { + bean.msg = error.message; + } + } + bean.ping = dayjs().valueOf() - startTime; } else { bean.msg = "Unknown Monitor Type"; bean.status = PENDING; @@ -611,12 +642,27 @@ class Monitor extends BeanModel { log.debug("monitor", `[${this.name}] sendNotification`); await Monitor.sendNotification(isFirstBeat, this, bean); + // Reset down count + bean.downCount = 0; + // Clear Status Page Cache log.debug("monitor", `[${this.name}] apicache clear`); apicache.clear(); } else { bean.important = false; + + if (bean.status === DOWN && this.resendInterval > 0) { + ++bean.downCount; + if (bean.downCount >= this.resendInterval) { + // Send notification again, because we are still DOWN + log.debug("monitor", `[${this.name}] sendNotification again: Down Count: ${bean.downCount} | Resend Interval: ${this.resendInterval}`); + await Monitor.sendNotification(isFirstBeat, this, bean); + + // Reset down count + bean.downCount = 0; + } + } } if (bean.status === UP) { @@ -627,7 +673,7 @@ class Monitor extends BeanModel { } log.warn("monitor", `Monitor #${this.id} '${this.name}': Pending: ${bean.msg} | Max retries: ${this.maxretries} | Retry: ${retries} | Retry Interval: ${beatInterval} seconds | Type: ${this.type}`); } else { - log.warn("monitor", `Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Interval: ${beatInterval} seconds | Type: ${this.type}`); + log.warn("monitor", `Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Interval: ${beatInterval} seconds | Type: ${this.type} | Down Count: ${bean.downCount} | Resend Interval: ${this.resendInterval}`); } log.debug("monitor", `[${this.name}] Send to socket`); diff --git a/server/notification-providers/bark.js b/server/notification-providers/bark.js index 092511d8..3258e7c5 100644 --- a/server/notification-providers/bark.js +++ b/server/notification-providers/bark.js @@ -12,9 +12,7 @@ const { default: axios } = require("axios"); // bark is an APN bridge that sends notifications to Apple devices. -const barkNotificationGroup = "UptimeKuma"; const barkNotificationAvatar = "https://github.com/louislam/uptime-kuma/raw/master/public/icon.png"; -const barkNotificationSound = "telegraph"; const successMessage = "Successes!"; class Bark extends NotificationProvider { @@ -50,13 +48,23 @@ class Bark extends NotificationProvider { * @param {string} postUrl URL to append parameters to * @returns {string} */ - appendAdditionalParameters(postUrl) { - // grouping all our notifications - postUrl += "?group=" + barkNotificationGroup; + appendAdditionalParameters(notification, postUrl) { // set icon to uptime kuma icon, 11kb should be fine postUrl += "&icon=" + barkNotificationAvatar; + // grouping all our notifications + if (notification.barkGroup != null) { + postUrl += "&group=" + notification.barkGroup; + } else { + // default name + postUrl += "&group=" + "UptimeKuma"; + } // picked a sound, this should follow system's mute status when arrival - postUrl += "&sound=" + barkNotificationSound; + if (notification.barkSound != null) { + postUrl += "&sound=" + notification.barkSound; + } else { + // default sound + postUrl += "&sound=" + "telegraph"; + } return postUrl; } diff --git a/server/notification-providers/home-assistant.js b/server/notification-providers/home-assistant.js new file mode 100644 index 00000000..285989ee --- /dev/null +++ b/server/notification-providers/home-assistant.js @@ -0,0 +1,38 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); + +const defaultNotificationService = "notify"; + +class HomeAssistant extends NotificationProvider { + name = "HomeAssistant"; + + async send(notification, message, monitor = null, heartbeat = null) { + const notificationService = notification?.notificationService || defaultNotificationService; + + try { + await axios.post( + `${notification.homeAssistantUrl}/api/services/notify/${notificationService}`, + { + title: "Uptime Kuma", + message, + ...(notificationService !== "persistent_notification" && { data: { + name: monitor?.name, + status: heartbeat?.status, + } }), + }, + { + headers: { + Authorization: `Bearer ${notification.longLivedAccessToken}`, + "Content-Type": "application/json", + }, + } + ); + + return "Sent Successfully."; + } catch (error) { + this.throwGeneralAxiosError(error); + } + } +} + +module.exports = HomeAssistant; diff --git a/server/notification.js b/server/notification.js index ad1c8705..8093572a 100644 --- a/server/notification.js +++ b/server/notification.js @@ -12,6 +12,7 @@ const Feishu = require("./notification-providers/feishu"); const GoogleChat = require("./notification-providers/google-chat"); const Gorush = require("./notification-providers/gorush"); const Gotify = require("./notification-providers/gotify"); +const HomeAssistant = require("./notification-providers/home-assistant"); const Line = require("./notification-providers/line"); const LineNotify = require("./notification-providers/linenotify"); const LunaSea = require("./notification-providers/lunasea"); @@ -61,6 +62,7 @@ class Notification { new GoogleChat(), new Gorush(), new Gotify(), + new HomeAssistant(), new Line(), new LineNotify(), new LunaSea(), diff --git a/server/server.js b/server/server.js index 808402df..c3451638 100644 --- a/server/server.js +++ b/server/server.js @@ -669,6 +669,7 @@ let needSetup = false; bean.basic_auth_pass = monitor.basic_auth_pass; bean.interval = monitor.interval; bean.retryInterval = monitor.retryInterval; + bean.resendInterval = monitor.resendInterval; bean.hostname = monitor.hostname; bean.maxretries = monitor.maxretries; bean.port = parseInt(monitor.port); @@ -699,6 +700,11 @@ let needSetup = false; bean.grpcBody = monitor.grpcBody; bean.grpcMetadata = monitor.grpcMetadata; bean.grpcEnableTls = monitor.grpcEnableTls; + bean.radiusUsername = monitor.radiusUsername; + bean.radiusPassword = monitor.radiusPassword; + bean.radiusCalledStationId = monitor.radiusCalledStationId; + bean.radiusCallingStationId = monitor.radiusCallingStationId; + bean.radiusSecret = monitor.radiusSecret; await R.store(bean); @@ -1279,6 +1285,7 @@ let needSetup = false; authDomain: monitorListData[i].authDomain, interval: monitorListData[i].interval, retryInterval: retryInterval, + resendInterval: monitorListData[i].resendInterval || 0, hostname: monitorListData[i].hostname, maxretries: monitorListData[i].maxretries, port: monitorListData[i].port, diff --git a/server/util-server.js b/server/util-server.js index b5511944..12e1f7ba 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -17,6 +17,12 @@ const { NtlmClient } = require("axios-ntlm"); const { Settings } = require("./settings"); const grpc = require("@grpc/grpc-js"); const protojs = require("protobufjs"); +const radiusClient = require("node-radius-client"); +const { + dictionaries: { + rfc2865: { file, attributes }, + }, +} = require("node-radius-utils"); // From ping-lite exports.WIN = /^win/.test(process.platform); @@ -287,6 +293,30 @@ exports.postgresQuery = function (connectionString, query) { }); }; +exports.radius = function ( + hostname, + username, + password, + calledStationId, + callingStationId, + secret, +) { + const client = new radiusClient({ + host: hostname, + dictionaries: [ file ], + }); + + return client.accessRequest({ + secret: secret, + attributes: [ + [ attributes.USER_NAME, username ], + [ attributes.USER_PASSWORD, password ], + [ attributes.CALLING_STATION_ID, callingStationId ], + [ attributes.CALLED_STATION_ID, calledStationId ], + ], + }); +}; + /** * Retrieve value of setting based on key * @param {string} key Key of setting to retrieve diff --git a/src/components/notifications/Bark.vue b/src/components/notifications/Bark.vue index 014450de..6cac73d3 100644 --- a/src/components/notifications/Bark.vue +++ b/src/components/notifications/Bark.vue @@ -2,9 +2,6 @@
-
-

*{{ $t("Required") }}

-
{{ $t("here") }}
+
+ + +
+
+ + +
diff --git a/src/components/notifications/HomeAssistant.vue b/src/components/notifications/HomeAssistant.vue new file mode 100644 index 00000000..67e370a1 --- /dev/null +++ b/src/components/notifications/HomeAssistant.vue @@ -0,0 +1,40 @@ + diff --git a/src/components/notifications/index.js b/src/components/notifications/index.js index c1b7da4a..ff523052 100644 --- a/src/components/notifications/index.js +++ b/src/components/notifications/index.js @@ -10,6 +10,7 @@ import Feishu from "./Feishu.vue"; import GoogleChat from "./GoogleChat.vue"; import Gorush from "./Gorush.vue"; import Gotify from "./Gotify.vue"; +import HomeAssistant from "./HomeAssistant.vue"; import Line from "./Line.vue"; import LineNotify from "./LineNotify.vue"; import LunaSea from "./LunaSea.vue"; @@ -54,6 +55,7 @@ const NotificationFormList = { "GoogleChat": GoogleChat, "gorush": Gorush, "gotify": Gotify, + "HomeAssistant": HomeAssistant, "line": Line, "LineNotify": LineNotify, "lunasea": LunaSea, diff --git a/src/languages/de-DE.js b/src/languages/de-DE.js index 3df13b94..ef47909c 100644 --- a/src/languages/de-DE.js +++ b/src/languages/de-DE.js @@ -165,7 +165,10 @@ export default { Pink: "Pink", "Search...": "Suchen...", "Heartbeat Retry Interval": "Überprüfungsintervall", + "Resend Notification if Down X times consequently": "Benachrichtigung erneut senden, wenn Inaktiv X mal hintereinander", retryCheckEverySecond: "Alle {0} Sekunden neu versuchen", + resendEveryXTimes: "Erneut versenden alle {0} mal", + resendDisabled: "Erneut versenden deaktiviert", "Import Backup": "Backup importieren", "Export Backup": "Backup exportieren", "Avg. Ping": "Durchschn. Ping", diff --git a/src/languages/en.js b/src/languages/en.js index 0e62cfef..c03191ff 100644 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -2,6 +2,8 @@ export default { languageName: "English", checkEverySecond: "Check every {0} seconds", retryCheckEverySecond: "Retry every {0} seconds", + resendEveryXTimes: "Resend every {0} times", + resendDisabled: "Resend disabled", retriesDescription: "Maximum retries before the service is marked as down and a notification is sent", ignoreTLSError: "Ignore TLS/SSL error for HTTPS websites", upsideDownModeDescription: "Flip the status upside down. If the service is reachable, it is DOWN.", @@ -74,6 +76,7 @@ export default { "Heartbeat Interval": "Heartbeat Interval", Retries: "Retries", "Heartbeat Retry Interval": "Heartbeat Retry Interval", + "Resend Notification if Down X times consequently": "Resend Notification if Down X times consequently", Advanced: "Advanced", "Upside Down Mode": "Upside Down Mode", "Max. Redirects": "Max. Redirects", @@ -410,6 +413,8 @@ export default { SignName: "SignName", "Sms template must contain parameters: ": "Sms template must contain parameters: ", "Bark Endpoint": "Bark Endpoint", + "Bark Group": "Bark Group", + "Bark Sound": "Bark Sound", WebHookUrl: "WebHookUrl", SecretKey: "SecretKey", "For safety, must use secret key": "For safety, must use secret key", @@ -469,6 +474,7 @@ export default { "Domain Name Expiry Notification": "Domain Name Expiry Notification", Proxy: "Proxy", "Date Created": "Date Created", + HomeAssistant: "Home Assistant", onebotHttpAddress: "OneBot HTTP Address", onebotMessageType: "OneBot Message Type", onebotGroupMessage: "Group", @@ -481,6 +487,12 @@ export default { "Domain Names": "Domain Names", signedInDisp: "Signed in as {0}", signedInDispDisabled: "Auth Disabled.", + RadiusSecret: "Radius Secret", + RadiusSecretDescription: "Shared Secret between client and server", + RadiusCalledStationId: "Called Station Id", + RadiusCalledStationIdDescription: "Identifier of the called device", + RadiusCallingStationId: "Calling Station Id", + RadiusCallingStationIdDescription: "Identifier of the calling device", "Certificate Expiry Notification": "Certificate Expiry Notification", "API Username": "API Username", "API Key": "API Key", diff --git a/src/languages/zh-CN.js b/src/languages/zh-CN.js index 67077f38..8dbe05f0 100644 --- a/src/languages/zh-CN.js +++ b/src/languages/zh-CN.js @@ -404,6 +404,8 @@ export default { TemplateCode: "TemplateCode", SignName: "SignName", "Bark Endpoint": "Bark 接入点", + "Bark Group": "Bark 群组", + "Bark Sound": "Bark 铃声", "Device Token": "Apple Device Token", Platform: "平台", iOS: "iOS", diff --git a/src/languages/zh-TW.js b/src/languages/zh-TW.js index be87c540..3405c02a 100644 --- a/src/languages/zh-TW.js +++ b/src/languages/zh-TW.js @@ -408,6 +408,8 @@ export default { SignName: "SignName", "Sms template must contain parameters: ": "Sms 範本必須包含參數:", "Bark Endpoint": "Bark 端點", + "Bark Group": "Bark 群組", + "Bark Sound": "Bark 鈴聲", WebHookUrl: "WebHookUrl", SecretKey: "SecretKey", "For safety, must use secret key": "為了安全起見,必須使用秘密金鑰", diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index a0e9e732..036b2205 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -54,6 +54,9 @@ + @@ -96,8 +99,8 @@ - -
+ +
@@ -211,6 +214,36 @@
+ +