feat: 增加消息推送设置 (#302)
* feat: 增加消息推送设置 * chore: bump version to v0.9.52
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "vocechat-web",
|
"name": "vocechat-web",
|
||||||
"version": "0.9.51",
|
"version": "0.9.52",
|
||||||
"homepage": "https://voce.chat",
|
"homepage": "https://voce.chat",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@metamask/onboarding": "^1.0.1",
|
"@metamask/onboarding": "^1.0.1",
|
||||||
|
|||||||
Generated
+6
-11
@@ -2604,11 +2604,8 @@ packages:
|
|||||||
caniuse-api@3.0.0:
|
caniuse-api@3.0.0:
|
||||||
resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==}
|
resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001718:
|
caniuse-lite@1.0.30001764:
|
||||||
resolution: {integrity: sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==}
|
resolution: {integrity: sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001731:
|
|
||||||
resolution: {integrity: sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==}
|
|
||||||
|
|
||||||
chalk@4.1.2:
|
chalk@4.1.2:
|
||||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||||
@@ -8773,7 +8770,7 @@ snapshots:
|
|||||||
autoprefixer@10.4.21(postcss@8.5.6):
|
autoprefixer@10.4.21(postcss@8.5.6):
|
||||||
dependencies:
|
dependencies:
|
||||||
browserslist: 4.25.1
|
browserslist: 4.25.1
|
||||||
caniuse-lite: 1.0.30001718
|
caniuse-lite: 1.0.30001764
|
||||||
fraction.js: 4.3.7
|
fraction.js: 4.3.7
|
||||||
normalize-range: 0.1.2
|
normalize-range: 0.1.2
|
||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
@@ -8921,7 +8918,7 @@ snapshots:
|
|||||||
|
|
||||||
browserslist@4.25.1:
|
browserslist@4.25.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
caniuse-lite: 1.0.30001731
|
caniuse-lite: 1.0.30001764
|
||||||
electron-to-chromium: 1.5.199
|
electron-to-chromium: 1.5.199
|
||||||
node-releases: 2.0.19
|
node-releases: 2.0.19
|
||||||
update-browserslist-db: 1.1.3(browserslist@4.25.1)
|
update-browserslist-db: 1.1.3(browserslist@4.25.1)
|
||||||
@@ -8961,13 +8958,11 @@ snapshots:
|
|||||||
caniuse-api@3.0.0:
|
caniuse-api@3.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
browserslist: 4.25.1
|
browserslist: 4.25.1
|
||||||
caniuse-lite: 1.0.30001718
|
caniuse-lite: 1.0.30001764
|
||||||
lodash.memoize: 4.1.2
|
lodash.memoize: 4.1.2
|
||||||
lodash.uniq: 4.5.0
|
lodash.uniq: 4.5.0
|
||||||
|
|
||||||
caniuse-lite@1.0.30001718: {}
|
caniuse-lite@1.0.30001764: {}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001731: {}
|
|
||||||
|
|
||||||
chalk@4.1.2:
|
chalk@4.1.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"my_account": "Mein Konto",
|
"my_account": "Mein Konto",
|
||||||
"config": "Konfiguration",
|
"config": "Konfiguration",
|
||||||
"bot": "Bot & Webhook",
|
"bot": "Bot & Webhook",
|
||||||
|
"notification_channels": "Benachrichtigungskanäle",
|
||||||
"firebase": "Firebase",
|
"firebase": "Firebase",
|
||||||
"agora": "Agora",
|
"agora": "Agora",
|
||||||
"smtp": "SMTP",
|
"smtp": "SMTP",
|
||||||
@@ -23,7 +24,8 @@
|
|||||||
"about": "Über",
|
"about": "Über",
|
||||||
"api_doc": "API-Dokumentation",
|
"api_doc": "API-Dokumentation",
|
||||||
"version": "Version",
|
"version": "Version",
|
||||||
"video": "Videoanrufe"
|
"video": "Videoanrufe",
|
||||||
|
"notification": "Benachrichtigungen"
|
||||||
},
|
},
|
||||||
"vocespace": {
|
"vocespace": {
|
||||||
"add": "Hinzufügen",
|
"add": "Hinzufügen",
|
||||||
@@ -350,6 +352,62 @@
|
|||||||
"step_1": "Schritt 1: Eigene Server-URL eintragen",
|
"step_1": "Schritt 1: Eigene Server-URL eintragen",
|
||||||
"step_2": "Schritt 2: Login-Token eingeben",
|
"step_2": "Schritt 2: Login-Token eingeben",
|
||||||
"step_2_desc": "Token per X-API-Key-Header abrufen oder unten kopieren:",
|
"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",
|
"my_account": "My Account",
|
||||||
"config": "Configuration",
|
"config": "Configuration",
|
||||||
"bot": "Bot & Webhook",
|
"bot": "Bot & Webhook",
|
||||||
|
"notification_channels": "Notification Channels",
|
||||||
"firebase": "Firebase",
|
"firebase": "Firebase",
|
||||||
"agora": "Agora",
|
"agora": "Agora",
|
||||||
"vocespace": "Video",
|
"vocespace": "Video",
|
||||||
@@ -24,7 +25,8 @@
|
|||||||
"about": "About",
|
"about": "About",
|
||||||
"api_doc": "API Documentation",
|
"api_doc": "API Documentation",
|
||||||
"version": "Version",
|
"version": "Version",
|
||||||
"video": "Video"
|
"video": "Video",
|
||||||
|
"notification": "Notifications"
|
||||||
},
|
},
|
||||||
"channel": {
|
"channel": {
|
||||||
"leave": "Leave Channel",
|
"leave": "Leave Channel",
|
||||||
@@ -379,5 +381,61 @@
|
|||||||
"step_2": "Step Two: Input the login token",
|
"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:",
|
"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"
|
"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",
|
"my_account": "Mi Cuenta",
|
||||||
"config": "Configuración",
|
"config": "Configuración",
|
||||||
"bot": "Bot & Webhook",
|
"bot": "Bot & Webhook",
|
||||||
|
"notification_channels": "Canales de Notificación",
|
||||||
"firebase": "Firebase",
|
"firebase": "Firebase",
|
||||||
"agora": "Agora",
|
"agora": "Agora",
|
||||||
"smtp": "SMTP",
|
"smtp": "SMTP",
|
||||||
@@ -23,7 +24,8 @@
|
|||||||
"about": "Acerca de",
|
"about": "Acerca de",
|
||||||
"api_doc": "Documentación de API",
|
"api_doc": "Documentación de API",
|
||||||
"version": "Versión",
|
"version": "Versión",
|
||||||
"video": "Video"
|
"video": "Video",
|
||||||
|
"notification": "Notificaciones"
|
||||||
},
|
},
|
||||||
"vocespace": {
|
"vocespace": {
|
||||||
"add": "Agregar",
|
"add": "Agregar",
|
||||||
@@ -293,5 +295,61 @@
|
|||||||
"step_2": "Paso Dos: Ingresa el token de inicio de sesión",
|
"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:",
|
"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"
|
"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",
|
"my_account": "Mon Compte",
|
||||||
"config": "Configuration",
|
"config": "Configuration",
|
||||||
"bot": "Bot & Webhook",
|
"bot": "Bot & Webhook",
|
||||||
|
"notification_channels": "Canaux de Notification",
|
||||||
"firebase": "Firebase",
|
"firebase": "Firebase",
|
||||||
"agora": "Agora",
|
"agora": "Agora",
|
||||||
"smtp": "SMTP",
|
"smtp": "SMTP",
|
||||||
@@ -23,7 +24,8 @@
|
|||||||
"about": "À Propos",
|
"about": "À Propos",
|
||||||
"api_doc": "Documentation API",
|
"api_doc": "Documentation API",
|
||||||
"version": "Version",
|
"version": "Version",
|
||||||
"video": "Vidéo"
|
"video": "Vidéo",
|
||||||
|
"notification": "Notifications"
|
||||||
},
|
},
|
||||||
"vocespace": {
|
"vocespace": {
|
||||||
"add": "Ajouter",
|
"add": "Ajouter",
|
||||||
@@ -299,5 +301,61 @@
|
|||||||
"step_2": "Étape Deux : Entrez le token de connexion",
|
"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 :",
|
"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"
|
"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": "メンバー",
|
"members": "メンバー",
|
||||||
"my_account": "アカウント",
|
"my_account": "アカウント",
|
||||||
"config": "設定",
|
"config": "設定",
|
||||||
|
"bot": "ボット&Webhook",
|
||||||
|
"notification_channels": "通知チャンネル",
|
||||||
"firebase": "Firebase",
|
"firebase": "Firebase",
|
||||||
"agora": "Agora",
|
"agora": "Agora",
|
||||||
"smtp": "SMTP",
|
"smtp": "SMTP",
|
||||||
@@ -19,7 +21,8 @@
|
|||||||
"about": "このアプリについて",
|
"about": "このアプリについて",
|
||||||
"api_doc": "API ドキュメント",
|
"api_doc": "API ドキュメント",
|
||||||
"version": "Version",
|
"version": "Version",
|
||||||
"video": "ビデオ"
|
"video": "ビデオ",
|
||||||
|
"notification": "通知設定"
|
||||||
},
|
},
|
||||||
"vocespace": {
|
"vocespace": {
|
||||||
"add": "追加",
|
"add": "追加",
|
||||||
@@ -165,5 +168,61 @@
|
|||||||
"step_2": "2. 登録済みのトークンを入力してください",
|
"step_2": "2. 登録済みのトークンを入力してください",
|
||||||
"step_2_desc": "トークンを取得するには、http header の X-API-Key にアクセスしてください、もしくは以下のテキストを直接コピーで使用してください👇",
|
"step_2_desc": "トークンを取得するには、http header の X-API-Key にアクセスしてください、もしくは以下のテキストを直接コピーで使用してください👇",
|
||||||
"last": "3. 最後に各 API には Try it Out オプションが付いてる、あれを使って API を試すことができます"
|
"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",
|
"my_account": "My Account",
|
||||||
"config": "Configuration",
|
"config": "Configuration",
|
||||||
"bot": "Bot & Webhook",
|
"bot": "Bot & Webhook",
|
||||||
|
"notification_channels": "Canais de Notificação",
|
||||||
"firebase": "Firebase",
|
"firebase": "Firebase",
|
||||||
"agora": "Agora",
|
"agora": "Agora",
|
||||||
"smtp": "SMTP",
|
"smtp": "SMTP",
|
||||||
@@ -23,7 +24,8 @@
|
|||||||
"about": "About",
|
"about": "About",
|
||||||
"api_doc": "API Documentation",
|
"api_doc": "API Documentation",
|
||||||
"version": "Version",
|
"version": "Version",
|
||||||
"video": "Video"
|
"video": "Video",
|
||||||
|
"notification": "Notificações"
|
||||||
},
|
},
|
||||||
"vocespace": {
|
"vocespace": {
|
||||||
"add": "Adicionar",
|
"add": "Adicionar",
|
||||||
@@ -285,5 +287,61 @@
|
|||||||
"step_2": "Step Two: Input the login token",
|
"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:",
|
"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"
|
"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": "Мой аккаунт",
|
"my_account": "Мой аккаунт",
|
||||||
"config": "Конфигурация",
|
"config": "Конфигурация",
|
||||||
"bot": "Bot & Webhook",
|
"bot": "Bot & Webhook",
|
||||||
|
"notification_channels": "Каналы уведомлений",
|
||||||
"firebase": "Firebase",
|
"firebase": "Firebase",
|
||||||
"agora": "Agora",
|
"agora": "Agora",
|
||||||
"smtp": "SMTP",
|
"smtp": "SMTP",
|
||||||
@@ -23,7 +24,8 @@
|
|||||||
"about": "About",
|
"about": "About",
|
||||||
"api_doc": "API Documentation",
|
"api_doc": "API Documentation",
|
||||||
"version": "Версия",
|
"version": "Версия",
|
||||||
"video": "Видео"
|
"video": "Видео",
|
||||||
|
"notification": "Уведомления"
|
||||||
},
|
},
|
||||||
"vocespace": {
|
"vocespace": {
|
||||||
"add": "Добавить",
|
"add": "Добавить",
|
||||||
@@ -345,5 +347,61 @@
|
|||||||
"step_2": "Шаг второй: введите токен входа",
|
"step_2": "Шаг второй: введите токен входа",
|
||||||
"step_2_desc": "Вы можете получить токен, обратившись к http-заголовку X-API-Key или просто скопировав текст ниже:",
|
"step_2_desc": "Вы можете получить токен, обратившись к http-заголовку X-API-Key или просто скопировав текст ниже:",
|
||||||
"last": "У каждого API есть кнопка «Попробовать», с помощью которой можно отлаживать API."
|
"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",
|
"my_account": "Hesabım",
|
||||||
"config": "Yapılandırma",
|
"config": "Yapılandırma",
|
||||||
"bot": "Bot & Webhook",
|
"bot": "Bot & Webhook",
|
||||||
|
"notification_channels": "Bildirim Kanalları",
|
||||||
"firebase": "Firebase",
|
"firebase": "Firebase",
|
||||||
"agora": "Agora",
|
"agora": "Agora",
|
||||||
"smtp": "SMTP",
|
"smtp": "SMTP",
|
||||||
@@ -24,7 +25,8 @@
|
|||||||
"version": "Sürüm",
|
"version": "Sürüm",
|
||||||
"faq": "SSS",
|
"faq": "SSS",
|
||||||
"feedback": "Geri Bildirim",
|
"feedback": "Geri Bildirim",
|
||||||
"video": "Video"
|
"video": "Video",
|
||||||
|
"notification": "Bildirimler"
|
||||||
},
|
},
|
||||||
"vocespace": {
|
"vocespace": {
|
||||||
"add": "Ekle",
|
"add": "Ekle",
|
||||||
@@ -238,5 +240,61 @@
|
|||||||
"step_2": "İkinci Adım: Oturum açma jetonunu girin",
|
"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:",
|
"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"
|
"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": "我的账号",
|
"my_account": "我的账号",
|
||||||
"config": "配置",
|
"config": "配置",
|
||||||
"bot": "机器人&Webhook",
|
"bot": "机器人&Webhook",
|
||||||
|
"notification_channels": "通知渠道",
|
||||||
"firebase": "Firebase",
|
"firebase": "Firebase",
|
||||||
"agora": "Agora",
|
"agora": "Agora",
|
||||||
"smtp": "SMTP",
|
"smtp": "SMTP",
|
||||||
@@ -23,7 +24,8 @@
|
|||||||
"api_doc": "API 文档",
|
"api_doc": "API 文档",
|
||||||
"version": "版本信息",
|
"version": "版本信息",
|
||||||
"feedback": "反馈",
|
"feedback": "反馈",
|
||||||
"video": "音视频"
|
"video": "音视频",
|
||||||
|
"notification": "通知设置"
|
||||||
},
|
},
|
||||||
"channel": {
|
"channel": {
|
||||||
"leave": "离开",
|
"leave": "离开",
|
||||||
@@ -378,5 +380,61 @@
|
|||||||
"step_2": "第二步:填入已登录的 Token",
|
"step_2": "第二步:填入已登录的 Token",
|
||||||
"step_2_desc": "Token 值本质是 JWT 中的 Token,所以有时长限制,如在使用时过期,请重新获取。获取方式可通过请求头 X-API-Key 拿到,或直接 Copy👇",
|
"step_2_desc": "Token 值本质是 JWT 中的 Token,所以有时长限制,如在使用时过期,请重新获取。获取方式可通过请求头 X-API-Key 拿到,或直接 Copy👇",
|
||||||
"last": "最后:每个 API 都有 Try it Out 选项,可以由此调试该 API"
|
"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 { authApi } from "./services/auth";
|
||||||
import { channelApi } from "./services/channel";
|
import { channelApi } from "./services/channel";
|
||||||
import { messageApi } from "./services/message";
|
import { messageApi } from "./services/message";
|
||||||
|
import { notificationApi } from "./services/notification";
|
||||||
import { serverApi } from "./services/server";
|
import { serverApi } from "./services/server";
|
||||||
import { userApi } from "./services/user";
|
import { userApi } from "./services/user";
|
||||||
import authDataReducer from "./slices/auth.data";
|
import authDataReducer from "./slices/auth.data";
|
||||||
@@ -44,7 +45,8 @@ const reducer = combineReducers({
|
|||||||
[messageApi.reducerPath]: messageApi.reducer,
|
[messageApi.reducerPath]: messageApi.reducer,
|
||||||
[userApi.reducerPath]: userApi.reducer,
|
[userApi.reducerPath]: userApi.reducer,
|
||||||
[channelApi.reducerPath]: channelApi.reducer,
|
[channelApi.reducerPath]: channelApi.reducer,
|
||||||
[serverApi.reducerPath]: serverApi.reducer
|
[serverApi.reducerPath]: serverApi.reducer,
|
||||||
|
[notificationApi.reducerPath]: notificationApi.reducer
|
||||||
});
|
});
|
||||||
|
|
||||||
const store = configureStore({
|
const store = configureStore({
|
||||||
@@ -56,7 +58,8 @@ const store = configureStore({
|
|||||||
userApi.middleware,
|
userApi.middleware,
|
||||||
channelApi.middleware,
|
channelApi.middleware,
|
||||||
serverApi.middleware,
|
serverApi.middleware,
|
||||||
messageApi.middleware
|
messageApi.middleware,
|
||||||
|
notificationApi.middleware
|
||||||
)
|
)
|
||||||
.prepend(listenerMiddleware.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 DataManagement from "./DataManagement";
|
||||||
import License from "./License";
|
import License from "./License";
|
||||||
import MyAccount from "./MyAccount";
|
import MyAccount from "./MyAccount";
|
||||||
|
import NotificationSettings from "./NotificationSettings";
|
||||||
import Overview from "./Overview";
|
import Overview from "./Overview";
|
||||||
import Widget from "./Widget";
|
import Widget from "./Widget";
|
||||||
import { shallowEqual } from "react-redux";
|
import { shallowEqual } from "react-redux";
|
||||||
import { ConfigVocespace } from "./config/Vocespace";
|
import { ConfigVocespace } from "./config/Vocespace";
|
||||||
|
import AdminNotificationChannels from "./AdminNotificationChannels";
|
||||||
|
|
||||||
const dataManagementNav = {
|
const dataManagementNav = {
|
||||||
name: "data_management",
|
name: "data_management",
|
||||||
@@ -35,6 +37,10 @@ const navs = [
|
|||||||
name: "my_account",
|
name: "my_account",
|
||||||
component: <MyAccount />,
|
component: <MyAccount />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "notification",
|
||||||
|
component: <NotificationSettings />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "members",
|
name: "members",
|
||||||
component: <ManageMembers />,
|
component: <ManageMembers />,
|
||||||
@@ -50,6 +56,11 @@ const navs = [
|
|||||||
component: <BotConfig />,
|
component: <BotConfig />,
|
||||||
admin: true,
|
admin: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "notification_channels",
|
||||||
|
component: <AdminNotificationChannels />,
|
||||||
|
admin: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "firebase",
|
name: "firebase",
|
||||||
component: <ConfigFirebase />,
|
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