diff --git a/public/locales/de/translation.json b/public/locales/de/translation.json
index f7cdac7..b1d9f14 100644
--- a/public/locales/de/translation.json
+++ b/public/locales/de/translation.json
@@ -29,7 +29,8 @@
"copiedToClipboard": "In die Zwischenablage kopiert",
"copyToClipboard": "In die Zwischenablage kopieren",
"show": "Anzeigen",
- "hide": "Verbergen"
+ "hide": "Verbergen",
+ "reload": "Neu laden"
}
},
"sideMenu": {
@@ -386,7 +387,6 @@
"repeatNewPassword": "Neues Passwort wiederholen",
"language": "Sprache"
},
-
"button": {
"createApiKey": {
"title": "Neuen API-Schlüssel erstellen",
@@ -396,7 +396,14 @@
}
}
},
- "icon": { "viewApiDoc": "Api-Dokumentation anschauen" }
+ "icon": { "viewApiDoc": "Api-Dokumentation anschauen" },
+ "telegram": {
+ "title": "Telegram Benachrichtigungen",
+ "disconnectPopconfirm": {
+ "title": "Sind Sie sicher, dass Sie die Verbindung zu Telegram trennen wollen?",
+ "description": "Sie werden keine Benachrichtigungen mehr erhalten"
+ }
+ }
},
"scanners": {
"column": {
diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json
index f740c95..53124f4 100644
--- a/public/locales/en/translation.json
+++ b/public/locales/en/translation.json
@@ -29,7 +29,8 @@
"copiedToClipboard": "Copied to clipboard",
"copyToClipboard": "Copy to clipboard",
"show": "Show",
- "hide": "Hide"
+ "hide": "Hide",
+ "reload": "Reload"
}
},
"sideMenu": {
@@ -415,7 +416,21 @@
}
}
},
- "icon": { "viewApiDoc": "View API Documentation" }
+ "icon": { "viewApiDoc": "View API Documentation" },
+ "telegram": {
+ "title": "Telegram notifications",
+ "subscribeButton": "Subscribe",
+ "checkStatusButton": "Check Status",
+ "subscribed": "Subscribed",
+ "disconnectPopconfirm": {
+ "title": "Are you sure you want to disconnect your Telegram account?",
+ "description": "You will no longer receive notifications via Telegram"
+ },
+ "verificationPopover": {
+ "title": "Verification",
+ "description": "Add the bot on your telegram account and send the command {{command}} to the bot"
+ }
+ }
},
"scanners": {
"column": {
diff --git a/src/Components/MyIcon/index.js b/src/Components/MyIcon/index.js
index 8fd232a..35149d1 100644
--- a/src/Components/MyIcon/index.js
+++ b/src/Components/MyIcon/index.js
@@ -1,7 +1,9 @@
import {
CopyOutlined,
+ DisconnectOutlined,
EyeInvisibleOutlined,
EyeOutlined,
+ ReloadOutlined,
} from "@ant-design/icons";
import { Tooltip } from "antd";
import { useTranslation } from "react-i18next";
@@ -37,3 +39,13 @@ export function MyShowHiddenIcon({ setIsHidden, isHidden }) {
);
}
+
+export function MyReloadIcon({ onClick }) {
+ const { t } = useTranslation();
+
+ return (
+
+
+
+ );
+}
diff --git a/src/Pages/UserProfile/index.js b/src/Pages/UserProfile/index.js
index 779bd6f..d8f29bd 100644
--- a/src/Pages/UserProfile/index.js
+++ b/src/Pages/UserProfile/index.js
@@ -5,6 +5,7 @@ import {
Form,
Input,
Popconfirm,
+ Popover,
Row,
Select,
Space,
@@ -14,7 +15,7 @@ import {
Upload,
notification,
} from "antd";
-import { useEffect, useState } from "react";
+import { useEffect, useRef, useState } from "react";
import {
Constants,
EncodeStringToBase64,
@@ -24,18 +25,29 @@ import {
hasPermission,
isEmailValid,
myFetch,
+ myFetchContentType,
wsConnectionCustomEventName,
} from "../../utils";
import { Link } from "react-router-dom";
-import { useTranslation } from "react-i18next";
-import { FileTextOutlined, KeyOutlined } from "@ant-design/icons";
+import { Trans, useTranslation } from "react-i18next";
+import {
+ DisconnectOutlined,
+ FileTextOutlined,
+ InfoCircleOutlined,
+ KeyOutlined,
+ NotificationOutlined,
+} from "@ant-design/icons";
import { MyUserAvatar } from "../../Components/MyAvatar";
import { useUserProfileContext } from "../../Contexts/UserProfileContext";
import { useWebSocketContext } from "../../Contexts/WebSocketContext";
import { useAppContext } from "../../Contexts/AppContext";
import { useSideBarContext } from "../../Contexts/SideBarContext";
import { SentMessagesCommands } from "../../Handlers/WebSocketMessageHandler";
-import { MyCopyIcon, MyShowHiddenIcon } from "../../Components/MyIcon";
+import {
+ MyCopyIcon,
+ MyReloadIcon,
+ MyShowHiddenIcon,
+} from "../../Components/MyIcon";
export default function UserProfile() {
const webSocketContext = useWebSocketContext();
@@ -53,6 +65,13 @@ export default function UserProfile() {
const [repeatedNewPassword, setRepeatedNewPassword] = useState("");
const [newApiKeyName, setNewApikeyName] = useState("");
const [showApiKeyPassword, setShowApiKeyPassword] = useState(false);
+ const [
+ telegramNotificationSubscribedStatus,
+ setTelegramNotificationSubscribedStatus,
+ ] = useState(null);
+
+ const statusInterval = useRef(null);
+ const telegramVerifyCodeRequestTime = useRef(null);
const getSessionTableColumns = () => {
return [
@@ -271,6 +290,165 @@ export default function UserProfile() {
setNewApikeyName("");
};
+ const telegramVerifyStatusRequest = (updateOnlyOnVerified) =>
+ myFetch(
+ `/status/${appContext.userId.current}`,
+ "GET",
+ null,
+ {},
+ myFetchContentType.JSON,
+ Constants.TELEGRAM_BOT_MANAGER_ADDRESS
+ ).then((data) => {
+ // manual check verify status by clicking reload icon
+ if (!updateOnlyOnVerified) {
+ setTelegramNotificationSubscribedStatus(data.Status);
+ }
+
+ // used for auto check verify status
+ if (data.Status === true) {
+ setTelegramNotificationSubscribedStatus(data.Status);
+ clearInterval(statusInterval.current);
+ telegramVerifyCodeRequestTime.current = null;
+ }
+ });
+
+ const TelegramNotificationSubscribedStatusItem = () => {
+ // check verified status
+ if (telegramNotificationSubscribedStatus === null) {
+ return (
+
+ );
+ }
+
+ // unsubscribe
+ if (telegramNotificationSubscribedStatus === true) {
+ return (
+
+ {t("userProfile.telegram.subscribed")}
+
+ {
+ myFetch(
+ `/verifycode/${appContext.userId.current}`,
+ "DELETE",
+ null,
+ {},
+ myFetchContentType.JSON,
+ Constants.TELEGRAM_BOT_MANAGER_ADDRESS
+ ).then(() => {
+ setTelegramNotificationSubscribedStatus(null);
+ });
+ }}
+ >
+
+
+
+ );
+ }
+
+ // subscribe
+ if (telegramNotificationSubscribedStatus === false) {
+ return (
+ }
+ style={{ width: "100%" }}
+ onClick={() =>
+ myFetch(
+ `/verifycode/${appContext.userId.current}`,
+ "GET",
+ null,
+ {},
+ myFetchContentType.JSON,
+ Constants.TELEGRAM_BOT_MANAGER_ADDRESS
+ ).then((data) => {
+ setTelegramNotificationSubscribedStatus(data.Code);
+ })
+ }
+ >
+ {t("userProfile.telegram.subscribeButton")}
+
+ );
+ }
+
+ // verification code
+ return (
+
+
+ /verify ${telegramNotificationSubscribedStatus}`,
+ }}
+ />
+
+
+

+
+
+ }
+ >
+
+
+
+ {telegramNotificationSubscribedStatus}
+
+
+
+ telegramVerifyStatusRequest()} />
+
+ );
+ };
+
+ useEffect(() => {
+ if (
+ telegramNotificationSubscribedStatus !== true &&
+ telegramNotificationSubscribedStatus !== false &&
+ telegramNotificationSubscribedStatus !== null
+ ) {
+ telegramVerifyCodeRequestTime.current = new Date();
+
+ statusInterval.current = setInterval(() => {
+ // check expiration time
+ if (
+ telegramVerifyCodeRequestTime.current !== null &&
+ new Date() - telegramVerifyCodeRequestTime.current >
+ Constants.GLOBALS.TELEGRAM_VERIFY_CODE_EXPIRATION_TIME
+ ) {
+ clearInterval(statusInterval.current);
+ telegramVerifyCodeRequestTime.current = null;
+ setTelegramNotificationSubscribedStatus(false);
+ return;
+ }
+
+ telegramVerifyStatusRequest(true);
+ }, 1000);
+ }
+
+ return () => clearInterval(statusInterval.current);
+ }, [telegramNotificationSubscribedStatus]);
+
useEffect(() => {
const userProfileRequest = () =>
myFetch("/user/profile", "GET").then((data) => {
@@ -342,6 +520,10 @@ export default function UserProfile() {
onChange={(e) => i18n.changeLanguage(e)}
/>
+
+
+
+
diff --git a/src/utils.js b/src/utils.js
index 98e5270..d31fb9a 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -14,6 +14,8 @@ let wsAddress = "";
let logApiAddress = "";
let roboticsApiAddress = "";
var roboticsSwaggerAddress = "";
+var telegramBotManagerAddress = "";
+var telegramBotManagerStaticContentAddress = "";
if (window.location.hostname === "localhost" && window.location.port === "") {
// for docker container testing on localhost
@@ -23,6 +25,8 @@ if (window.location.hostname === "localhost" && window.location.port === "") {
logApiAddress = "http://localhost/lm/v1/log";
roboticsApiAddress = "http://localhost/rcm/v1";
roboticsSwaggerAddress = "http://localhost/rcm/swagger/index.html";
+ telegramBotManagerAddress = "http://localhost/tnm/v1";
+ telegramBotManagerStaticContentAddress = "http://localhost/tnm/";
} else if (window.location.hostname === "localhost") {
// programming on localhost
apiAddress = "http://localhost:50050/v1";
@@ -31,6 +35,8 @@ if (window.location.hostname === "localhost" && window.location.port === "") {
logApiAddress = "http://127.0.0.1:50110/v1/log";
roboticsApiAddress = "http://localhost:50055/v1";
roboticsSwaggerAddress = "http://localhost:50055/swagger/index.html";
+ telegramBotManagerAddress = "http://localhost:50056/v1";
+ telegramBotManagerStaticContentAddress = "http://localhost:50056/";
/*} else if (window.location.hostname === "192.168.178.93") {
apiAddress = "http://192.168.178.93:50050/v1";
staticContentAddress = "http://192.168.178.93:50050/";
@@ -43,6 +49,8 @@ if (window.location.hostname === "localhost" && window.location.port === "") {
logApiAddress = `${window.location.protocol}${window.location.hostname}/lm/v1/log`;
roboticsApiAddress = `${window.location.protocol}${window.location.hostname}/rcm/v1`;
roboticsSwaggerAddress = `${window.location.protocol}${window.location.hostname}/rcm/swagger/index.html`;
+ telegramBotManagerAddress = `${window.location.protocol}${window.location.hostname}/tnm/v1`;
+ telegramBotManagerStaticContentAddress = `${window.location.protocol}${window.location.hostname}/tnm/`;
}
export const Constants = {
@@ -66,6 +74,8 @@ export const Constants = {
LOG_API_ADDRESS: logApiAddress,
ROBOTICS_API_ADDRESS: roboticsApiAddress, // robot-control-manager
ROBOTICS_SWAGGER_ADDRESS: roboticsSwaggerAddress,
+ TELEGRAM_BOT_MANAGER_ADDRESS: telegramBotManagerAddress,
+ TELEGRAM_BOT_MANAGER_CONTENT_ADDRESS: telegramBotManagerStaticContentAddress,
ROUTE_PATHS: {
EQUIPMENT_DOCUMENTATION: "/equipment-documentation",
EQUIPMENT_DOCUMENTATION_VIEW: "/equipment-documentation/",
@@ -113,6 +123,7 @@ export const Constants = {
MAX_USER_API_KEY_NAME_LENGTH: 30,
ROBOTICS_ROBOTS_PAGINATION_LIMIT: 5,
ROBOTICS_UNAUTHORIZED_PAGINATION_LIMIT: 5,
+ TELEGRAM_VERIFY_CODE_EXPIRATION_TIME: 10 * 60 * 1000, // 10 minutes
},
MAX_AVATAR_SIZE: 5 * 1024 * 1024,
ACCEPTED_AVATAR_FILE_TYPES: [