feat: 增加消息推送设置 (#302)
* feat: 增加消息推送设置 * chore: bump version to v0.9.52
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vocechat-web",
|
||||
"version": "0.9.51",
|
||||
"version": "0.9.52",
|
||||
"homepage": "https://voce.chat",
|
||||
"dependencies": {
|
||||
"@metamask/onboarding": "^1.0.1",
|
||||
|
||||
Generated
+6
-11
@@ -2604,11 +2604,8 @@ packages:
|
||||
caniuse-api@3.0.0:
|
||||
resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==}
|
||||
|
||||
caniuse-lite@1.0.30001718:
|
||||
resolution: {integrity: sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==}
|
||||
|
||||
caniuse-lite@1.0.30001731:
|
||||
resolution: {integrity: sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==}
|
||||
caniuse-lite@1.0.30001764:
|
||||
resolution: {integrity: sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==}
|
||||
|
||||
chalk@4.1.2:
|
||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||
@@ -8773,7 +8770,7 @@ snapshots:
|
||||
autoprefixer@10.4.21(postcss@8.5.6):
|
||||
dependencies:
|
||||
browserslist: 4.25.1
|
||||
caniuse-lite: 1.0.30001718
|
||||
caniuse-lite: 1.0.30001764
|
||||
fraction.js: 4.3.7
|
||||
normalize-range: 0.1.2
|
||||
picocolors: 1.1.1
|
||||
@@ -8921,7 +8918,7 @@ snapshots:
|
||||
|
||||
browserslist@4.25.1:
|
||||
dependencies:
|
||||
caniuse-lite: 1.0.30001731
|
||||
caniuse-lite: 1.0.30001764
|
||||
electron-to-chromium: 1.5.199
|
||||
node-releases: 2.0.19
|
||||
update-browserslist-db: 1.1.3(browserslist@4.25.1)
|
||||
@@ -8961,13 +8958,11 @@ snapshots:
|
||||
caniuse-api@3.0.0:
|
||||
dependencies:
|
||||
browserslist: 4.25.1
|
||||
caniuse-lite: 1.0.30001718
|
||||
caniuse-lite: 1.0.30001764
|
||||
lodash.memoize: 4.1.2
|
||||
lodash.uniq: 4.5.0
|
||||
|
||||
caniuse-lite@1.0.30001718: {}
|
||||
|
||||
caniuse-lite@1.0.30001731: {}
|
||||
caniuse-lite@1.0.30001764: {}
|
||||
|
||||
chalk@4.1.2:
|
||||
dependencies:
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"my_account": "Mein Konto",
|
||||
"config": "Konfiguration",
|
||||
"bot": "Bot & Webhook",
|
||||
"notification_channels": "Benachrichtigungskanäle",
|
||||
"firebase": "Firebase",
|
||||
"agora": "Agora",
|
||||
"smtp": "SMTP",
|
||||
@@ -23,7 +24,8 @@
|
||||
"about": "Über",
|
||||
"api_doc": "API-Dokumentation",
|
||||
"version": "Version",
|
||||
"video": "Videoanrufe"
|
||||
"video": "Videoanrufe",
|
||||
"notification": "Benachrichtigungen"
|
||||
},
|
||||
"vocespace": {
|
||||
"add": "Hinzufügen",
|
||||
@@ -350,6 +352,62 @@
|
||||
"step_1": "Schritt 1: Eigene Server-URL eintragen",
|
||||
"step_2": "Schritt 2: Login-Token eingeben",
|
||||
"step_2_desc": "Token per X-API-Key-Header abrufen oder unten kopieren:",
|
||||
"last": "Jede API hat einen „Testen“-Button zur Debugging."
|
||||
"last": "Jede API hat einen „Testen"-Button zur Debugging."
|
||||
},
|
||||
"notification": {
|
||||
"desc": "Benachrichtigungseinstellungen für verschiedene Kanäle konfigurieren",
|
||||
"loading": "Lädt...",
|
||||
"no_channels": "Keine Benachrichtigungskanäle verfügbar. Bitte kontaktieren Sie Ihren Administrator, um Benachrichtigungskanäle zu konfigurieren.",
|
||||
"status": "Status",
|
||||
"enabled": "Aktiviert",
|
||||
"not_configured": "Nicht konfiguriert",
|
||||
"configure": "Konfigurieren",
|
||||
"enable_notifications": "Benachrichtigungen aktivieren",
|
||||
"trigger_conditions": "Auslösebedingungen",
|
||||
"trigger_on_all_messages": "Alle Nachrichten empfangen",
|
||||
"trigger_on_mentions": "Bei Erwähnung benachrichtigen",
|
||||
"skip_when_online": "Benachrichtigungen überspringen, wenn online",
|
||||
"select_groups": "Gruppen auswählen",
|
||||
"save": "Speichern",
|
||||
"saving": "Speichert...",
|
||||
"cancel": "Abbrechen",
|
||||
"delete": "Konfiguration löschen",
|
||||
"deleting": "Löscht...",
|
||||
"save_success": "Benachrichtigungseinstellungen erfolgreich gespeichert",
|
||||
"save_error": "Fehler beim Speichern der Benachrichtigungseinstellungen",
|
||||
"delete_success": "Benachrichtigungseinstellungen erfolgreich gelöscht",
|
||||
"delete_error": "Fehler beim Löschen der Benachrichtigungseinstellungen"
|
||||
},
|
||||
"admin_notification_channels": {
|
||||
"desc": "Benachrichtigungskanäle für Benutzer konfigurieren",
|
||||
"create_channel": "Kanal erstellen",
|
||||
"edit_channel": "Bearbeiten",
|
||||
"delete_channel": "Löschen",
|
||||
"test_channel": "Testen",
|
||||
"channel_name": "Kanalname",
|
||||
"channel_type": "Kanaltyp",
|
||||
"channel_status": "Status",
|
||||
"enabled": "Aktiviert",
|
||||
"disabled": "Deaktiviert",
|
||||
"actions": "Aktionen",
|
||||
"create_title": "Benachrichtigungskanal erstellen",
|
||||
"create_desc": "Neuen Benachrichtigungskanal konfigurieren",
|
||||
"edit_title": "Benachrichtigungskanal bearbeiten",
|
||||
"edit_desc": "Kanalkonfiguration aktualisieren",
|
||||
"delete_title": "Kanal löschen",
|
||||
"delete_desc": "Möchten Sie diesen Kanal wirklich löschen? Alle Benutzereinstellungen für diesen Kanal werden entfernt.",
|
||||
"test_success": "Testbenachrichtigung erfolgreich gesendet",
|
||||
"test_error": "Fehler beim Senden der Testbenachrichtigung",
|
||||
"create_success": "Kanal erfolgreich erstellt",
|
||||
"create_error": "Fehler beim Erstellen des Kanals",
|
||||
"update_success": "Kanal erfolgreich aktualisiert",
|
||||
"update_error": "Fehler beim Aktualisieren des Kanals",
|
||||
"delete_success": "Kanal erfolgreich gelöscht",
|
||||
"delete_error": "Fehler beim Löschen des Kanals",
|
||||
"config_section": "Kanalkonfiguration",
|
||||
"basic_info": "Grundinformationen",
|
||||
"enable_channel": "Kanal aktivieren",
|
||||
"rate_limit": "Ratenlimit (Anfragen pro Sekunde)",
|
||||
"no_channels": "Noch keine Benachrichtigungskanäle konfiguriert"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"my_account": "My Account",
|
||||
"config": "Configuration",
|
||||
"bot": "Bot & Webhook",
|
||||
"notification_channels": "Notification Channels",
|
||||
"firebase": "Firebase",
|
||||
"agora": "Agora",
|
||||
"vocespace": "Video",
|
||||
@@ -24,7 +25,8 @@
|
||||
"about": "About",
|
||||
"api_doc": "API Documentation",
|
||||
"version": "Version",
|
||||
"video": "Video"
|
||||
"video": "Video",
|
||||
"notification": "Notifications"
|
||||
},
|
||||
"channel": {
|
||||
"leave": "Leave Channel",
|
||||
@@ -379,5 +381,61 @@
|
||||
"step_2": "Step Two: Input the login token",
|
||||
"step_2_desc": "You can get the token by access X-API-Key http header or just copy the text below:",
|
||||
"last": "Every API has a 'Try it Out' button, and you can debug the API with it"
|
||||
},
|
||||
"notification": {
|
||||
"desc": "Configure your notification channels",
|
||||
"loading": "Loading...",
|
||||
"no_channels": "No notification channel types available. Please contact your administrator.",
|
||||
"status": "Status",
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled",
|
||||
"my_channels": "My Notification Channels",
|
||||
"available_types": "Available Channel Types",
|
||||
"create_channel": "Create Channel",
|
||||
"edit_channel": "Edit Channel",
|
||||
"channel_name": "Channel Name",
|
||||
"channel_config": "Channel Configuration",
|
||||
"enable_notifications": "Enable notifications",
|
||||
"trigger_conditions": "Trigger conditions",
|
||||
"trigger_on_all_messages": "Receive all messages",
|
||||
"trigger_on_mentions": "Notify when mentioned",
|
||||
"skip_when_online": "Skip notifications when online",
|
||||
"select_groups": "Select groups",
|
||||
"save": "Save",
|
||||
"saving": "Saving...",
|
||||
"cancel": "Cancel",
|
||||
"edit": "Edit",
|
||||
"test": "Test",
|
||||
"delete": "Delete",
|
||||
"save_success": "Channel saved successfully",
|
||||
"save_error": "Failed to save channel",
|
||||
"create_success": "Channel created successfully",
|
||||
"create_error": "Failed to create channel",
|
||||
"delete_success": "Channel deleted successfully",
|
||||
"delete_error": "Failed to delete channel",
|
||||
"test_success": "Test notification sent successfully",
|
||||
"test_error": "Failed to send test notification"
|
||||
},
|
||||
"admin_notification_channels": {
|
||||
"desc": "Enable notification channel types for users",
|
||||
"enabled_types": "Enabled Channel Types",
|
||||
"available_types": "Available Channel Types",
|
||||
"enable_type": "Enable",
|
||||
"channel_type": "Channel Type",
|
||||
"channel_status": "Status",
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled",
|
||||
"actions": "Actions",
|
||||
"delete_channel": "Delete",
|
||||
"delete_title": "Delete Channel Type",
|
||||
"delete_desc": "Are you sure you want to remove this channel type? Users will no longer be able to create new channels of this type.",
|
||||
"cancel": "Cancel",
|
||||
"delete": "Delete",
|
||||
"create_success": "Channel type enabled successfully",
|
||||
"create_error": "Failed to enable channel type",
|
||||
"update_success": "Channel type updated successfully",
|
||||
"update_error": "Failed to update channel type",
|
||||
"delete_success": "Channel type deleted successfully",
|
||||
"delete_error": "Failed to delete channel type"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"my_account": "Mi Cuenta",
|
||||
"config": "Configuración",
|
||||
"bot": "Bot & Webhook",
|
||||
"notification_channels": "Canales de Notificación",
|
||||
"firebase": "Firebase",
|
||||
"agora": "Agora",
|
||||
"smtp": "SMTP",
|
||||
@@ -23,7 +24,8 @@
|
||||
"about": "Acerca de",
|
||||
"api_doc": "Documentación de API",
|
||||
"version": "Versión",
|
||||
"video": "Video"
|
||||
"video": "Video",
|
||||
"notification": "Notificaciones"
|
||||
},
|
||||
"vocespace": {
|
||||
"add": "Agregar",
|
||||
@@ -293,5 +295,61 @@
|
||||
"step_2": "Paso Dos: Ingresa el token de inicio de sesión",
|
||||
"step_2_desc": "Puedes obtener el token accediendo al encabezado X-API-Key http o simplemente copiando el texto a continuación:",
|
||||
"last": "Cada API tiene un botón 'Probar' para que puedas depurar la API con él"
|
||||
},
|
||||
"notification": {
|
||||
"desc": "Configurar preferencias de notificación para diferentes canales",
|
||||
"loading": "Cargando...",
|
||||
"no_channels": "No hay canales de notificación disponibles. Póngase en contacto con su administrador para configurar los canales de notificación.",
|
||||
"status": "Estado",
|
||||
"enabled": "Habilitado",
|
||||
"not_configured": "No configurado",
|
||||
"configure": "Configurar",
|
||||
"enable_notifications": "Habilitar notificaciones",
|
||||
"trigger_conditions": "Condiciones de activación",
|
||||
"trigger_on_all_messages": "Recibir todos los mensajes",
|
||||
"trigger_on_mentions": "Notificar cuando se mencione",
|
||||
"skip_when_online": "Omitir notificaciones cuando esté en línea",
|
||||
"select_groups": "Seleccionar grupos",
|
||||
"save": "Guardar",
|
||||
"saving": "Guardando...",
|
||||
"cancel": "Cancelar",
|
||||
"delete": "Eliminar configuración",
|
||||
"deleting": "Eliminando...",
|
||||
"save_success": "Preferencias de notificación guardadas correctamente",
|
||||
"save_error": "Error al guardar las preferencias de notificación",
|
||||
"delete_success": "Preferencias de notificación eliminadas correctamente",
|
||||
"delete_error": "Error al eliminar las preferencias de notificación"
|
||||
},
|
||||
"admin_notification_channels": {
|
||||
"desc": "Configurar canales de notificación para usuarios",
|
||||
"create_channel": "Crear Canal",
|
||||
"edit_channel": "Editar",
|
||||
"delete_channel": "Eliminar",
|
||||
"test_channel": "Probar",
|
||||
"channel_name": "Nombre del Canal",
|
||||
"channel_type": "Tipo de Canal",
|
||||
"channel_status": "Estado",
|
||||
"enabled": "Habilitado",
|
||||
"disabled": "Deshabilitado",
|
||||
"actions": "Acciones",
|
||||
"create_title": "Crear Canal de Notificación",
|
||||
"create_desc": "Configurar un nuevo canal de notificación",
|
||||
"edit_title": "Editar Canal de Notificación",
|
||||
"edit_desc": "Actualizar configuración del canal",
|
||||
"delete_title": "Eliminar Canal",
|
||||
"delete_desc": "¿Está seguro de que desea eliminar este canal? Se eliminarán todas las preferencias de usuario para este canal.",
|
||||
"test_success": "Notificación de prueba enviada correctamente",
|
||||
"test_error": "Error al enviar la notificación de prueba",
|
||||
"create_success": "Canal creado correctamente",
|
||||
"create_error": "Error al crear el canal",
|
||||
"update_success": "Canal actualizado correctamente",
|
||||
"update_error": "Error al actualizar el canal",
|
||||
"delete_success": "Canal eliminado correctamente",
|
||||
"delete_error": "Error al eliminar el canal",
|
||||
"config_section": "Configuración del Canal",
|
||||
"basic_info": "Información Básica",
|
||||
"enable_channel": "Habilitar Canal",
|
||||
"rate_limit": "Límite de velocidad (solicitudes por segundo)",
|
||||
"no_channels": "Aún no se han configurado canales de notificación"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"my_account": "Mon Compte",
|
||||
"config": "Configuration",
|
||||
"bot": "Bot & Webhook",
|
||||
"notification_channels": "Canaux de Notification",
|
||||
"firebase": "Firebase",
|
||||
"agora": "Agora",
|
||||
"smtp": "SMTP",
|
||||
@@ -23,7 +24,8 @@
|
||||
"about": "À Propos",
|
||||
"api_doc": "Documentation API",
|
||||
"version": "Version",
|
||||
"video": "Vidéo"
|
||||
"video": "Vidéo",
|
||||
"notification": "Notifications"
|
||||
},
|
||||
"vocespace": {
|
||||
"add": "Ajouter",
|
||||
@@ -299,5 +301,61 @@
|
||||
"step_2": "Étape Deux : Entrez le token de connexion",
|
||||
"step_2_desc": "Vous pouvez obtenir le token en accédant à l'en-tête http X-API-Key ou en copiant simplement le texte ci-dessous :",
|
||||
"last": "Chaque API a un bouton 'Essayez-le', et vous pouvez déboguer l'API avec cela"
|
||||
},
|
||||
"notification": {
|
||||
"desc": "Configurer les préférences de notification pour différents canaux",
|
||||
"loading": "Chargement...",
|
||||
"no_channels": "Aucun canal de notification disponible. Veuillez contacter votre administrateur pour configurer les canaux de notification.",
|
||||
"status": "Statut",
|
||||
"enabled": "Activé",
|
||||
"not_configured": "Non configuré",
|
||||
"configure": "Configurer",
|
||||
"enable_notifications": "Activer les notifications",
|
||||
"trigger_conditions": "Conditions de déclenchement",
|
||||
"trigger_on_all_messages": "Recevoir tous les messages",
|
||||
"trigger_on_mentions": "Notifier lors d'une mention",
|
||||
"skip_when_online": "Ignorer les notifications en ligne",
|
||||
"select_groups": "Sélectionner les groupes",
|
||||
"save": "Enregistrer",
|
||||
"saving": "Enregistrement...",
|
||||
"cancel": "Annuler",
|
||||
"delete": "Supprimer la configuration",
|
||||
"deleting": "Suppression...",
|
||||
"save_success": "Préférences de notification enregistrées avec succès",
|
||||
"save_error": "Échec de l'enregistrement des préférences de notification",
|
||||
"delete_success": "Préférences de notification supprimées avec succès",
|
||||
"delete_error": "Échec de la suppression des préférences de notification"
|
||||
},
|
||||
"admin_notification_channels": {
|
||||
"desc": "Configurer les canaux de notification pour les utilisateurs",
|
||||
"create_channel": "Créer un Canal",
|
||||
"edit_channel": "Modifier",
|
||||
"delete_channel": "Supprimer",
|
||||
"test_channel": "Tester",
|
||||
"channel_name": "Nom du Canal",
|
||||
"channel_type": "Type de Canal",
|
||||
"channel_status": "Statut",
|
||||
"enabled": "Activé",
|
||||
"disabled": "Désactivé",
|
||||
"actions": "Actions",
|
||||
"create_title": "Créer un Canal de Notification",
|
||||
"create_desc": "Configurer un nouveau canal de notification",
|
||||
"edit_title": "Modifier le Canal de Notification",
|
||||
"edit_desc": "Mettre à jour la configuration du canal",
|
||||
"delete_title": "Supprimer le Canal",
|
||||
"delete_desc": "Êtes-vous sûr de vouloir supprimer ce canal ? Toutes les préférences utilisateur pour ce canal seront supprimées.",
|
||||
"test_success": "Notification de test envoyée avec succès",
|
||||
"test_error": "Échec de l'envoi de la notification de test",
|
||||
"create_success": "Canal créé avec succès",
|
||||
"create_error": "Échec de la création du canal",
|
||||
"update_success": "Canal mis à jour avec succès",
|
||||
"update_error": "Échec de la mise à jour du canal",
|
||||
"delete_success": "Canal supprimé avec succès",
|
||||
"delete_error": "Échec de la suppression du canal",
|
||||
"config_section": "Configuration du Canal",
|
||||
"basic_info": "Informations de Base",
|
||||
"enable_channel": "Activer le Canal",
|
||||
"rate_limit": "Limite de débit (requêtes par seconde)",
|
||||
"no_channels": "Aucun canal de notification configuré pour le moment"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
"members": "メンバー",
|
||||
"my_account": "アカウント",
|
||||
"config": "設定",
|
||||
"bot": "ボット&Webhook",
|
||||
"notification_channels": "通知チャンネル",
|
||||
"firebase": "Firebase",
|
||||
"agora": "Agora",
|
||||
"smtp": "SMTP",
|
||||
@@ -19,7 +21,8 @@
|
||||
"about": "このアプリについて",
|
||||
"api_doc": "API ドキュメント",
|
||||
"version": "Version",
|
||||
"video": "ビデオ"
|
||||
"video": "ビデオ",
|
||||
"notification": "通知設定"
|
||||
},
|
||||
"vocespace": {
|
||||
"add": "追加",
|
||||
@@ -165,5 +168,61 @@
|
||||
"step_2": "2. 登録済みのトークンを入力してください",
|
||||
"step_2_desc": "トークンを取得するには、http header の X-API-Key にアクセスしてください、もしくは以下のテキストを直接コピーで使用してください👇",
|
||||
"last": "3. 最後に各 API には Try it Out オプションが付いてる、あれを使って API を試すことができます"
|
||||
},
|
||||
"notification": {
|
||||
"desc": "異なるチャネルの通知設定を構成する",
|
||||
"loading": "読み込み中...",
|
||||
"no_channels": "利用可能な通知チャネルがありません。管理者に連絡して通知チャネルを設定してください。",
|
||||
"status": "ステータス",
|
||||
"enabled": "有効",
|
||||
"not_configured": "未設定",
|
||||
"configure": "設定",
|
||||
"enable_notifications": "通知を有効にする",
|
||||
"trigger_conditions": "トリガー条件",
|
||||
"trigger_on_all_messages": "すべてのメッセージを受信",
|
||||
"trigger_on_mentions": "メンション時に通知",
|
||||
"skip_when_online": "オンライン時は通知をスキップ",
|
||||
"select_groups": "グループを選択",
|
||||
"save": "保存",
|
||||
"saving": "保存中...",
|
||||
"cancel": "キャンセル",
|
||||
"delete": "設定を削除",
|
||||
"deleting": "削除中...",
|
||||
"save_success": "通知設定が正常に保存されました",
|
||||
"save_error": "通知設定の保存に失敗しました",
|
||||
"delete_success": "通知設定が正常に削除されました",
|
||||
"delete_error": "通知設定の削除に失敗しました"
|
||||
},
|
||||
"admin_notification_channels": {
|
||||
"desc": "ユーザー向けの通知チャンネルを設定",
|
||||
"create_channel": "チャンネルを作成",
|
||||
"edit_channel": "編集",
|
||||
"delete_channel": "削除",
|
||||
"test_channel": "テスト",
|
||||
"channel_name": "チャンネル名",
|
||||
"channel_type": "チャンネルタイプ",
|
||||
"channel_status": "ステータス",
|
||||
"enabled": "有効",
|
||||
"disabled": "無効",
|
||||
"actions": "操作",
|
||||
"create_title": "通知チャンネルを作成",
|
||||
"create_desc": "新しい通知チャンネルを設定",
|
||||
"edit_title": "通知チャンネルを編集",
|
||||
"edit_desc": "チャンネル設定を更新",
|
||||
"delete_title": "チャンネルを削除",
|
||||
"delete_desc": "このチャンネルを削除してもよろしいですか?このチャンネルのすべてのユーザー設定が削除されます。",
|
||||
"test_success": "テスト通知が正常に送信されました",
|
||||
"test_error": "テスト通知の送信に失敗しました",
|
||||
"create_success": "チャンネルが正常に作成されました",
|
||||
"create_error": "チャンネルの作成に失敗しました",
|
||||
"update_success": "チャンネルが正常に更新されました",
|
||||
"update_error": "チャンネルの更新に失敗しました",
|
||||
"delete_success": "チャンネルが正常に削除されました",
|
||||
"delete_error": "チャンネルの削除に失敗しました",
|
||||
"config_section": "チャンネル設定",
|
||||
"basic_info": "基本情報",
|
||||
"enable_channel": "チャンネルを有効にする",
|
||||
"rate_limit": "レート制限(秒あたりのリクエスト数)",
|
||||
"no_channels": "通知チャンネルがまだ設定されていません"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"my_account": "My Account",
|
||||
"config": "Configuration",
|
||||
"bot": "Bot & Webhook",
|
||||
"notification_channels": "Canais de Notificação",
|
||||
"firebase": "Firebase",
|
||||
"agora": "Agora",
|
||||
"smtp": "SMTP",
|
||||
@@ -23,7 +24,8 @@
|
||||
"about": "About",
|
||||
"api_doc": "API Documentation",
|
||||
"version": "Version",
|
||||
"video": "Video"
|
||||
"video": "Video",
|
||||
"notification": "Notificações"
|
||||
},
|
||||
"vocespace": {
|
||||
"add": "Adicionar",
|
||||
@@ -285,5 +287,61 @@
|
||||
"step_2": "Step Two: Input the login token",
|
||||
"step_2_desc": "You can get the token by access X-API-Key http header or just copy the text below:",
|
||||
"last": "Every API has a 'Try it Out' button, and you can debug the API with it"
|
||||
},
|
||||
"notification": {
|
||||
"desc": "Configurar preferências de notificação para diferentes canais",
|
||||
"loading": "Carregando...",
|
||||
"no_channels": "Nenhum canal de notificação disponível. Entre em contato com o administrador para configurar os canais de notificação.",
|
||||
"status": "Status",
|
||||
"enabled": "Ativado",
|
||||
"not_configured": "Não configurado",
|
||||
"configure": "Configurar",
|
||||
"enable_notifications": "Ativar notificações",
|
||||
"trigger_conditions": "Condições de acionamento",
|
||||
"trigger_on_all_messages": "Receber todas as mensagens",
|
||||
"trigger_on_mentions": "Notificar quando mencionado",
|
||||
"skip_when_online": "Ignorar notificações quando online",
|
||||
"select_groups": "Selecionar grupos",
|
||||
"save": "Salvar",
|
||||
"saving": "Salvando...",
|
||||
"cancel": "Cancelar",
|
||||
"delete": "Excluir configuração",
|
||||
"deleting": "Excluindo...",
|
||||
"save_success": "Preferências de notificação salvas com sucesso",
|
||||
"save_error": "Falha ao salvar preferências de notificação",
|
||||
"delete_success": "Preferências de notificação excluídas com sucesso",
|
||||
"delete_error": "Falha ao excluir preferências de notificação"
|
||||
},
|
||||
"admin_notification_channels": {
|
||||
"desc": "Configurar canais de notificação para usuários",
|
||||
"create_channel": "Criar Canal",
|
||||
"edit_channel": "Editar",
|
||||
"delete_channel": "Excluir",
|
||||
"test_channel": "Testar",
|
||||
"channel_name": "Nome do Canal",
|
||||
"channel_type": "Tipo de Canal",
|
||||
"channel_status": "Status",
|
||||
"enabled": "Ativado",
|
||||
"disabled": "Desativado",
|
||||
"actions": "Ações",
|
||||
"create_title": "Criar Canal de Notificação",
|
||||
"create_desc": "Configurar um novo canal de notificação",
|
||||
"edit_title": "Editar Canal de Notificação",
|
||||
"edit_desc": "Atualizar configuração do canal",
|
||||
"delete_title": "Excluir Canal",
|
||||
"delete_desc": "Tem certeza de que deseja excluir este canal? Todas as preferências de usuário para este canal serão removidas.",
|
||||
"test_success": "Notificação de teste enviada com sucesso",
|
||||
"test_error": "Falha ao enviar notificação de teste",
|
||||
"create_success": "Canal criado com sucesso",
|
||||
"create_error": "Falha ao criar canal",
|
||||
"update_success": "Canal atualizado com sucesso",
|
||||
"update_error": "Falha ao atualizar canal",
|
||||
"delete_success": "Canal excluído com sucesso",
|
||||
"delete_error": "Falha ao excluir canal",
|
||||
"config_section": "Configuração do Canal",
|
||||
"basic_info": "Informações Básicas",
|
||||
"enable_channel": "Ativar Canal",
|
||||
"rate_limit": "Limite de taxa (solicitações por segundo)",
|
||||
"no_channels": "Nenhum canal de notificação configurado ainda"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"my_account": "Мой аккаунт",
|
||||
"config": "Конфигурация",
|
||||
"bot": "Bot & Webhook",
|
||||
"notification_channels": "Каналы уведомлений",
|
||||
"firebase": "Firebase",
|
||||
"agora": "Agora",
|
||||
"smtp": "SMTP",
|
||||
@@ -23,7 +24,8 @@
|
||||
"about": "About",
|
||||
"api_doc": "API Documentation",
|
||||
"version": "Версия",
|
||||
"video": "Видео"
|
||||
"video": "Видео",
|
||||
"notification": "Уведомления"
|
||||
},
|
||||
"vocespace": {
|
||||
"add": "Добавить",
|
||||
@@ -345,5 +347,61 @@
|
||||
"step_2": "Шаг второй: введите токен входа",
|
||||
"step_2_desc": "Вы можете получить токен, обратившись к http-заголовку X-API-Key или просто скопировав текст ниже:",
|
||||
"last": "У каждого API есть кнопка «Попробовать», с помощью которой можно отлаживать API."
|
||||
},
|
||||
"notification": {
|
||||
"desc": "Настройка параметров уведомлений для различных каналов",
|
||||
"loading": "Загрузка...",
|
||||
"no_channels": "Нет доступных каналов уведомлений. Обратитесь к администратору для настройки каналов уведомлений.",
|
||||
"status": "Статус",
|
||||
"enabled": "Включено",
|
||||
"not_configured": "Не настроено",
|
||||
"configure": "Настроить",
|
||||
"enable_notifications": "Включить уведомления",
|
||||
"trigger_conditions": "Условия срабатывания",
|
||||
"trigger_on_all_messages": "Получать все сообщения",
|
||||
"trigger_on_mentions": "Уведомлять при упоминании",
|
||||
"skip_when_online": "Пропускать уведомления в сети",
|
||||
"select_groups": "Выбрать группы",
|
||||
"save": "Сохранить",
|
||||
"saving": "Сохранение...",
|
||||
"cancel": "Отмена",
|
||||
"delete": "Удалить конфигурацию",
|
||||
"deleting": "Удаление...",
|
||||
"save_success": "Настройки уведомлений успешно сохранены",
|
||||
"save_error": "Не удалось сохранить настройки уведомлений",
|
||||
"delete_success": "Настройки уведомлений успешно удалены",
|
||||
"delete_error": "Не удалось удалить настройки уведомлений"
|
||||
},
|
||||
"admin_notification_channels": {
|
||||
"desc": "Настроить каналы уведомлений для пользователей",
|
||||
"create_channel": "Создать канал",
|
||||
"edit_channel": "Редактировать",
|
||||
"delete_channel": "Удалить",
|
||||
"test_channel": "Тест",
|
||||
"channel_name": "Название канала",
|
||||
"channel_type": "Тип канала",
|
||||
"channel_status": "Статус",
|
||||
"enabled": "Включено",
|
||||
"disabled": "Отключено",
|
||||
"actions": "Действия",
|
||||
"create_title": "Создать канал уведомлений",
|
||||
"create_desc": "Настроить новый канал уведомлений",
|
||||
"edit_title": "Редактировать канал уведомлений",
|
||||
"edit_desc": "Обновить конфигурацию канала",
|
||||
"delete_title": "Удалить канал",
|
||||
"delete_desc": "Вы уверены, что хотите удалить этот канал? Все пользовательские настройки для этого канала будут удалены.",
|
||||
"test_success": "Тестовое уведомление успешно отправлено",
|
||||
"test_error": "Не удалось отправить тестовое уведомление",
|
||||
"create_success": "Канал успешно создан",
|
||||
"create_error": "Не удалось создать канал",
|
||||
"update_success": "Канал успешно обновлен",
|
||||
"update_error": "Не удалось обновить канал",
|
||||
"delete_success": "Канал успешно удален",
|
||||
"delete_error": "Не удалось удалить канал",
|
||||
"config_section": "Конфигурация канала",
|
||||
"basic_info": "Основная информация",
|
||||
"enable_channel": "Включить канал",
|
||||
"rate_limit": "Ограничение скорости (запросов в секунду)",
|
||||
"no_channels": "Каналы уведомлений еще не настроены"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"my_account": "Hesabım",
|
||||
"config": "Yapılandırma",
|
||||
"bot": "Bot & Webhook",
|
||||
"notification_channels": "Bildirim Kanalları",
|
||||
"firebase": "Firebase",
|
||||
"agora": "Agora",
|
||||
"smtp": "SMTP",
|
||||
@@ -24,7 +25,8 @@
|
||||
"version": "Sürüm",
|
||||
"faq": "SSS",
|
||||
"feedback": "Geri Bildirim",
|
||||
"video": "Video"
|
||||
"video": "Video",
|
||||
"notification": "Bildirimler"
|
||||
},
|
||||
"vocespace": {
|
||||
"add": "Ekle",
|
||||
@@ -238,5 +240,61 @@
|
||||
"step_2": "İkinci Adım: Oturum açma jetonunu girin",
|
||||
"step_2_desc": "Jetonu 'X-API-Key' http header'e erişerek alabilir veya aşağıdaki metni kopyalayabilirsiniz:",
|
||||
"last": "Her API'de bir 'Deneyin' düğmesi vardır ve bununla API'de hata ayıklayabilirsiniz"
|
||||
},
|
||||
"notification": {
|
||||
"desc": "Farklı kanallar için bildirim tercihlerini yapılandırın",
|
||||
"loading": "Yükleniyor...",
|
||||
"no_channels": "Kullanılabilir bildirim kanalı yok. Bildirim kanallarını yapılandırmak için yöneticinizle iletişime geçin.",
|
||||
"status": "Durum",
|
||||
"enabled": "Etkin",
|
||||
"not_configured": "Yapılandırılmamış",
|
||||
"configure": "Yapılandır",
|
||||
"enable_notifications": "Bildirimleri etkinleştir",
|
||||
"trigger_conditions": "Tetikleme koşulları",
|
||||
"trigger_on_all_messages": "Tüm mesajları al",
|
||||
"trigger_on_mentions": "Bahsedildiğinde bildir",
|
||||
"skip_when_online": "Çevrimiçiyken bildirimleri atla",
|
||||
"select_groups": "Grupları seç",
|
||||
"save": "Kaydet",
|
||||
"saving": "Kaydediliyor...",
|
||||
"cancel": "İptal",
|
||||
"delete": "Yapılandırmayı sil",
|
||||
"deleting": "Siliniyor...",
|
||||
"save_success": "Bildirim tercihleri başarıyla kaydedildi",
|
||||
"save_error": "Bildirim tercihleri kaydedilemedi",
|
||||
"delete_success": "Bildirim tercihleri başarıyla silindi",
|
||||
"delete_error": "Bildirim tercihleri silinemedi"
|
||||
},
|
||||
"admin_notification_channels": {
|
||||
"desc": "Kullanıcılar için bildirim kanallarını yapılandır",
|
||||
"create_channel": "Kanal Oluştur",
|
||||
"edit_channel": "Düzenle",
|
||||
"delete_channel": "Sil",
|
||||
"test_channel": "Test Et",
|
||||
"channel_name": "Kanal Adı",
|
||||
"channel_type": "Kanal Türü",
|
||||
"channel_status": "Durum",
|
||||
"enabled": "Etkin",
|
||||
"disabled": "Devre Dışı",
|
||||
"actions": "İşlemler",
|
||||
"create_title": "Bildirim Kanalı Oluştur",
|
||||
"create_desc": "Yeni bir bildirim kanalı yapılandır",
|
||||
"edit_title": "Bildirim Kanalını Düzenle",
|
||||
"edit_desc": "Kanal yapılandırmasını güncelle",
|
||||
"delete_title": "Kanalı Sil",
|
||||
"delete_desc": "Bu kanalı silmek istediğinizden emin misiniz? Bu kanal için tüm kullanıcı tercihleri kaldırılacaktır.",
|
||||
"test_success": "Test bildirimi başarıyla gönderildi",
|
||||
"test_error": "Test bildirimi gönderilemedi",
|
||||
"create_success": "Kanal başarıyla oluşturuldu",
|
||||
"create_error": "Kanal oluşturulamadı",
|
||||
"update_success": "Kanal başarıyla güncellendi",
|
||||
"update_error": "Kanal güncellenemedi",
|
||||
"delete_success": "Kanal başarıyla silindi",
|
||||
"delete_error": "Kanal silinemedi",
|
||||
"config_section": "Kanal Yapılandırması",
|
||||
"basic_info": "Temel Bilgiler",
|
||||
"enable_channel": "Kanalı Etkinleştir",
|
||||
"rate_limit": "Hız sınırı (saniye başına istek)",
|
||||
"no_channels": "Henüz bildirim kanalı yapılandırılmamış"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"my_account": "我的账号",
|
||||
"config": "配置",
|
||||
"bot": "机器人&Webhook",
|
||||
"notification_channels": "通知渠道",
|
||||
"firebase": "Firebase",
|
||||
"agora": "Agora",
|
||||
"smtp": "SMTP",
|
||||
@@ -23,7 +24,8 @@
|
||||
"api_doc": "API 文档",
|
||||
"version": "版本信息",
|
||||
"feedback": "反馈",
|
||||
"video": "音视频"
|
||||
"video": "音视频",
|
||||
"notification": "通知设置"
|
||||
},
|
||||
"channel": {
|
||||
"leave": "离开",
|
||||
@@ -378,5 +380,61 @@
|
||||
"step_2": "第二步:填入已登录的 Token",
|
||||
"step_2_desc": "Token 值本质是 JWT 中的 Token,所以有时长限制,如在使用时过期,请重新获取。获取方式可通过请求头 X-API-Key 拿到,或直接 Copy👇",
|
||||
"last": "最后:每个 API 都有 Try it Out 选项,可以由此调试该 API"
|
||||
},
|
||||
"notification": {
|
||||
"desc": "配置您的通知渠道",
|
||||
"loading": "加载中...",
|
||||
"no_channels": "暂无可用的通知渠道类型。请联系管理员。",
|
||||
"status": "状态",
|
||||
"enabled": "已启用",
|
||||
"disabled": "已禁用",
|
||||
"my_channels": "我的通知渠道",
|
||||
"available_types": "可用渠道类型",
|
||||
"create_channel": "创建渠道",
|
||||
"edit_channel": "编辑渠道",
|
||||
"channel_name": "渠道名称",
|
||||
"channel_config": "渠道配置",
|
||||
"enable_notifications": "启用通知",
|
||||
"trigger_conditions": "触发条件",
|
||||
"trigger_on_all_messages": "接收所有消息",
|
||||
"trigger_on_mentions": "被提及时通知",
|
||||
"skip_when_online": "在线时跳过通知",
|
||||
"select_groups": "选择群组",
|
||||
"save": "保存",
|
||||
"saving": "保存中...",
|
||||
"cancel": "取消",
|
||||
"edit": "编辑",
|
||||
"test": "测试",
|
||||
"delete": "删除",
|
||||
"save_success": "渠道保存成功",
|
||||
"save_error": "渠道保存失败",
|
||||
"create_success": "渠道创建成功",
|
||||
"create_error": "渠道创建失败",
|
||||
"delete_success": "渠道删除成功",
|
||||
"delete_error": "渠道删除失败",
|
||||
"test_success": "测试通知发送成功",
|
||||
"test_error": "测试通知发送失败"
|
||||
},
|
||||
"admin_notification_channels": {
|
||||
"desc": "为用户启用通知渠道类型",
|
||||
"enabled_types": "已启用的渠道类型",
|
||||
"available_types": "可用渠道类型",
|
||||
"enable_type": "启用",
|
||||
"channel_type": "渠道类型",
|
||||
"channel_status": "状态",
|
||||
"enabled": "已启用",
|
||||
"disabled": "已禁用",
|
||||
"actions": "操作",
|
||||
"delete_channel": "删除",
|
||||
"delete_title": "删除渠道类型",
|
||||
"delete_desc": "确定要删除此渠道类型吗?用户将无法再创建此类型的新渠道。",
|
||||
"cancel": "取消",
|
||||
"delete": "删除",
|
||||
"create_success": "渠道类型启用成功",
|
||||
"create_error": "渠道类型启用失败",
|
||||
"update_success": "渠道类型更新成功",
|
||||
"update_error": "渠道类型更新失败",
|
||||
"delete_success": "渠道类型删除成功",
|
||||
"delete_error": "渠道类型删除失败"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||
import baseQuery from "./base.query";
|
||||
import {
|
||||
AvailableChannelType,
|
||||
EnabledChannelType,
|
||||
ToggleChannelTypeDTO,
|
||||
UserNotificationChannel,
|
||||
CreateUserChannelDTO,
|
||||
UpdateUserChannelDTO,
|
||||
} from "@/types/notification";
|
||||
|
||||
export const notificationApi = createApi({
|
||||
reducerPath: "notificationApi",
|
||||
baseQuery,
|
||||
tagTypes: ["EnabledChannelTypes", "AvailableChannelTypes", "UserChannels"],
|
||||
endpoints: (builder) => ({
|
||||
// Admin endpoints
|
||||
getEnabledChannelTypes: builder.query<EnabledChannelType[], void>({
|
||||
query: () => ({ url: `/admin/notification/channel-types` }),
|
||||
providesTags: ["EnabledChannelTypes"],
|
||||
}),
|
||||
toggleChannelType: builder.mutation<EnabledChannelType, ToggleChannelTypeDTO>({
|
||||
query: (data) => ({
|
||||
url: `/admin/notification/channel-types`,
|
||||
method: "POST",
|
||||
body: data,
|
||||
}),
|
||||
invalidatesTags: ["EnabledChannelTypes", "AvailableChannelTypes"],
|
||||
}),
|
||||
deleteChannelType: builder.mutation<string, string>({
|
||||
query: (channelType) => ({
|
||||
url: `/admin/notification/channel-types/${channelType}`,
|
||||
method: "DELETE",
|
||||
}),
|
||||
invalidatesTags: ["EnabledChannelTypes", "AvailableChannelTypes"],
|
||||
}),
|
||||
|
||||
// User endpoints
|
||||
getAvailableChannelTypes: builder.query<AvailableChannelType[], void>({
|
||||
query: () => ({ url: `/user/notification/available-types` }),
|
||||
providesTags: ["AvailableChannelTypes"],
|
||||
}),
|
||||
getUserChannels: builder.query<UserNotificationChannel[], void>({
|
||||
query: () => ({ url: `/user/notification/channels` }),
|
||||
providesTags: ["UserChannels"],
|
||||
}),
|
||||
createUserChannel: builder.mutation<UserNotificationChannel, CreateUserChannelDTO>({
|
||||
query: (data) => ({
|
||||
url: `/user/notification/channels`,
|
||||
method: "POST",
|
||||
body: data,
|
||||
}),
|
||||
invalidatesTags: ["UserChannels"],
|
||||
}),
|
||||
updateUserChannel: builder.mutation<UserNotificationChannel, UpdateUserChannelDTO>({
|
||||
query: ({ id, ...data }) => ({
|
||||
url: `/user/notification/channels/${id}`,
|
||||
method: "PUT",
|
||||
body: data,
|
||||
}),
|
||||
invalidatesTags: ["UserChannels"],
|
||||
}),
|
||||
deleteUserChannel: builder.mutation<string, number>({
|
||||
query: (id) => ({
|
||||
url: `/user/notification/channels/${id}`,
|
||||
method: "DELETE",
|
||||
}),
|
||||
invalidatesTags: ["UserChannels"],
|
||||
}),
|
||||
testUserChannel: builder.mutation<string, number>({
|
||||
query: (id) => ({
|
||||
url: `/user/notification/channels/${id}/test`,
|
||||
method: "POST",
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const {
|
||||
useGetEnabledChannelTypesQuery,
|
||||
useToggleChannelTypeMutation,
|
||||
useDeleteChannelTypeMutation,
|
||||
useGetAvailableChannelTypesQuery,
|
||||
useGetUserChannelsQuery,
|
||||
useCreateUserChannelMutation,
|
||||
useUpdateUserChannelMutation,
|
||||
useDeleteUserChannelMutation,
|
||||
useTestUserChannelMutation,
|
||||
} = notificationApi;
|
||||
+5
-2
@@ -6,6 +6,7 @@ import listenerMiddleware from "./listener.middleware";
|
||||
import { authApi } from "./services/auth";
|
||||
import { channelApi } from "./services/channel";
|
||||
import { messageApi } from "./services/message";
|
||||
import { notificationApi } from "./services/notification";
|
||||
import { serverApi } from "./services/server";
|
||||
import { userApi } from "./services/user";
|
||||
import authDataReducer from "./slices/auth.data";
|
||||
@@ -44,7 +45,8 @@ const reducer = combineReducers({
|
||||
[messageApi.reducerPath]: messageApi.reducer,
|
||||
[userApi.reducerPath]: userApi.reducer,
|
||||
[channelApi.reducerPath]: channelApi.reducer,
|
||||
[serverApi.reducerPath]: serverApi.reducer
|
||||
[serverApi.reducerPath]: serverApi.reducer,
|
||||
[notificationApi.reducerPath]: notificationApi.reducer
|
||||
});
|
||||
|
||||
const store = configureStore({
|
||||
@@ -56,7 +58,8 @@ const store = configureStore({
|
||||
userApi.middleware,
|
||||
channelApi.middleware,
|
||||
serverApi.middleware,
|
||||
messageApi.middleware
|
||||
messageApi.middleware,
|
||||
notificationApi.middleware
|
||||
)
|
||||
.prepend(listenerMiddleware.middleware)
|
||||
});
|
||||
|
||||
@@ -0,0 +1,310 @@
|
||||
import { ChannelSchema, ChannelType } from "@/types/notification";
|
||||
|
||||
export const channelSchemas: ChannelSchema[] = [
|
||||
{
|
||||
type: "telegram",
|
||||
name: "Telegram Bot",
|
||||
description: "Telegram Bot API with media support",
|
||||
fields: [
|
||||
{
|
||||
name: "bot_token",
|
||||
type: "text",
|
||||
label: "Bot Token",
|
||||
placeholder: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11",
|
||||
required: true,
|
||||
description: "Get from @BotFather",
|
||||
},
|
||||
{
|
||||
name: "chat_id",
|
||||
type: "text",
|
||||
label: "Chat ID",
|
||||
placeholder: "@your_channel or user_id",
|
||||
required: true,
|
||||
description: "Channel username or user ID",
|
||||
},
|
||||
{
|
||||
name: "parse_mode",
|
||||
type: "select",
|
||||
label: "Parse Mode",
|
||||
required: false,
|
||||
description: "Message formatting mode",
|
||||
options: [
|
||||
{ value: "", label: "None" },
|
||||
{ value: "Markdown", label: "Markdown" },
|
||||
{ value: "MarkdownV2", label: "MarkdownV2" },
|
||||
{ value: "HTML", label: "HTML" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "discord",
|
||||
name: "Discord Webhook",
|
||||
description: "Discord webhook notifications with rich embeds",
|
||||
fields: [
|
||||
{
|
||||
name: "webhook_url",
|
||||
type: "url",
|
||||
label: "Webhook URL",
|
||||
placeholder: "https://discord.com/api/webhooks/...",
|
||||
required: true,
|
||||
description: "Discord webhook URL",
|
||||
},
|
||||
{
|
||||
name: "username",
|
||||
type: "text",
|
||||
label: "Username",
|
||||
placeholder: "VoceChat Bot",
|
||||
required: false,
|
||||
description: "Override webhook username",
|
||||
},
|
||||
{
|
||||
name: "avatar_url",
|
||||
type: "url",
|
||||
label: "Avatar URL",
|
||||
placeholder: "https://example.com/avatar.png",
|
||||
required: false,
|
||||
description: "Override webhook avatar",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "feishu",
|
||||
name: "Feishu Bot",
|
||||
description: "Feishu (Lark) webhook notifications",
|
||||
fields: [
|
||||
{
|
||||
name: "webhook_url",
|
||||
type: "url",
|
||||
label: "Webhook URL",
|
||||
placeholder: "https://open.feishu.cn/open-apis/bot/v2/hook/...",
|
||||
required: true,
|
||||
description: "Feishu webhook URL",
|
||||
},
|
||||
{
|
||||
name: "sign_secret",
|
||||
type: "text",
|
||||
label: "Sign Secret",
|
||||
placeholder: "Optional signing secret",
|
||||
required: false,
|
||||
description: "For signature verification",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "dingtalk",
|
||||
name: "DingTalk Bot",
|
||||
description: "DingTalk webhook notifications",
|
||||
fields: [
|
||||
{
|
||||
name: "webhook_url",
|
||||
type: "url",
|
||||
label: "Webhook URL",
|
||||
placeholder: "https://oapi.dingtalk.com/robot/send?access_token=...",
|
||||
required: true,
|
||||
description: "DingTalk webhook URL",
|
||||
},
|
||||
{
|
||||
name: "sign_secret",
|
||||
type: "text",
|
||||
label: "Sign Secret",
|
||||
placeholder: "Optional signing secret",
|
||||
required: false,
|
||||
description: "For signature verification",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "wecombot",
|
||||
name: "WeCom Bot",
|
||||
description: "WeCom (WeChat Work) webhook notifications",
|
||||
fields: [
|
||||
{
|
||||
name: "webhook_url",
|
||||
type: "url",
|
||||
label: "Webhook URL",
|
||||
placeholder: "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=...",
|
||||
required: true,
|
||||
description: "WeCom webhook URL",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "bark",
|
||||
name: "Bark",
|
||||
description: "Bark iOS notification service",
|
||||
fields: [
|
||||
{
|
||||
name: "server_url",
|
||||
type: "url",
|
||||
label: "Server URL",
|
||||
placeholder: "https://api.day.app",
|
||||
required: true,
|
||||
description: "Bark server URL",
|
||||
},
|
||||
{
|
||||
name: "device_key",
|
||||
type: "text",
|
||||
label: "Device Key",
|
||||
placeholder: "Your device key",
|
||||
required: true,
|
||||
description: "Device key from Bark app",
|
||||
},
|
||||
{
|
||||
name: "sound",
|
||||
type: "text",
|
||||
label: "Sound",
|
||||
placeholder: "default",
|
||||
required: false,
|
||||
description: "Notification sound name",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "gotify",
|
||||
name: "Gotify",
|
||||
description: "Gotify self-hosted notification server",
|
||||
fields: [
|
||||
{
|
||||
name: "server_url",
|
||||
type: "url",
|
||||
label: "Server URL",
|
||||
placeholder: "https://gotify.example.com",
|
||||
required: true,
|
||||
description: "Gotify server URL",
|
||||
},
|
||||
{
|
||||
name: "app_token",
|
||||
type: "text",
|
||||
label: "App Token",
|
||||
placeholder: "Application token",
|
||||
required: true,
|
||||
description: "Application token from Gotify",
|
||||
},
|
||||
{
|
||||
name: "priority",
|
||||
type: "number",
|
||||
label: "Priority",
|
||||
placeholder: "5",
|
||||
required: false,
|
||||
description: "Message priority (0-10)",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "serverchan",
|
||||
name: "Server Chan",
|
||||
description: "Server Chan notification service",
|
||||
fields: [
|
||||
{
|
||||
name: "send_key",
|
||||
type: "text",
|
||||
label: "SendKey",
|
||||
placeholder: "Your SendKey",
|
||||
required: true,
|
||||
description: "SendKey from Server Chan",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "pushdeer",
|
||||
name: "PushDeer",
|
||||
description: "PushDeer notification service",
|
||||
fields: [
|
||||
{
|
||||
name: "server_url",
|
||||
type: "url",
|
||||
label: "Server URL",
|
||||
placeholder: "https://api2.pushdeer.com",
|
||||
required: true,
|
||||
description: "PushDeer server URL",
|
||||
},
|
||||
{
|
||||
name: "push_key",
|
||||
type: "text",
|
||||
label: "Push Key",
|
||||
placeholder: "Your push key",
|
||||
required: true,
|
||||
description: "Push key from PushDeer",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "matrix",
|
||||
name: "Matrix",
|
||||
description: "Matrix protocol notifications",
|
||||
fields: [
|
||||
{
|
||||
name: "homeserver_url",
|
||||
type: "url",
|
||||
label: "Homeserver URL",
|
||||
placeholder: "https://matrix.org",
|
||||
required: true,
|
||||
description: "Matrix homeserver URL",
|
||||
},
|
||||
{
|
||||
name: "access_token",
|
||||
type: "text",
|
||||
label: "Access Token",
|
||||
placeholder: "Your access token",
|
||||
required: true,
|
||||
description: "Matrix access token",
|
||||
},
|
||||
{
|
||||
name: "room_id",
|
||||
type: "text",
|
||||
label: "Room ID",
|
||||
placeholder: "!roomid:matrix.org",
|
||||
required: true,
|
||||
description: "Matrix room ID",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "custom_json",
|
||||
name: "Custom JSON Webhook",
|
||||
description: "Custom HTTP webhook with JSON payload",
|
||||
fields: [
|
||||
{
|
||||
name: "webhook_url",
|
||||
type: "url",
|
||||
label: "Webhook URL",
|
||||
placeholder: "https://example.com/webhook",
|
||||
required: true,
|
||||
description: "HTTP endpoint URL",
|
||||
},
|
||||
{
|
||||
name: "method",
|
||||
type: "select",
|
||||
label: "HTTP Method",
|
||||
required: true,
|
||||
description: "HTTP request method",
|
||||
options: [
|
||||
{ value: "POST", label: "POST" },
|
||||
{ value: "PUT", label: "PUT" },
|
||||
{ value: "PATCH", label: "PATCH" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "headers",
|
||||
type: "textarea",
|
||||
label: "Custom Headers",
|
||||
placeholder: '{"Authorization": "Bearer token"}',
|
||||
required: false,
|
||||
description: "JSON object with custom headers",
|
||||
},
|
||||
{
|
||||
name: "body_template",
|
||||
type: "textarea",
|
||||
label: "Body Template",
|
||||
placeholder: '{"message": "{{content}}", "user": "{{sender}}"}',
|
||||
required: false,
|
||||
description: "JSON template with placeholders",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export function getChannelSchema(type: ChannelType): ChannelSchema | undefined {
|
||||
return channelSchemas.find((s) => s.type === type);
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
import { useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import {
|
||||
useGetEnabledChannelTypesQuery,
|
||||
useToggleChannelTypeMutation,
|
||||
useDeleteChannelTypeMutation,
|
||||
} from "@/app/services/notification";
|
||||
import { ConfigTip } from "@/components/ConfigTip";
|
||||
import Button from "@/components/styled/Button";
|
||||
import Toggle from "@/components/styled/Toggle";
|
||||
import { EnabledChannelType, ChannelType } from "@/types/notification";
|
||||
import { channelSchemas } from "./channelSchemas";
|
||||
import ServerVersionChecker from "@/components/ServerVersionChecker";
|
||||
|
||||
export default function AdminNotificationChannels() {
|
||||
const { t } = useTranslation("setting", { keyPrefix: "admin_notification_channels" });
|
||||
const { data: channelTypes = [], isLoading } = useGetEnabledChannelTypesQuery();
|
||||
const [toggleChannelType] = useToggleChannelTypeMutation();
|
||||
const [deleteChannelType] = useDeleteChannelTypeMutation();
|
||||
|
||||
const [deletingType, setDeletingType] = useState<string | null>(null);
|
||||
|
||||
const handleToggle = async (channelType: ChannelType, currentEnabled: boolean) => {
|
||||
try {
|
||||
await toggleChannelType({
|
||||
channel_type: channelType,
|
||||
enabled: !currentEnabled,
|
||||
}).unwrap();
|
||||
toast.success(t("update_success"));
|
||||
} catch (error) {
|
||||
toast.error(t("update_error"));
|
||||
console.error("Failed to toggle channel type:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (channelType: string) => {
|
||||
try {
|
||||
await deleteChannelType(channelType).unwrap();
|
||||
toast.success(t("delete_success"));
|
||||
setDeletingType(null);
|
||||
} catch (error) {
|
||||
toast.error(t("delete_error"));
|
||||
console.error("Failed to delete channel type:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEnableNew = async (channelType: ChannelType) => {
|
||||
try {
|
||||
await toggleChannelType({
|
||||
channel_type: channelType,
|
||||
enabled: true,
|
||||
}).unwrap();
|
||||
toast.success(t("create_success"));
|
||||
} catch (error) {
|
||||
toast.error(t("create_error"));
|
||||
console.error("Failed to enable channel type:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// Get all available channel types
|
||||
const allChannelTypes = channelSchemas.map((s) => s.type);
|
||||
const enabledTypes = new Set(channelTypes.map((ct) => ct.channel_type));
|
||||
const availableToEnable = allChannelTypes.filter((type) => !enabledTypes.has(type));
|
||||
|
||||
// Debug logging
|
||||
console.log("Admin Notification Channels Debug:", {
|
||||
allChannelTypes,
|
||||
enabledTypes: Array.from(enabledTypes),
|
||||
availableToEnable,
|
||||
channelTypesFromAPI: channelTypes,
|
||||
});
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="setting-container max-md:w-full">
|
||||
<div className="text-gray-500">Loading...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ServerVersionChecker version="0.5.11">
|
||||
<div className="setting-container max-md:w-full">
|
||||
<ConfigTip title={t("desc")} />
|
||||
|
||||
{/* Enabled Channel Types */}
|
||||
{channelTypes.length > 0 && (
|
||||
<div className="mt-6">
|
||||
<h3 className="font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
||||
{t("enabled_types")}
|
||||
</h3>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full border-collapse">
|
||||
<thead>
|
||||
<tr className="border-b border-gray-200 dark:border-gray-700">
|
||||
<th className="text-left py-3 px-4 font-semibold text-gray-700 dark:text-gray-300">
|
||||
{t("channel_type")}
|
||||
</th>
|
||||
<th className="text-left py-3 px-4 font-semibold text-gray-700 dark:text-gray-300">
|
||||
{t("channel_status")}
|
||||
</th>
|
||||
<th className="text-right py-3 px-4 font-semibold text-gray-700 dark:text-gray-300">
|
||||
{t("actions")}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{channelTypes.map((channelType) => {
|
||||
const schema = channelSchemas.find((s) => s.type === channelType.channel_type);
|
||||
return (
|
||||
<tr
|
||||
key={channelType.id}
|
||||
className="border-b border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800"
|
||||
>
|
||||
<td className="py-3 px-4">
|
||||
<div>
|
||||
<div className="font-medium text-gray-900 dark:text-gray-100">
|
||||
{schema?.name || channelType.channel_type}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{schema?.description}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-3 px-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Toggle
|
||||
checked={channelType.enabled}
|
||||
onClick={() =>
|
||||
handleToggle(channelType.channel_type, channelType.enabled)
|
||||
}
|
||||
/>
|
||||
<span
|
||||
className={
|
||||
channelType.enabled
|
||||
? "text-green-600 dark:text-green-400"
|
||||
: "text-gray-500"
|
||||
}
|
||||
>
|
||||
{channelType.enabled ? t("enabled") : t("disabled")}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-3 px-4">
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
className="mini danger"
|
||||
onClick={() => setDeletingType(channelType.channel_type)}
|
||||
>
|
||||
{t("delete_channel")}
|
||||
</Button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Available Channel Types to Enable */}
|
||||
<div className="mt-8">
|
||||
<h3 className="font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
||||
{t("available_types")}
|
||||
</h3>
|
||||
{availableToEnable.length > 0 ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{availableToEnable.map((type) => {
|
||||
const schema = channelSchemas.find((s) => s.type === type);
|
||||
if (!schema) return null;
|
||||
return (
|
||||
<div
|
||||
key={type}
|
||||
className="border border-gray-200 dark:border-gray-700 rounded-lg p-4 bg-white dark:bg-gray-800 flex flex-col"
|
||||
>
|
||||
<h4 className="font-semibold text-gray-900 dark:text-gray-100 mb-2">
|
||||
{schema.name}
|
||||
</h4>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 mb-4 flex-1">
|
||||
{schema.description}
|
||||
</p>
|
||||
<Button onClick={() => handleEnableNew(type)} className="w-full">
|
||||
{t("enable_type")}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-gray-500 dark:text-gray-400">
|
||||
All channel types are already enabled. Total schemas: {channelSchemas.length}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Delete Confirmation Modal */}
|
||||
{deletingType && (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white dark:bg-gray-900 rounded-lg p-6 max-w-md w-full mx-4">
|
||||
<h3 className="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
||||
{t("delete_title")}
|
||||
</h3>
|
||||
<p className="text-gray-700 dark:text-gray-300 mb-6">
|
||||
{t("delete_desc")}
|
||||
</p>
|
||||
<div className="flex gap-3 justify-end">
|
||||
<Button onClick={() => setDeletingType(null)} className="cancel">
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
<Button onClick={() => handleDelete(deletingType)} className="danger">
|
||||
{t("delete")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ServerVersionChecker>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,430 @@
|
||||
import { useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { shallowEqual } from "react-redux";
|
||||
|
||||
import {
|
||||
useGetAvailableChannelTypesQuery,
|
||||
useGetUserChannelsQuery,
|
||||
useCreateUserChannelMutation,
|
||||
useUpdateUserChannelMutation,
|
||||
useDeleteUserChannelMutation,
|
||||
useTestUserChannelMutation,
|
||||
} from "@/app/services/notification";
|
||||
import { useAppSelector } from "@/app/store";
|
||||
import { ConfigTip } from "@/components/ConfigTip";
|
||||
import Button from "@/components/styled/Button";
|
||||
import Checkbox from "@/components/styled/Checkbox";
|
||||
import Input from "@/components/styled/Input";
|
||||
import Textarea from "@/components/styled/Textarea";
|
||||
import Select from "@/components/styled/Select";
|
||||
import Label from "@/components/styled/Label";
|
||||
import Toggle from "@/components/styled/Toggle";
|
||||
import { UserNotificationChannel, ChannelType, CreateUserChannelDTO } from "@/types/notification";
|
||||
import { getChannelSchema } from "./AdminNotificationChannels/channelSchemas";
|
||||
import ServerVersionChecker from "@/components/ServerVersionChecker";
|
||||
|
||||
export default function NotificationSettings() {
|
||||
const { t } = useTranslation("setting", { keyPrefix: "notification" });
|
||||
const [editingChannelId, setEditingChannelId] = useState<number | null>(null);
|
||||
const [creatingType, setCreatingType] = useState<ChannelType | null>(null);
|
||||
const [formData, setFormData] = useState<Partial<CreateUserChannelDTO>>({});
|
||||
|
||||
// Fetch data
|
||||
const { data: availableTypes = [], isLoading: typesLoading } = useGetAvailableChannelTypesQuery();
|
||||
const { data: userChannels = [], isLoading: channelsLoading } = useGetUserChannelsQuery();
|
||||
const channels = useAppSelector((store) => Object.values(store.channels.byId), shallowEqual);
|
||||
|
||||
// Mutations
|
||||
const [createChannel, { isLoading: isCreating }] = useCreateUserChannelMutation();
|
||||
const [updateChannel, { isLoading: isUpdating }] = useUpdateUserChannelMutation();
|
||||
const [deleteChannel, { isLoading: isDeleting }] = useDeleteUserChannelMutation();
|
||||
const [testChannel] = useTestUserChannelMutation();
|
||||
|
||||
const handleCreateClick = (channelType: ChannelType) => {
|
||||
setCreatingType(channelType);
|
||||
setFormData({
|
||||
channel_type: channelType,
|
||||
name: "",
|
||||
enabled: true,
|
||||
config: {},
|
||||
trigger_on_all_messages: false,
|
||||
trigger_on_mentions: true,
|
||||
trigger_on_groups: [],
|
||||
skip_when_online: true,
|
||||
});
|
||||
};
|
||||
|
||||
const handleEditClick = (channel: UserNotificationChannel) => {
|
||||
setEditingChannelId(channel.id);
|
||||
setFormData({
|
||||
channel_type: channel.channel_type,
|
||||
name: channel.name,
|
||||
enabled: channel.enabled,
|
||||
config: channel.config,
|
||||
trigger_on_all_messages: channel.trigger_on_all_messages,
|
||||
trigger_on_mentions: channel.trigger_on_mentions,
|
||||
trigger_on_groups: channel.trigger_on_groups,
|
||||
skip_when_online: channel.skip_when_online,
|
||||
rate_limit_rps: channel.rate_limit_rps,
|
||||
});
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setEditingChannelId(null);
|
||||
setCreatingType(null);
|
||||
setFormData({});
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!formData.name?.trim()) {
|
||||
toast.error("Channel name is required");
|
||||
return;
|
||||
}
|
||||
|
||||
const schema = getChannelSchema(formData.channel_type!);
|
||||
if (schema) {
|
||||
const missingFields = schema.fields
|
||||
.filter((field) => field.required && !formData.config?.[field.name])
|
||||
.map((field) => field.label);
|
||||
|
||||
if (missingFields.length > 0) {
|
||||
toast.error(`Missing required fields: ${missingFields.join(", ")}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (editingChannelId) {
|
||||
await updateChannel({
|
||||
id: editingChannelId,
|
||||
...(formData as CreateUserChannelDTO),
|
||||
}).unwrap();
|
||||
toast.success(t("save_success"));
|
||||
} else {
|
||||
await createChannel(formData as CreateUserChannelDTO).unwrap();
|
||||
toast.success(t("create_success"));
|
||||
}
|
||||
handleCancel();
|
||||
} catch (error) {
|
||||
toast.error(editingChannelId ? t("save_error") : t("create_error"));
|
||||
console.error("Failed to save channel:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (channelId: number) => {
|
||||
try {
|
||||
await deleteChannel(channelId).unwrap();
|
||||
toast.success(t("delete_success"));
|
||||
} catch (error) {
|
||||
toast.error(t("delete_error"));
|
||||
console.error("Failed to delete channel:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTest = async (channelId: number) => {
|
||||
try {
|
||||
await testChannel(channelId).unwrap();
|
||||
toast.success(t("test_success"));
|
||||
} catch (error) {
|
||||
toast.error(t("test_error"));
|
||||
console.error("Failed to test channel:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfigChange = (fieldName: string, value: any) => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
config: {
|
||||
...prev.config,
|
||||
[fieldName]: value,
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
const handleGroupToggle = (groupId: number) => {
|
||||
setFormData((prev) => {
|
||||
const groups = prev.trigger_on_groups || [];
|
||||
const newGroups = groups.includes(groupId)
|
||||
? groups.filter((id) => id !== groupId)
|
||||
: [...groups, groupId];
|
||||
return { ...prev, trigger_on_groups: newGroups };
|
||||
});
|
||||
};
|
||||
|
||||
if (typesLoading || channelsLoading) {
|
||||
return (
|
||||
<div className="setting-container max-md:w-full">
|
||||
<div className="text-gray-500">{t("loading")}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (availableTypes.length === 0) {
|
||||
return (
|
||||
<div className="setting-container max-md:w-full">
|
||||
<ConfigTip title={t("desc")} />
|
||||
<div className="text-gray-500 mt-4">{t("no_channels")}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const schema = formData.channel_type ? getChannelSchema(formData.channel_type) : null;
|
||||
const isEditing = editingChannelId !== null || creatingType !== null;
|
||||
|
||||
return (
|
||||
<ServerVersionChecker version="0.5.11">
|
||||
<div className="setting-container max-md:w-full">
|
||||
<ConfigTip title={t("desc")} />
|
||||
|
||||
{/* User's Configured Channels */}
|
||||
{userChannels.length > 0 && (
|
||||
<div className="mt-6">
|
||||
<h3 className="font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
||||
{t("my_channels")}
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
{userChannels.map((channel) => {
|
||||
const channelSchema = getChannelSchema(channel.channel_type);
|
||||
return (
|
||||
<div
|
||||
key={channel.id}
|
||||
className="border border-gray-200 dark:border-gray-700 rounded-lg p-4 bg-white dark:bg-gray-800"
|
||||
>
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<h4 className="font-semibold text-gray-900 dark:text-gray-100">
|
||||
{channel.name}
|
||||
</h4>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
|
||||
{channelSchema?.name || channel.channel_type}
|
||||
</p>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300 mt-2">
|
||||
{t("status")}:{" "}
|
||||
<span
|
||||
className={
|
||||
channel.enabled
|
||||
? "text-green-600 dark:text-green-400"
|
||||
: "text-gray-500"
|
||||
}
|
||||
>
|
||||
{channel.enabled ? t("enabled") : t("disabled")}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-2 ml-4">
|
||||
<Button className="mini" onClick={() => handleEditClick(channel)}>
|
||||
{t("edit")}
|
||||
</Button>
|
||||
<Button className="mini" onClick={() => handleTest(channel.id)}>
|
||||
{t("test")}
|
||||
</Button>
|
||||
<Button
|
||||
className="mini danger"
|
||||
onClick={() => handleDelete(channel.id)}
|
||||
disabled={isDeleting}
|
||||
>
|
||||
{t("delete")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Available Channel Types */}
|
||||
{!isEditing && (
|
||||
<div className="mt-8">
|
||||
<h3 className="font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
||||
{t("available_types")}
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{availableTypes.map((type) => {
|
||||
const schema = getChannelSchema(type.id as ChannelType);
|
||||
return (
|
||||
<div
|
||||
key={type.id}
|
||||
className="border border-gray-200 dark:border-gray-700 rounded-lg p-4 bg-white dark:bg-gray-800 flex flex-col"
|
||||
>
|
||||
<h4 className="font-semibold text-gray-900 dark:text-gray-100 mb-2">
|
||||
{type.name}
|
||||
</h4>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 mb-4 flex-1">
|
||||
{type.description}
|
||||
</p>
|
||||
<Button
|
||||
onClick={() => handleCreateClick(type.id as ChannelType)}
|
||||
className="w-full"
|
||||
>
|
||||
{t("create_channel")}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Create/Edit Form */}
|
||||
{isEditing && formData.channel_type && (
|
||||
<div className="mt-8 border border-gray-200 dark:border-gray-700 rounded-lg p-6 bg-white dark:bg-gray-800">
|
||||
<h3 className="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-6">
|
||||
{editingChannelId ? t("edit_channel") : t("create_channel")}
|
||||
</h3>
|
||||
|
||||
<div className="space-y-4 max-h-[60vh] overflow-y-auto">
|
||||
{/* Channel Name */}
|
||||
<div>
|
||||
<Label className="block mb-2">{t("channel_name")} *</Label>
|
||||
<Input
|
||||
value={formData.name || ""}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
placeholder="My Notification Channel"
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Channel Configuration */}
|
||||
{schema && schema.fields.length > 0 && (
|
||||
<div className="pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<Label className="block font-semibold mb-3">{t("channel_config")}</Label>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mb-4">
|
||||
{schema.description}
|
||||
</p>
|
||||
|
||||
{schema.fields.map((field) => (
|
||||
<div key={field.name} className="mb-3">
|
||||
<Label className="block mb-2">
|
||||
{field.label} {field.required && "*"}
|
||||
</Label>
|
||||
|
||||
{field.type === "textarea" ? (
|
||||
<Textarea
|
||||
value={formData.config?.[field.name] || ""}
|
||||
onChange={(e) => handleConfigChange(field.name, e.target.value)}
|
||||
placeholder={field.placeholder}
|
||||
className="w-full"
|
||||
rows={3}
|
||||
/>
|
||||
) : field.type === "select" ? (
|
||||
<Select
|
||||
value={formData.config?.[field.name] || ""}
|
||||
onChange={(e) => handleConfigChange(field.name, e.target.value)}
|
||||
className="w-full"
|
||||
>
|
||||
{field.options?.map((opt) => (
|
||||
<option key={opt.value} value={opt.value}>
|
||||
{opt.label}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
) : (
|
||||
<Input
|
||||
type={field.type}
|
||||
value={formData.config?.[field.name] || ""}
|
||||
onChange={(e) => handleConfigChange(field.name, e.target.value)}
|
||||
placeholder={field.placeholder}
|
||||
className="w-full"
|
||||
/>
|
||||
)}
|
||||
|
||||
{field.description && (
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
{field.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Enable Channel */}
|
||||
<div className="flex items-center justify-between pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<Label>{t("enable_notifications")}</Label>
|
||||
<Toggle
|
||||
checked={formData.enabled || false}
|
||||
onClick={() => setFormData({ ...formData, enabled: !formData.enabled })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Trigger Conditions */}
|
||||
<div className="space-y-3 pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<Label className="block font-semibold">{t("trigger_conditions")}</Label>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={formData.trigger_on_all_messages || false}
|
||||
onClick={() =>
|
||||
setFormData({
|
||||
...formData,
|
||||
trigger_on_all_messages: !formData.trigger_on_all_messages,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<span className="text-sm">{t("trigger_on_all_messages")}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={formData.trigger_on_mentions || false}
|
||||
onClick={() =>
|
||||
setFormData({
|
||||
...formData,
|
||||
trigger_on_mentions: !formData.trigger_on_mentions,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<span className="text-sm">{t("trigger_on_mentions")}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={formData.skip_when_online || false}
|
||||
onClick={() =>
|
||||
setFormData({
|
||||
...formData,
|
||||
skip_when_online: !formData.skip_when_online,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<span className="text-sm">{t("skip_when_online")}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Select Groups */}
|
||||
{channels.length > 0 && (
|
||||
<div className="space-y-2 pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<Label className="block font-semibold">{t("select_groups")}</Label>
|
||||
<div className="space-y-2 max-h-48 overflow-y-auto">
|
||||
{channels.map((group) => (
|
||||
<div key={group.gid} className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={(formData.trigger_on_groups || []).includes(group.gid)}
|
||||
onClick={() => handleGroupToggle(group.gid)}
|
||||
/>
|
||||
<span className="text-sm">{group.name}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex gap-2 pt-6 mt-6 border-t border-gray-200 dark:border-gray-700">
|
||||
<Button onClick={handleSave} disabled={isCreating || isUpdating}>
|
||||
{isCreating || isUpdating ? t("saving") : t("save")}
|
||||
</Button>
|
||||
<Button onClick={handleCancel} disabled={isCreating || isUpdating}>
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ServerVersionChecker>
|
||||
);
|
||||
}
|
||||
@@ -13,10 +13,12 @@ import ConfigSMTP from "./config/SMTP";
|
||||
import DataManagement from "./DataManagement";
|
||||
import License from "./License";
|
||||
import MyAccount from "./MyAccount";
|
||||
import NotificationSettings from "./NotificationSettings";
|
||||
import Overview from "./Overview";
|
||||
import Widget from "./Widget";
|
||||
import { shallowEqual } from "react-redux";
|
||||
import { ConfigVocespace } from "./config/Vocespace";
|
||||
import AdminNotificationChannels from "./AdminNotificationChannels";
|
||||
|
||||
const dataManagementNav = {
|
||||
name: "data_management",
|
||||
@@ -35,6 +37,10 @@ const navs = [
|
||||
name: "my_account",
|
||||
component: <MyAccount />,
|
||||
},
|
||||
{
|
||||
name: "notification",
|
||||
component: <NotificationSettings />,
|
||||
},
|
||||
{
|
||||
name: "members",
|
||||
component: <ManageMembers />,
|
||||
@@ -50,6 +56,11 @@ const navs = [
|
||||
component: <BotConfig />,
|
||||
admin: true,
|
||||
},
|
||||
{
|
||||
name: "notification_channels",
|
||||
component: <AdminNotificationChannels />,
|
||||
admin: true,
|
||||
},
|
||||
{
|
||||
name: "firebase",
|
||||
component: <ConfigFirebase />,
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
// Notification channel types
|
||||
|
||||
/**
|
||||
* Channel types supported by the notification system
|
||||
*/
|
||||
export type ChannelType =
|
||||
| 'telegram' | 'discord' | 'feishu' | 'dingtalk'
|
||||
| 'wecombot' | 'bark' | 'gotify' | 'serverchan'
|
||||
| 'pushdeer' | 'matrix' | 'custom_json';
|
||||
|
||||
/**
|
||||
* Available channel type (returned by user API)
|
||||
*/
|
||||
export interface AvailableChannelType {
|
||||
id: string; // channel_type as string
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin: Enabled channel type configuration
|
||||
*/
|
||||
export interface EnabledChannelType {
|
||||
id: number;
|
||||
channel_type: ChannelType;
|
||||
enabled: boolean;
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin: DTO for enabling/disabling channel type
|
||||
*/
|
||||
export interface ToggleChannelTypeDTO {
|
||||
channel_type: ChannelType;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* User: Notification channel configuration
|
||||
*/
|
||||
export interface UserNotificationChannel {
|
||||
id: number;
|
||||
user_id: number;
|
||||
channel_type: ChannelType;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
config: Record<string, any>;
|
||||
trigger_on_all_messages: boolean;
|
||||
trigger_on_mentions: boolean;
|
||||
trigger_on_groups: number[];
|
||||
skip_when_online: boolean;
|
||||
rate_limit_rps?: number;
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* User: DTO for creating user notification channel
|
||||
*/
|
||||
export interface CreateUserChannelDTO {
|
||||
channel_type: ChannelType;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
config: Record<string, any>;
|
||||
trigger_on_all_messages: boolean;
|
||||
trigger_on_mentions: boolean;
|
||||
trigger_on_groups: number[];
|
||||
skip_when_online: boolean;
|
||||
rate_limit_rps?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* User: DTO for updating user notification channel
|
||||
*/
|
||||
export interface UpdateUserChannelDTO extends CreateUserChannelDTO {
|
||||
id: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schema definition for channel configuration fields
|
||||
*/
|
||||
export interface ChannelFieldSchema {
|
||||
name: string;
|
||||
type: 'text' | 'url' | 'number' | 'textarea' | 'select';
|
||||
label: string;
|
||||
placeholder?: string;
|
||||
required: boolean;
|
||||
description?: string;
|
||||
options?: { value: string; label: string }[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Schema definition for a channel type
|
||||
*/
|
||||
export interface ChannelSchema {
|
||||
type: ChannelType;
|
||||
name: string;
|
||||
description: string;
|
||||
fields: ChannelFieldSchema[];
|
||||
}
|
||||
Reference in New Issue
Block a user